Skip to content

Commit 4f55ebe

Browse files
committed
vfs: convert FD callbacks to async and fix documentation
Convert FD-based callback functions (close, read, write, fstat, ftruncate, fdatasync, fsync, fchmod, fchown, futimes, readv, writev) from sync handler + process.nextTick to async handlers using the undefined | Promise pattern, matching the approach already used for path-based operations. Add async FD handlers to setup.js that call the async methods on MemoryFileHandle (read, write, stat, truncate, close) instead of their sync counterparts, avoiding event loop blocking for custom VFS providers that do real I/O. Fix vfs.md documentation that was significantly out of date: - Remove false claim that chmod, chown, truncate, utimes, link, fdatasync, fsync have no VFS equivalent (all are implemented) - Add missing intercepted methods to the fs integration section (truncate, link, chmod, chown, lchown, utimes, lutimes, mkdtemp, lchmod, cp, statfs, opendir, readv, writev, ftruncate, fchmod, fchown, futimes, fdatasync, fsync) - Shrink "not intercepted" list to just glob/globSync - Add missing provider.supportsWatch documentation - Update overlay mode operation routing lists
1 parent 3459ad5 commit 4f55ebe

3 files changed

Lines changed: 118 additions & 133 deletions

File tree

doc/api/vfs.md

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -454,12 +454,13 @@ before VFS operations.
454454
**Operation routing:**
455455

456456
* **Read operations** (`readFile`, `readdir`, `stat`, `lstat`, `access`,
457-
`exists`, `realpath`, `readlink`): Check VFS first. If the path doesn't exist
458-
in VFS, fall through to the real file system.
457+
`exists`, `realpath`, `readlink`, `statfs`, `opendir`): Check VFS first. If
458+
the path doesn't exist in VFS, fall through to the real file system.
459459
* **Write operations** (`writeFile`, `appendFile`, `mkdir`, `rename`, `unlink`,
460-
`rmdir`, `symlink`, `copyFile`): Always operate on VFS. New files are created
461-
in VFS, and attempting to modify a real file that doesn't exist in VFS will
462-
create a new VFS file instead.
460+
`rmdir`, `symlink`, `copyFile`, `truncate`, `link`, `chmod`, `chown`,
461+
`utimes`, `lutimes`, `mkdtemp`, `rm`, `cp`): Always operate on VFS. New
462+
files are created in VFS, and attempting to modify a real file that doesn't
463+
exist in VFS will create a new VFS file instead.
463464
* **File descriptors**: Once a file is opened, all subsequent operations on that
464465
descriptor stay within the same layer (VFS or real FS) where it was opened.
465466

@@ -469,16 +470,6 @@ The `VirtualFileSystem` class supports all common synchronous `node:fs` methods
469470
for reading, writing, and managing files and directories. Methods mirror the
470471
`node:fs` module API.
471472

472-
The following `node:fs` sync methods have **no** VFS equivalent:
473-
474-
* `chmodSync()` / `fchmodSync()` - VFS does not support permission changes
475-
* `chownSync()` / `fchownSync()` - VFS does not support ownership changes
476-
* `truncateSync()` / `ftruncateSync()` - Use `writeFileSync()` instead
477-
* `utimesSync()` / `futimesSync()` / `lutimesSync()` - VFS does not support
478-
changing timestamps
479-
* `linkSync()` - VFS does not support hard links (use `symlinkSync()`)
480-
* `fdatasyncSync()` / `fsyncSync()` - Not applicable to in-memory storage
481-
482473
#### Promise Methods
483474

484475
All synchronous methods have promise-based equivalents available through
@@ -535,6 +526,17 @@ added: REPLACEME
535526

536527
Returns `true` if the provider supports symbolic links.
537528

529+
### `provider.supportsWatch`
530+
531+
<!-- YAML
532+
added: REPLACEME
533+
-->
534+
535+
* {boolean}
536+
537+
Returns `true` if the provider supports file watching via `watch()`,
538+
`watchFile()`, and `unwatchFile()`.
539+
538540
### Creating Custom Providers
539541

