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

Stage files and clean temporary directories

Open a std.fs.Root to anchor operations to a directory. Child names are single-component lossless paths: no slash, NUL, . or ... Child opens reject symlinks. The initial root’s ancestors remain the caller’s trust decision.

An anonymous temporary file has no name until publish succeeds. Publication refuses an existing destination. Returning early before publication closes the file and releases its partial contents without deleting a named path.

This example uses a random private directory inside the working directory:

import std.io

import std.fs as fs
import std.path as path
from std.sync import CancellationToken

def main() -> Result[(), AllocError | IoError | std.io.StreamError | fs.FsError]:
    current = path.from_text(".")?
    parent = fs.root(&current)?
    prefix = path.from_text("plenty-example-")?
    mut directory = fs.temporary_dir(&parent, &prefix, 8)?
    original = path.from_text("original.txt")?
    copied = path.from_text("copied.txt")?
    mut staged = fs.temporary_file(directory.root())?
    staged.stream.file.write("hello")?
    staged.publish(&original)?
    staged.close()?

    cancel = CancellationToken()?
    print(fs.copy_file(directory.root(), &original, &copied, 1024, &cancel, preserve=fs.CopyMetadata.PermissionsAndTimes)?)?
    pattern = path.from_text("*.txt")?
    names = fs.glob(directory.root(), &pattern, 10, 10, 1000)?
    print(len(names))?
    directory.root().remove_file(&original)?
    directory.root().remove_file(&copied)?
    directory.cleanup()?
    Ok(())
5
2

These operations require Linux, a filesystem supporting O_TMPFILE, and /proc/self/fd for publication. Unsupported environments return errors rather than using a weaker fallback. Single-file copying uses bounded scratch storage, returns errors for byte limits/cancellation, and leaves no partial destination name. To preserve ordinary permission bits and access/modification times, pass preserve=fs.CopyMetadata.PermissionsAndTimes to copy_file; ownership, ACLs, xattrs and special permission bits are excluded.

Temporary-directory cleanup removes only an empty directory. Its destructor is best-effort; call cleanup() when you need to observe failure. Other actors must not rename entries in the parent while the temporary directory is live. An identity check detects existing replacements but is not atomic with rmdir. Use explicit bounded remove_tree only when intentionally deleting contents; it can leave partial progress on cancellation or errors.

See the reference for root contracts, temporary-resource limits, and copy, deletion and glob budgets.

For a directory tree, use copy_tree with explicit entry, depth, byte and handle budgets from copy_limits(). Its default copies contents only. The destination must be new, symlinks and special files are rejected, and a failure after creation leaves partial output for the caller to inspect or remove. The recursive-copy example and recovery contract show the complete call and explain TreeCopyError.Setup versus partial-output Partial/Entry errors.

For cooperating processes that must not operate on a resource simultaneously, use std.fs.lock. Keep the returned owner alive for the protected work. acquire_now reports contention immediately; acquire_before takes a total deadline and a cancellation token. Use a stable lockfile in a trusted directory and leave that file in place after releasing the lock: removing it while another process owns it can split coordination across different inodes. Locks close automatically, and normal subprocesses do not inherit them across exec.

Create a private named file

When a file should be visible immediately and must not already exist, use Root.create_file. Its default 0600 mode prevents group and other access from creation; create_dir defaults to 0700. Both are restricted by the process umask. An existing file, directory or symlink causes an error.

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

def main() -> Result[(), AllocError | IoError | std.io.StreamError]:
    parent = fs.root(&path.from_text(".")?)?
    name = path.from_text("private-state")?
    parent.create_dir(&name)?
    directory = parent.open_dir(&name)?
    token = path.from_text("token")?
    mut file = directory.create_file(&token)?
    file.file.write("secret")?
    fs.set_file_unix_mode(&file, 0o400u32)?
    file.close()?
    directory.remove_file(&token)?
    parent.remove_dir(&name)?
    Ok(())

set_file_unix_mode changes the held file, so a replacement at its old path cannot redirect the update. Root.set_unix_mode does the same for a directory. Setting a mode does not close the handle or revoke existing access. Unlike staging, a named file remains after a write failure; use the staging pattern when readers must see only complete output. See the permission contracts for custom modes, ACLs, umask and errors.