Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rooted filesystem helpers

std.fs mutation helpers operate on an owned Root, opened by root(&path) -> Result[Root, IoError]. The handle anchors operations to that directory even if its pathname changes. Opening rejects a final symlink, including a path with trailing slashes. Ancestor components of the initial root path are resolved normally: selecting that root is the caller’s trust decision, not a sandbox or a canonicalization operation.

import std.fs as fs
import std.path as path

def main() -> Result[(), AllocError | IoError]:
    current = path.from_text(".")?
    root = fs.root(&current)?
    drafts = path.from_text("drafts")?
    final = path.from_text("final")?
    root.create_dir(&drafts)?
    root.move(&drafts, &final)?
    reports = root.open_dir(&final)?
    reports.create_dir(&path.from_text("2024")?)?
    mut names = reports.names()?
    while True:
        match names.next()?:
            case Some(name):
                print(name.to_string()?)?
            case Nothing:
                break
    Ok(())
2024

Every child name is a lossless Path containing one nonempty component. Slash, NUL, . and .. are rejected. Child directory/file opens use O_NOFOLLOW; symlinks and symlink cycles cannot escape through these opens. Mounted directories are not a boundary. A directory moved after opening stays accessible through its handle. These rules do not provide isolation from a process with permission to rename entries in the same tree.

Root methodBehavior
open_dir(&name)New independently owned Root; final symlink refused
read_file(&name)Read-only std.io.FileStream; regular files only
create_dir(&name, unix_mode=0o700u32)Exclusive directory creation; mode restricted by umask
create_file(&name, unix_mode=0o600u32)Exclusive regular-file creation; owned read/write FileStream, mode restricted by umask
set_unix_mode(unix_mode)Change ordinary permission bits on this held directory
remove_file(&name)Unlink one non-directory, including a symlink itself
remove_dir(&name)Remove one empty directory; no link following
move(&source, &destination)Same-root atomic renameat2(RENAME_NOREPLACE)
names()Owned lazy Names; next() yields Result[Option[Path], IoError]

Move refuses every existing destination, including a dangling link. It moves a source symlink itself; there is no replacement or cross-root/cross-device copy-and-delete fallback. Removal has ordinary unlink/rmdir permissions and race semantics. Names.close() is fallible and repeatable. EOF and errors close the stream; drop closes it as well. Names are detached, native-order byte values, including hidden names. No metadata observation through an unanchored joined path is exposed by this interface.

Root, names, streams and temporary owners are affine. An owned Root or FileStream can move to a worker; creation and permission operations are handle effects permitted there. Sharing a handle by loan still follows the worker resource rules. Shared Root loans allow pathname operations within a thread, since they do not mutate a cursor. Filesystem state is external mutable state; this does not synchronize concurrent processes. Every syscall retains its native IoError; buffer and owner allocation is fallible. Paths require O(name length) temporary storage. Native primitives make narrow OS requests; traversal, budgets, retries and temporary-resource lifecycle are ordinary checked Plenty code.

Current target: x86_64 Linux GNU. No new dependency or linker requirement is introduced. The matching installed compiler embeds the module and runtime; imports work offline after relocation. See temporary resources and bounded copying, deletion and globbing.

Private creation and permission changes

Use Root.create_file for a named file that must not already exist. It returns Result[FileStream, IoError], opened for reading and writing; close it through FileStream.close() or let its owner drop. Allocation for the path and owner happens before creation. Neither allocation failure nor an existing entry leaves a new file. Every existing entry, including a dangling symlink, causes an error without truncation, replacement or permission changes. A successful creation remains named after close, drop or a later write failure. Use anonymous staging when partial contents must stay unnamed.

create_dir returns Result[(), IoError] and creates only the named directory, not its parents. Existing directories and symlinks fail too. Open the result with open_dir when a held directory handle is needed. Creation and this subsequent open are not atomic; the parent must be trusted against concurrent entry replacement, as for the other rooted helpers.

Both creation methods accept unix_mode: u32. The default modes (0600 for a file, 0700 for a directory) prohibit group/other access from the instant of creation. The mode goes directly to openat/mkdirat, with no create-then-chmod window. Ordinary Unix umask/default-ACL rules apply: requested bits are an upper bound, and the process umask can remove them when no default ACL applies. The library never reads or changes the process-global umask. Directory setgid inheritance and existing filesystem ACL policy are controlled by the OS.

To change permissions later, call Root.set_unix_mode(unix_mode) on a held directory or fs.set_file_unix_mode(&stream, unix_mode) on a held FileStream. Both return Result[(), IoError], perform no input-sized allocation and call fchmod on the descriptor. Umask does not restrict these updates. They neither reopen a pathname nor follow a newly substituted symlink. The operation changes the inode shared by all of its hard links, even after renaming or unlinking. It does not revoke already-open descriptors, change ownership, or promise an ACL-free filesystem. The OS enforces ownership/capability checks and ACL effects.

Modes outside 0o000u32..0o777u32 return IoError.InvalidInput before any filesystem change: file-type bits, sticky and set-ID bits are deliberately unsupported. Modes are not silently masked. OS failures retain their native IoError.System code; closed streams return IoError.Closed. Creation can also return an allocation error through IoError.Data(DataError.Allocation(...)). These APIs have the same 64-bit Linux scope as the rooted helpers; unsupported hosts return IoError.Unsupported. Native code is limited to descriptor creation and fchmod, because these operations require OS syscalls; mode validation and public policy remain Plenty code.