Module std::fs

Files as an effect: code reaches the file system only through an Fs capability in its row, and only beneath the directory descriptors it is handed: every path is resolved beneath a dir: &Fd, and an absolute one or a .. that would leave it fails EXDEV (18). The root hands a program its first directory, cwd, and the program hands it on. Paths are bytes (&[u8]). Errors are the kernel's errno values (Err(Int)).

A descriptor is a bare linear Fd: Fs::close consumes it and answers the error closing reports (a write the disk refused); there is no File whose drop would have to lose it. Install Blocking (one system call per op) or Evented (ops on the root's reactor); std::net::Net's executors serve Fs too, and a test uses std::memfs::MemFs. A bare with Fs is Fs portable migrates cross, the bound under which a function runs under each.

Source: std/fs.weir; committed interface: std/fs.iface. Index

const READ

pub const READ: Int = 1;

Fs::open flag: open for reading.

const WRITE

pub const WRITE: Int = 2;

Fs::open flag: open for writing.

const CREATE

pub const CREATE: Int = 4;

Fs::open flag: create the file if missing (needs WRITE).

const TRUNC

pub const TRUNC: Int = 8;

Fs::open flag: truncate the file to empty (needs WRITE).

const EXCL

pub const EXCL: Int = 16;

Fs::open flag: with CREATE, the file must not exist yet.

const DIR

pub const DIR: Int = 32;

Fs::open flag: open a directory (with nothing else but READ); flags not combined as these docs say are EINVAL (22).

effect Fs

pub effect Fs default portable migrates cross {
    once fn open(dir: &Fd, path: &[u8], flags: Int) -> Result<Fd, Int>;
    once fn read_at(f: &Fd, b: &mut [u8], off: usize) -> Result<usize, Int>;
    once fn write_at(f: &Fd, b: &[u8], off: usize) -> Result<usize, Int>;
    once fn sync(f: &Fd) -> Result<Unit, Int>;
    once fn size(f: &Fd) -> Result<usize, Int>;
    once fn mkdir(dir: &Fd, path: &[u8]) -> Result<Unit, Int>;
    once fn remove(dir: &Fd, path: &[u8]) -> Result<Unit, Int>;
    once fn rename(dir: &Fd, from: &[u8], to: &[u8]) -> Result<Unit, Int>;
    once fn list(dir: &Fd, b: &mut [u8], at: usize) -> Result<usize, Int>;
    linear fn close(fd: Fd) -> Result<Unit, Int>;
}

The file operations, each a once op answering Err(errno) on failure; an executor may park the task while the op runs. Every path is resolved beneath its dir.

op open

once fn open(dir: &Fd, path: &[u8], flags: Int) -> Result<Fd, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Opens or creates path beneath dir as flags say: a file, or a directory with DIR.

op read_at

once fn read_at(f: &Fd, b: &mut [u8], off: usize) -> Result<usize, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Reads into b from offset off: the count read, 0 at and past the end.

op write_at

once fn write_at(f: &Fd, b: &[u8], off: usize) -> Result<usize, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Writes b at offset off: the count written, which may be short.

op sync

once fn sync(f: &Fd) -> Result<Unit, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Brings what was written to f to the device (fdatasync): once it answers Ok, a crash keeps every write before it. Of a directory, the names made, removed or renamed in it, which a crash may otherwise take back. After a failed sync, what it was to make durable may still be read but is not on the disk, and a later sync that answers Ok does not put it there (Linux since 4.13): treat the failure as fatal, not as a call to retry.

op size

once fn size(f: &Fd) -> Result<usize, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

A file's length in bytes.

op mkdir

once fn mkdir(dir: &Fd, path: &[u8]) -> Result<Unit, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Makes the directory path beneath dir.

op remove

once fn remove(dir: &Fd, path: &[u8]) -> Result<Unit, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Removes the file or empty directory path beneath dir.

op rename

once fn rename(dir: &Fd, from: &[u8], to: &[u8]) -> Result<Unit, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

Renames from to to, both beneath dir, replacing a file at to.

op list

once fn list(dir: &Fd, b: &mut [u8], at: usize) -> Result<usize, Int>;

Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.

The names in dir but . and .., sorted bytewise, from the atth, into b: as many whole names as fit, each followed by a NUL byte; the count, 0 past the last.

op close

linear fn close(fd: Fd) -> Result<Unit, Int>;

Control kind linear: the clause takes the continuation (cont k) and resumes it exactly once, so the perform is a suspension.

Closes fd, first bringing what was written to the device, and answers the first error. A linear op, never cancelled, so a descriptor is not leaked by a cancel.

fn cwd

pub fn cwd() -> Result<Fd, Int> with Sys

The process's working directory, opened as a directory: the root's first directory, from which a program reaches every file it may. Needs the root's Sys, so main calls it.

Committed interface (std/fs.iface):

bound Sys: Sys any
scheduling point: Sys not quiet

fn preopen

pub fn preopen(name: &[u8]) -> Result<Fd, Int> with Sys

The directory the root was handed under name (weir run --dir <path>:<name>, a native binary's --dir), opened anew. A name it was not handed is ENOENT (2), as is every name in the simulated world.

Committed interface (std/fs.iface):

bound Sys: Sys any
scheduling point: Sys not quiet

handler Blocking

pub handler Blocking: shared strict cross for Fs with Sys

The blocking executor: each op is one system call on the calling thread. Install it as with Blocking { … } where the root's Sys is in scope (main).

Handles Fs, with clauses:

handler Evented

pub handler Evented(s: Slots<Unit, once>): shared strict cross for Fs with Reactor

The completion executor: each op is submitted to the root's reactor and the body parks until it completes, so a file op never blocks a worker's loop. Install it as with Evented(slots()) { … } (needs Reactor); slots() is its table. The with body is its one strand: there are no tasks, so at most one op is in flight.

Handles Fs, with clauses:

fn read_all

pub fn read_all(dir: &Fd, path: &[u8]) -> Result<Buf<u8>, Int> with Fs, Alloc portable

The whole of the file path beneath dir (with Alloc for the buffer). The read's error comes first, then the close's.

Committed interface (std/fs.iface):

bound Fs: std::fs::Fs portable cross
bound Alloc: Alloc any portable
may diverge
aborts: Fs.open, Fs.read_at, Fs.size, Alloc.alloc
scheduling point: always
holds dir across a migration: Fs.open holds its arguments across a migration
holds path across a migration: Fs.open holds its arguments across a migration
cleanup on an abort suspends on Fs

fn write_all

pub fn write_all(dir: &Fd, path: &[u8], data: &[u8]) -> Result<Unit, Int> with Fs

Makes path beneath dir hold exactly data, creating it if missing. The write's error comes first, then the close's, which reports what the device refused after accepting the write; a write that makes no progress is EIO (5).

Committed interface (std/fs.iface):

bound Fs: std::fs::Fs portable cross
may diverge
aborts: Fs.open, Fs.write_at
scheduling point: always
holds dir across a migration: Fs.open holds its arguments across a migration
holds path across a migration: Fs.open holds its arguments across a migration
holds data across a migration: Fs resumes elsewhere | Fs.write_at holds its arguments across a migration
cleanup on an abort suspends on Fs

fn names

pub fn names(dir: &Fd) -> Result<Buf<Buf<u8>>, Int> with Fs, Alloc portable

The names in the directory dir but . and .., sorted bytewise, each its own Buf.

Committed interface (std/fs.iface):

bound Fs: std::fs::Fs portable cross
bound Alloc: Alloc any portable
may diverge
aborts: Fs.list, Alloc.alloc
scheduling point: always
holds dir across a migration: Fs resumes elsewhere | Fs.list holds its arguments across a migration