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(¤t)?
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.