540542
To create a custom provider, extend `VirtualProvider` and implement the
@@ -718,6 +720,8 @@ method is intercepted in its synchronous, callback, and/or promise form.
718720
* `realpathSync()`, `realpath()`, `fs.promises.realpath()`
719721
* `accessSync()`, `access()`, `fs.promises.access()`
720722
* `readlinkSync()`, `readlink()`, `fs.promises.readlink()`
723+
* `statfsSync()`, `statfs()`, `fs.promises.statfs()`
724+
* `opendirSync()`, `opendir()`
721725

722726
**Path-based write operations** (synchronous, callback, and promise):
723727

@@ -730,14 +734,32 @@ method is intercepted in its synchronous, callback, and/or promise form.
730734
* `renameSync()`, `rename()`, `fs.promises.rename()`
731735
* `copyFileSync()`, `copyFile()`, `fs.promises.copyFile()`
732736
* `symlinkSync()`, `symlink()`, `fs.promises.symlink()`
737+
* `truncateSync()`, `truncate()`, `fs.promises.truncate()`
738+
* `linkSync()`, `link()`, `fs.promises.link()`
739+
* `chmodSync()`, `chmod()`, `fs.promises.chmod()`
740+
* `chownSync()`, `chown()`, `fs.promises.chown()`
741+
* `lchownSync()`, `lchown()`, `fs.promises.lchown()`
742+
* `utimesSync()`, `utimes()`, `fs.promises.utimes()`
743+
* `lutimesSync()`, `lutimes()`, `fs.promises.lutimes()`
744+
* `mkdtempSync()`, `mkdtemp()`, `fs.promises.mkdtemp()`
745+
* `lchmod()`, `fs.promises.lchmod()`
746+
* `cpSync()`, `cp()`, `fs.promises.cp()`
733747

734748
**File descriptor operations** (synchronous and callback):
735749

736750
* `openSync()`, `open()`
737751
* `closeSync()`, `close()`
738752
* `readSync()`, `read()`
739753
* `writeSync()`, `write()`
754+
* `readvSync()`, `readv()`
755+
* `writevSync()`, `writev()`
740756
* `fstatSync()`, `fstat()`
757+
* `ftruncateSync()`, `ftruncate()`
758+
* `fchmodSync()`, `fchmod()` (no-op for VFS file descriptors)
759+
* `fchownSync()`, `fchown()` (no-op for VFS file descriptors)
760+
* `futimesSync()`, `futimes()` (no-op for VFS file descriptors)
761+
* `fdatasyncSync()`, `fdatasync()` (no-op for VFS file descriptors)
762+
* `fsyncSync()`, `fsync()` (no-op for VFS file descriptors)
741763

742764
Virtual file descriptors use values starting at 10000 to avoid conflicts with
743765
real file descriptors.
@@ -758,18 +780,7 @@ real file descriptors.
758780
The following `node:fs` methods are **not** intercepted and always operate on
759781
the real file system:
760782

761-
* `chmod()`, `chmodSync()`, `fchmod()`, `fchmodSync()`
762-
* `chown()`, `chownSync()`, `fchown()`, `fchownSync()`
763-
* `truncate()`, `truncateSync()`, `ftruncate()`, `ftruncateSync()`
764-
* `utimes()`, `utimesSync()`, `futimes()`, `futimesSync()`, `lutimes()`,
765-
`lutimesSync()`
766-
* `link()`, `linkSync()`
767-
* `fdatasync()`, `fdatasyncSync()`, `fsync()`, `fsyncSync()`
768-
* `mkdtemp()`, `mkdtempSync()`
769-
* `cp()`, `cpSync()`
770783
* `glob()`, `globSync()`
771-
* `statfs()`, `statfsSync()`
772-
* `opendir()`, `opendirSync()`
773784

774785
## Integration with module loading
775786

lib/fs.js

