Skip to content

Commit 2cef149

Browse files
committed
fixup! vfs: read full zero-size real file handles
1 parent 8a749d7 commit 2cef149

2 files changed

Lines changed: 78 additions & 6 deletions

File tree

lib/internal/vfs/providers/real.js

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypePush,
45
Promise,
56
StringPrototypeStartsWith,
67
} = primordials;
@@ -18,6 +19,8 @@ const {
1819
createENOENT,
1920
} = require('internal/vfs/errors');
2021

22+
const kReadFileUnknownBufferLength = 8192;
23+
2124
/**
2225
* A file handle that wraps a real file descriptor.
2326
*/
@@ -35,10 +38,6 @@ class RealFileHandle extends VirtualFileHandle {
3538
}
3639
}
3740

38-
#readFileBuffer(size) {
39-
return Buffer.allocUnsafe(size || 8192);
40-
}
41-
4241
#readFileResult(buffer, bytesRead, options) {
4342
buffer = buffer.subarray(0, bytesRead);
4443
const encoding = typeof options === 'string' ? options : options?.encoding;
@@ -48,6 +47,11 @@ class RealFileHandle extends VirtualFileHandle {
4847
return buffer;
4948
}
5049

50+
#readFileUnknownSizeResult(buffers, totalRead, options) {
51+
return this.#readFileResult(
52+
Buffer.concat(buffers, totalRead), totalRead, options);
53+
}
54+
5155
/**
5256
* @param {string} path The VFS path
5357
* @param {string} flags The open flags
@@ -94,7 +98,23 @@ class RealFileHandle extends VirtualFileHandle {
9498
readFileSync(options) {
9599
this.#checkClosed('read');
96100
const size = fs.fstatSync(this.#fd).size;
97-
const buffer = this.#readFileBuffer(size);
101+
if (size === 0) {
102+
const buffers = [];
103+
let totalRead = 0;
104+
105+
while (true) {
106+
const buffer = Buffer.allocUnsafe(kReadFileUnknownBufferLength);
107+
const read = fs.readSync(
108+
this.#fd, buffer, 0, buffer.byteLength, totalRead);
109+
if (read === 0) break;
110+
ArrayPrototypePush(buffers, buffer.subarray(0, read));
111+
totalRead += read;
112+
}
113+
114+
return this.#readFileUnknownSizeResult(buffers, totalRead, options);
115+
}
116+
117+
const buffer = Buffer.allocUnsafe(size);
98118
let bytesRead = 0;
99119
while (bytesRead < buffer.byteLength) {
100120
const read = fs.readSync(
@@ -114,7 +134,27 @@ class RealFileHandle extends VirtualFileHandle {
114134
async readFile(options) {
115135
this.#checkClosed('read');
116136
const size = (await this.stat()).size;
117-
const buffer = this.#readFileBuffer(size);
137+
if (size === 0) {
138+
const buffers = [];
139+
let totalRead = 0;
140+
141+
while (true) {
142+
const buffer = Buffer.allocUnsafe(kReadFileUnknownBufferLength);
143+
const { bytesRead: read } = await this.read(
144+
buffer,
145+
0,
146+
buffer.byteLength,
147+
totalRead,
148+
);
149+
if (read === 0) break;
150+
ArrayPrototypePush(buffers, buffer.subarray(0, read));
151+
totalRead += read;
152+
}
153+
154+
return this.#readFileUnknownSizeResult(buffers, totalRead, options);
155+
}
156+
157+
const buffer = Buffer.allocUnsafe(size);
118158
let bytesRead = 0;
119159
while (bytesRead < buffer.byteLength) {
120160
const { bytesRead: read } = await this.read(

test/parallel/test-vfs-real-provider-handle.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,38 @@ const myVfs = vfs.create(new vfs.RealFSProvider(root));
9292
}
9393
}
9494

95+
// ===== readFile reads past the fallback chunk when fstat reports size 0 =====
96+
{
97+
const content = 'a'.repeat(8192) + 'trailing data';
98+
fs.writeFileSync(path.join(root, 'zero-stat.txt'), content);
99+
const syncHandle = await myVfs.provider.open('/zero-stat.txt', 'r');
100+
const asyncHandle = await myVfs.provider.open('/zero-stat.txt', 'r');
101+
const originalFstatSync = fs.fstatSync;
102+
const originalFstat = fs.fstat;
103+
104+
fs.fstatSync = common.mustCall(function fstatSync(...args) {
105+
const stats = originalFstatSync.apply(this, args);
106+
stats.size = 0;
107+
return stats;
108+
});
109+
fs.fstat = common.mustCall(function fstat(fd, options, callback) {
110+
return originalFstat.call(this, fd, options, (err, stats) => {
111+
if (stats) stats.size = 0;
112+
callback(err, stats);
113+
});
114+
});
115+
116+
try {
117+
assert.strictEqual(syncHandle.readFileSync('utf8'), content);
118+
assert.strictEqual(await asyncHandle.readFile('utf8'), content);
119+
} finally {
120+
fs.fstatSync = originalFstatSync;
121+
fs.fstat = originalFstat;
122+
await syncHandle.close();
123+
await asyncHandle.close();
124+
}
125+
}
126+
95127
// ===== EBADF after close =====
96128
{
97129
await myVfs.promises.writeFile('/h.txt', 'hello');

0 commit comments

Comments
 (0)