denoland/deno
ext/fs
Active contributors: David Sherret, Bartek Iwańczuk, Yoshiya Hinosawa
Purpose
ext/fs is the filesystem extension. It powers the Deno.* filesystem APIs (Deno.open, Deno.readFile, Deno.writeFile, Deno.mkdir, Deno.remove, Deno.stat, Deno.watchFs, …) and is also the substrate that Node's node:fs polyfill ultimately delegates to.
This page is also a worked example of the standard extension shape: a lib.rs that registers ops and JS, a sibling interface.rs defining the FileSystem trait, an ops.rs with the actual ops, and a single numbered JS file (30_fs.js) exposing the API to user code.
Directory layout
ext/fs/
├── Cargo.toml
├── lib.rs # extension!() registration
├── interface.rs # FileSystem trait and types
├── ops.rs # the #[op2] functions
├── std_fs.rs # the std::fs-backed implementation of FileSystem
└── 30_fs.js # JS surface exposed as Deno.*Key abstractions
| Type | File | Role |
|---|---|---|
FileSystem (trait) |
interface.rs |
Abstract filesystem; lets denort, deno_runtime, and tests plug in different impls |
RealFs |
std_fs.rs |
Default impl using std::fs |
FileSystemRc |
interface.rs |
Rc<dyn FileSystem> shared via op state |
FsError |
interface.rs |
Error type that implements JsErrorClass so it surfaces correctly to JS |
op_fs_* |
ops.rs |
Per-API ops: op_fs_open_async, op_fs_read, op_fs_write, op_fs_stat, op_fs_remove, op_fs_watch, … |
The FileSystem trait abstraction matters because:
- The CLI uses
RealFs. denort(thedeno compileruntime) sometimes uses an in-memory FS that reads from the embedded eszip.- Tests can substitute fakes.
How it works
graph LR
User["Deno.readFile(path)"] --> Js["ext/fs/30_fs.js"]
Js --> Op["core.ops.op_fs_read_file_async"]
Op --> Check["PermissionsContainer.check_read(path, 'Deno.readFile')"]
Check --> Trait["state.borrow::<FileSystemRc>()"]
Trait --> Real["RealFs (or denort's in-memory fs)"]
Real --> Disk[(disk / eszip)]Each op:
- Borrows
FileSystemRcfrom op state. - Borrows
PermissionsContainerand runs the appropriatecheck_*for the path. - Calls into the trait method, which the impl performs (delegating to
tokio::fsorstd::fs). - Returns the result, with
FsErrortranslated into a JS-friendly error class.
Async vs sync
Most filesystem ops have both sync and async forms (Deno.readFile and Deno.readFileSync). The async ops do their work on Tokio's blocking thread pool (spawn_blocking) so the runtime's main current-thread executor isn't stalled by disk I/O. Sync ops run inline; user code that calls them on the main thread is intentionally blocking.
A recent perf fix illustrates this: fix(ext/fs): run open_async on the blocking pool so FIFO opens don't stall the runtime — opening a FIFO can block until a writer connects, and doing that on the main runtime thread freezes everything else.
Watch
Deno.watchFs is implemented on top of notify (the cross-platform fs-watch crate). The op returns a resource that JS code iterates with for await.
Permissions
Every fs op checks read or write permission for the involved path. Notable details:
- The descriptor passed to
check_readis the resolved absolute path, not the user's input. Symlink-following and canonicalization happen inside the op. - Some ops (e.g.,
Deno.realPath) need both read on the symlink chain and read on the target. Deno.makeTempFilechecks write permission on the OS temp directory, not on a user-supplied path.
The error returned on denial is a PermissionDeniedError with class NotCapable.
Integration points
- Used by:
ext/node(thenode:fspolyfill calls these ops);cli/file_fetcher.rs(when reading fromfile:URLs);runtime/worker.rs(which stuffs theFileSystemRcinto op state during bootstrap). - Permissions:
runtime/permissions/lib.rs::PermissionsContainer::{check_read, check_write}. - Error class:
FsErrorpropagates throughdeno_error::JsErrorBoxso JS sees a realErrorsubclass with the right name.
Entry points for modification
- New filesystem op — add the function in
ops.rswith#[op2], register inlib.rs'sextension!()ops list, expose from30_fs.js. - New
FileSystemtrait method — add tointerface.rs, implement instd_fs.rs, updatedenort's in-memory impl if relevant. - Permission-check change — update the call in the relevant op. Match the API name string to the user-visible
Deno.*function.
Key source files
| File | Purpose |
|---|---|
ext/fs/lib.rs |
deno_core::extension!() registration |
ext/fs/interface.rs |
FileSystem trait, FsError, types |
ext/fs/ops.rs |
The #[op2] pub fn op_fs_* definitions |
ext/fs/std_fs.rs |
RealFs — the std::fs-backed implementation |
ext/fs/30_fs.js |
JS surface (Deno.open, Deno.readFile, …) |
runtime/worker.rs |
Where FileSystemRc is inserted into op state |
runtime/permissions/lib.rs |
check_read/check_write called from fs ops |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.