Lines changed: 29 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -569,18 +569,7 @@ function close(fd, callback = defaultCloseCallback) {
569569
callback = makeCallback(callback);
570570

571571
const h = vfsState.handlers;
572-
if (h !== null) {
573-
try {
574-
const result = h.closeSync(fd);
575-
if (result !== undefined) {
576-
process.nextTick(callback, null);
577-
return;
578-
}
579-
} catch (err) {
580-
process.nextTick(callback, err);
581-
return;
582-
}
583-
}
572+
if (h !== null && vfsVoid(h.close(fd), callback)) return;
584573

585574
const req = new FSReqCallback();
586575
req.oncomplete = callback;
@@ -763,14 +752,10 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) {
763752

764753
const h = vfsState.handlers;
765754
if (h !== null) {
766-
try {
767-
const result = h.readSync(fd, buffer, offset, length, position);
768-
if (result !== undefined) {
769-
process.nextTick(callback, null, result, buffer);
770-
return;
771-
}
772-
} catch (err) {
773-
process.nextTick(callback, err);
755+
const promise = h.read(fd, buffer, offset, length, position);
756+
if (promise !== undefined) {
757+
PromisePrototypeThen(promise,
758+
(bytesRead) => callback(null, bytesRead, buffer), callback);
774759
return;
775760
}
776761
}
@@ -887,14 +872,10 @@ function readv(fd, buffers, position, callback) {
887872

888873
const h = vfsState.handlers;
889874
if (h !== null) {
890-
try {
891-
const result = h.readvSync(fd, buffers, position);
892-
if (result !== undefined) {
893-
process.nextTick(callback, null, result, buffers);
894-
return;
895-
}
896-
} catch (err) {
897-
process.nextTick(callback, err);
875+
const promise = h.readv(fd, buffers, position);
876+
if (promise !== undefined) {
877+
PromisePrototypeThen(promise,
878+
(read) => callback(null, read, buffers), callback);
898879
return;
899880
}
900881
}
@@ -979,14 +960,10 @@ function write(fd, buffer, offsetOrOptions, length, position, callback) {
979960

980961
const h = vfsState.handlers;
981962
if (h !== null) {
982-
try {
983-
const result = h.writeSync(fd, buffer, offset, length, position);
984-
if (result !== undefined) {
985-
process.nextTick(callback, null, result, buffer);
986-
return;
987-
}
988-
} catch (err) {
989-
process.nextTick(callback, err);
963+
const promise = h.write(fd, buffer, offset, length, position);
964+
if (promise !== undefined) {
965+
PromisePrototypeThen(promise,
966+
(bytesWritten) => callback(null, bytesWritten, buffer), callback);
990967
return;
991968
}
992969
}
@@ -1016,15 +993,11 @@ function write(fd, buffer, offsetOrOptions, length, position, callback) {
1016993

1017994
const h = vfsState.handlers;
1018995
if (h !== null) {
1019-
try {
1020-
const bufAsBuffer = Buffer.from(str, length);
1021-
const result = h.writeSync(fd, bufAsBuffer, 0, bufAsBuffer.length, offset);
1022-
if (result !== undefined) {
1023-
process.nextTick(callback, null, result, buffer);
1024-
return;
1025-
}
1026-
} catch (err) {
1027-
process.nextTick(callback, err);
996+
const bufAsBuffer = Buffer.from(str, length);
997+
const promise = h.write(fd, bufAsBuffer, 0, bufAsBuffer.length, offset);
998+
if (promise !== undefined) {
999+
PromisePrototypeThen(promise,
1000+
(bytesWritten) => callback(null, bytesWritten, buffer), callback);
10281001
return;
10291002
}
10301003
}
@@ -1137,14 +1110,10 @@ function writev(fd, buffers, position, callback) {
11371110

11381111
const h = vfsState.handlers;
11391112
if (h !== null) {
1140-
try {
1141-
const result = h.writevSync(fd, buffers, position);
1142-
if (result !== undefined) {
1143-
process.nextTick(callback, null, result, buffers);
1144-
return;
1145-
}
1146-
} catch (err) {
1147-
process.nextTick(callback, err);
1113+
const promise = h.writev(fd, buffers, position);
1114+
if (promise !== undefined) {
1115+
PromisePrototypeThen(promise,
1116+
(written) => callback(null, written, buffers), callback);
11481117
return;
11491118
}
11501119
}
@@ -1307,13 +1276,7 @@ function ftruncate(fd, len = 0, callback) {
13071276
callback = makeCallback(callback);
13081277

13091278
const h = vfsState.handlers;
1310-
if (h !== null) {
1311-
const result = h.ftruncateSync(fd, len);
1312-
if (result !== undefined) {
1313-
process.nextTick(callback, null);
1314-
return;
1315-
}
1316-
}
1279+
if (h !== null && vfsVoid(h.ftruncate(fd, len), callback)) return;
13171280

13181281
const req = new FSReqCallback();
13191282
req.oncomplete = callback;
@@ -1481,13 +1444,7 @@ function fdatasync(fd, callback) {
14811444
callback = makeCallback(callback);
14821445

14831446
const h = vfsState.handlers;
1484-
if (h !== null) {
1485-
const result = h.fdatasyncSync(fd);
1486-
if (result !== undefined) {
1487-
process.nextTick(callback, null);
1488-
return;
1489-
}
1490-
}
1447+
if (h !== null && vfsVoid(h.fdatasync(fd), callback)) return;
14911448

14921449
const req = new FSReqCallback();
14931450
req.oncomplete = callback;
@@ -1530,13 +1487,7 @@ function fsync(fd, callback) {
15301487
callback = makeCallback(callback);
15311488

15321489
const h = vfsState.handlers;
1533-
if (h !== null) {
1534-
const result = h.fsyncSync(fd);
1535-
if (result !== undefined) {
1536-
process.nextTick(callback, null);
1537-
return;
1538-
}
1539-
}
1490+
if (h !== null && vfsVoid(h.fsync(fd), callback)) return;
15401491

15411492
const req = new FSReqCallback();
15421493
req.oncomplete = callback;
@@ -1905,18 +1856,7 @@ function fstat(fd, options = { bigint: false }, callback) {
19051856
}
19061857

19071858
const h = vfsState.handlers;
1908-
if (h !== null) {
1909-
try {
1910-
const result = h.fstatSync(fd);
1911-
if (result !== undefined) {
1912-
process.nextTick(callback, null, result);
1913-
return;
1914-
}
1915-
} catch (err) {
1916-
process.nextTick(callback, err);
1917-
return;
1918-
}
1919-
}
1859+
if (h !== null && vfsResult(h.fstat(fd, options), callback)) return;
19201860

19211861
callback = makeStatsCallback(callback);
19221862

@@ -2347,13 +2287,7 @@ function fchmod(fd, mode, callback) {
23472287
callback = makeCallback(callback);
23482288

23492289
const h = vfsState.handlers;
2350-
if (h !== null) {
2351-
const result = h.fchmodSync(fd);
2352-
if (result !== undefined) {
2353-
process.nextTick(callback, null);
2354-
return;
2355-
}
2356-
}
2290+
if (h !== null && vfsVoid(h.fchmod(fd), callback)) return;
23572291

23582292
if (permission.isEnabled()) {
23592293
callback(new ERR_ACCESS_DENIED('fchmod API is disabled when Permission Model is enabled.'));
@@ -2530,13 +2464,7 @@ function fchown(fd, uid, gid, callback) {
25302464
callback = makeCallback(callback);
25312465

25322466
const h = vfsState.handlers;
2533-
if (h !== null) {
2534-
const result = h.fchownSync(fd);
2535-
if (result !== undefined) {
2536-
process.nextTick(callback, null);
2537-
return;
2538-
}
2539-
}
2467+
if (h !== null && vfsVoid(h.fchown(fd), callback)) return;
25402468

25412469
if (permission.isEnabled()) {
25422470
callback(new ERR_ACCESS_DENIED('fchown API is disabled when Permission Model is enabled.'));
@@ -2682,13 +2610,7 @@ function futimes(fd, atime, mtime, callback) {
26822610
callback = makeCallback(callback);
26832611

26842612
const h = vfsState.handlers;
2685-
if (h !== null) {
2686-
const result = h.futimesSync(fd);
2687-
if (result !== undefined) {
2688-
process.nextTick(callback, null);
2689-
return;
2690-
}
2691-
}
2613+
if (h !== null && vfsVoid(h.futimes(fd), callback)) return;
26922614

26932615
if (permission.isEnabled()) {
26942616
callback(new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'));

0 commit comments

Comments
 (0)