Open-Source Wikis

/

Deno

/

Extensions

/

`ext/fs`

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 (the deno compile runtime) 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::&lt;FileSystemRc&gt;()"]
    Trait --> Real["RealFs (or denort's in-memory fs)"]
    Real --> Disk[(disk / eszip)]

Each op:

  1. Borrows FileSystemRc from op state.
  2. Borrows PermissionsContainer and runs the appropriate check_* for the path.
  3. Calls into the trait method, which the impl performs (delegating to tokio::fs or std::fs).
  4. Returns the result, with FsError translated 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_read is 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.makeTempFile checks 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 (the node:fs polyfill calls these ops); cli/file_fetcher.rs (when reading from file: URLs); runtime/worker.rs (which stuffs the FileSystemRc into op state during bootstrap).
  • Permissions: runtime/permissions/lib.rs::PermissionsContainer::{check_read, check_write}.
  • Error class: FsError propagates through deno_error::JsErrorBox so JS sees a real Error subclass with the right name.

Entry points for modification

  • New filesystem op — add the function in ops.rs with #[op2], register in lib.rs's extension!() ops list, expose from 30_fs.js.
  • New FileSystem trait method — add to interface.rs, implement in std_fs.rs, update denort'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.

`ext/fs` – Deno wiki | Factory