Skip to main content

nodejs/stdlib/
fs_promises.rs

1//! Node `fs/promises` (also `require('fs').promises`) — Promise-returning file
2//! operations.
3//!
4//! Every method delegates to the SAME synchronous I/O the `*Sync` `fs` module
5//! performs (`readFile`→`readFileSync`, `chmod`→`chmodSync`, …), then wraps the
6//! outcome in a settled Promise: fulfilled with the value on success, rejected
7//! with an `Error` object on failure. Delegating to `fs::call` keeps the encoding
8//! handling, `Stats`/`Dirent`/`Dir` shapes, and error strings identical to the
9//! rest of `fs`.
10//!
11//! The work is performed synchronously (node-js has no thread-pooled `fs` at this
12//! layer); the Promise is already settled when returned, so `await`/`.then`
13//! observe the result on the next microtask tick — the observable contract
14//! callers rely on.
15
16use crate::host::with_host;
17use fusevm::Value;
18
19pub const METHODS: &[&str] = &[
20    "readFile",
21    "writeFile",
22    "appendFile",
23    "readdir",
24    "mkdir",
25    "rmdir",
26    "rm",
27    "unlink",
28    "stat",
29    "lstat",
30    "statfs",
31    "access",
32    "rename",
33    "copyFile",
34    "cp",
35    "chmod",
36    "chown",
37    "lchown",
38    "link",
39    "symlink",
40    "readlink",
41    "realpath",
42    "truncate",
43    "utimes",
44    "lutimes",
45    "mkdtemp",
46    "opendir",
47    "glob",
48];
49
50pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
51    // Every method delegates to the synchronous `fs` sibling of the same name,
52    // then wraps the outcome in a settled Promise.
53    let sync_name = match method {
54        "readFile" => "readFileSync",
55        "writeFile" => "writeFileSync",
56        "appendFile" => "appendFileSync",
57        "readdir" => "readdirSync",
58        "mkdir" => "mkdirSync",
59        "rmdir" => "rmdirSync",
60        "rm" => "rmSync",
61        "unlink" => "unlinkSync",
62        "stat" => "statSync",
63        "lstat" => "lstatSync",
64        "statfs" => "statfsSync",
65        "access" => "accessSync",
66        "rename" => "renameSync",
67        "copyFile" => "copyFileSync",
68        "cp" => "cpSync",
69        "chmod" => "chmodSync",
70        "chown" => "chownSync",
71        "lchown" => "lchownSync",
72        "link" => "linkSync",
73        "symlink" => "symlinkSync",
74        "readlink" => "readlinkSync",
75        "realpath" => "realpathSync",
76        "truncate" => "truncateSync",
77        "utimes" => "utimesSync",
78        "lutimes" => "lutimesSync",
79        "mkdtemp" => "mkdtempSync",
80        "opendir" => "opendirSync",
81        "glob" => "globSync",
82        _ => return None,
83    };
84    // The method itself always succeeds in *returning a Promise*; success/failure
85    // of the I/O is encoded in that Promise's settled state.
86    Some(Ok(settled(sync(sync_name, args))))
87}
88
89/// Run a synchronous `fs` method by name, returning its `Result` (the `Option`
90/// is always `Some` for the known sync names above).
91fn sync(sync_method: &str, args: &[Value]) -> Result<Value, String> {
92    super::fs::call(sync_method, args).unwrap_or_else(|| Err("Error: EIO: internal error".into()))
93}
94
95/// Wrap a synchronous outcome in an already-settled Promise: fulfilled with the
96/// value, or rejected with an `Error` synthesized from the message. The host
97/// borrow is released before `resolve`/`reject` (which re-enter the host).
98fn settled(result: Result<Value, String>) -> Value {
99    let p = with_host(|h| h.new_promise());
100    let id = with_host(|h| h.promise_id(&p).unwrap_or(0));
101    match result {
102        Ok(v) => crate::host::resolve_promise_val(id, v),
103        Err(e) => {
104            let ev = with_host(|h| crate::builtins::synth_error(h, &e));
105            crate::host::reject_promise_val(id, ev);
106        }
107    }
108    p
109}