pub struct Dir { /* private fields */ }Expand description
A directory file descriptor opened O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC.
All entry-level operations are relative to this fd, preventing TOCTOU races that path-based lookups are vulnerable to.
The fd is held behind an Arc so per-entry operations can move an owned
reference into their spawn_blocking closure. spawn_blocking tasks are
not cancellable: if the surrounding future is dropped (timeout, fail_early
abort, Ctrl-C) the closure keeps running detached. Cloning the Arc (a
refcount bump, no syscall) keeps the open file description alive for the
closure’s full duration even if the originating Dir is dropped mid-flight,
preserving the openat TOCTOU guarantee. Later fd-relative methods
(open_file_read, create_file, make_dir, read_entries, …) must follow
this same clone-Arc-into-closure shape.
Implementations§
Source§impl Dir
impl Dir
Sourcepub fn side(&self) -> Side
pub fn side(&self) -> Side
Which filesystem side this directory lives on (for congestion gating).
Sourcepub async fn open_root_dir(
path: &Path,
dereference: bool,
side: Side,
) -> Result<Dir>
pub async fn open_root_dir( path: &Path, dereference: bool, side: Side, ) -> Result<Dir>
Open path as a directory fd.
The final component is always opened with O_NOFOLLOW. If dereference
is false and the final component is a symlink, the call fails with
ELOOP. If dereference is true and the final component is a symlink,
the call is retried without O_NOFOLLOW so the symlink is followed.
The parent prefix is resolved normally (it is trusted) — unless strict
operand resolution is armed (--require-toctou-safe), in which case the
whole path is resolved RESOLVE_NO_SYMLINKS and a symlink in ANY
component fails closed with ELOOP.
Sourcepub async fn open_parent_dir(path: &Path, side: Side) -> Result<TrustedDir>
pub async fn open_parent_dir(path: &Path, side: Side) -> Result<TrustedDir>
Open a TRUSTED command-line parent-prefix directory, resolving symlinks normally (the final component IS followed if it is a symlink).
The trusted-boundary model (docs/tocttou.md, “Trusted boundary”) trusts the directory named on
the command line up to and including itself; only entries strictly BELOW
it are hardened with O_NOFOLLOW. The parent prefix that CONTAINS the
operand is therefore resolved like a normal path open — a symlinked parent
(e.g. rcp file symlink_to_dir/out, where symlink_to_dir is a symlink to
a real directory) must be followed into the real directory, not rejected
with ELOOP/ENOTDIR.
This differs from Self::open_root_dir, which O_NOFOLLOWs the final
component (the named operand itself) and only follows it when
dereference is set. Use open_parent_dir for the operand’s CONTAINER
directory; use open_root_dir for the operand entry. Every descendant
openat during the walk still uses O_NOFOLLOW, so the hardening below
the named root is unaffected.
Returns a TrustedDir: this is the ONLY constructor of that type, so a
symlink-following open can be obtained nowhere else. Crossing into the
hardened tree below the named root is the explicit TrustedDir::into_tree
step.
Under strict operand resolution (--require-toctou-safe) the prefix must
already be symlink-free: it is resolved RESOLVE_NO_SYMLINKS, and a
symlink in any component fails closed with ELOOP instead of being
followed. Pass fully-resolved operands (realpath output) in that mode.
Sourcepub async fn open_dir(&self, name: &OsStr) -> Result<Dir>
pub async fn open_dir(&self, name: &OsStr) -> Result<Dir>
Open a child directory entry by name, refusing to follow symlinks.
Fails with ELOOP if name refers to a symlink, or ENOTDIR if it
refers to a non-directory entry. The returned Dir carries the same
congestion side as self.
Sourcepub async fn open_file_read(&self, name: &OsStr) -> Result<(File, FileMeta)>
pub async fn open_file_read(&self, name: &OsStr) -> Result<(File, FileMeta)>
Open a child regular file for reading, refusing to follow symlinks and never blocking on a FIFO. Returns the open file plus its metadata snapshot.
O_NONBLOCK is included so that if an attacker races the directory entry to
a FIFO between getdents and this open, the open returns immediately
(O_RDONLY|O_NONBLOCK on a FIFO never blocks on Linux) rather than blocking
forever waiting for a writer. O_NOFOLLOW prevents symlink following but
does not catch FIFOs (they are not symlinks); the subsequent fstat +
S_ISREG check rejects any non-regular file (FIFO, device, directory) with
EINVAL. O_NONBLOCK persists on the returned File, which is harmless for
regular-file I/O on a local fs.
Fails with EINVAL if name is not a single path component, ELOOP if
name is a symlink, or EINVAL (after open, via the fstat+S_ISREG
check) if the entry is any non-regular type such as a FIFO, device, or
directory.
This is the canonical regular-file payload+metadata read: the returned FileMeta (not the
classify Handle’s metadata) is what callers must apply/send, so bytes and metadata come
from the same fd (read-side fidelity, see docs/tocttou.md).
Sourcepub async fn meta(&self) -> Result<FileMeta>
pub async fn meta(&self) -> Result<FileMeta>
fstat this directory’s own held fd, returning its metadata snapshot.
Lets a caller apply/send a directory’s metadata from the SAME fd whose read_entries
produced its contents (read-side fidelity, see docs/tocttou.md), rather than from a
separately-opened classify Handle that a concurrent swap could desync from the
enumerated contents. Gated as Stat.
Sourcepub async fn child(&self, name: &OsStr) -> Result<Handle>
pub async fn child(&self, name: &OsStr) -> Result<Handle>
Open a child entry by name, classifying it without following symlinks.
Uses O_PATH|O_NOFOLLOW, which yields a valid fd even for symlinks. The
stat is then obtained via fstatat with AT_EMPTY_PATH on the resulting
fd so the classification is always consistent with the opened entry.
Sourcepub async fn recheck(&self, name: &OsStr, expected: &Handle) -> Result<Handle>
pub async fn recheck(&self, name: &OsStr, expected: &Handle) -> Result<Handle>
Re-open name and confirm it still refers to the same inode as expected
(same dev + ino). Returns the fresh Handle on match.
On mismatch — the directory entry was swapped to a different inode between
when expected was obtained and this call — returns ESTALE. Callers fail
closed: they must not proceed with an operation that assumed a specific identity
for the entry.
§Soundness
expected’s O_PATH fd pins the old inode alive for the duration of the
call: as long as any fd referencing an inode is open, the kernel cannot
recycle that inode number. A matching (dev, ino) therefore genuinely
proves the two fds refer to the same inode — there is no window in which
the number could have been reused.
Sourcepub async fn make_dir(&self, name: &OsStr, mode: u32) -> Result<Dir>
pub async fn make_dir(&self, name: &OsStr, mode: u32) -> Result<Dir>
Create a child directory and return an open Dir handle to it (same side as self).
Fails with EINVAL if name is not a single path component, or EEXIST if
a directory (or any other entry) at name already exists.
This is a two-step operation: mkdirat (gated as MkDir) to create the
directory, followed by open_dir (gated as Stat) to open and return it.
Sourcepub async fn read_entries(&self) -> Result<Vec<(OsString, Option<EntryKind>)>>
pub async fn read_entries(&self) -> Result<Vec<(OsString, Option<EntryKind>)>>
Enumerate the directory’s entries (excluding . and ..).
Returns each entry’s name and its getdents d_type as a best-effort
EntryKind hint (None when the filesystem reports DT_UNKNOWN). The
hint is advisory only — callers MUST confirm type via child/fstat
before acting (TOCTOU safety).
This method acquires only the static ops rate gate (not the congestion
probe). Directory enumeration is deliberately not probed because buffered
getdents produces bimodal latency (cache hit vs. real kernel call) that
would pollute the congestion controller’s baseline — see
walk::next_entry_probed for the full rationale.
Sourcepub async fn unlink_at(&self, name: &OsStr) -> Result<()>
pub async fn unlink_at(&self, name: &OsStr) -> Result<()>
Remove a child non-directory entry by name, gated on this directory’s own congestion side.
For a symlink, this unlinks the link itself — never its target.
Fails with EINVAL if name is not a single path component, or EISDIR
if name refers to a directory.
Sourcepub async fn rmdir_at(&self, name: &OsStr) -> Result<()>
pub async fn rmdir_at(&self, name: &OsStr) -> Result<()>
Remove a child empty directory by name, gated on this directory’s own congestion side.
Fails with EINVAL if name is not a single path component, ENOTEMPTY
if the directory is not empty, or ENOTDIR if name is not a directory.
Sourcepub async fn symlink_at(&self, name: &OsStr, target: &Path) -> Result<Handle>
pub async fn symlink_at(&self, name: &OsStr, target: &Path) -> Result<Handle>
Create a symlink name → target in this directory, returning a
fd-pinned Handle to the just-created link.
The returned handle has kind() == EntryKind::Symlink and can be used to
apply metadata to the link race-free. target is the link contents — it
is an arbitrary path and is not restricted to a single component.
Fails with EINVAL if name is not a single path component, or EEXIST
if an entry at name already exists.
Sourcepub async fn read_link_at(&self, name: &OsStr) -> Result<PathBuf>
pub async fn read_link_at(&self, name: &OsStr) -> Result<PathBuf>
Read the target of a child symlink.
Fails with EINVAL if name is not a single path component, or EINVAL
(from readlinkat) if name is not a symlink.
Sourcepub async fn hard_link_at(
&self,
name: &OsStr,
dst: &Dir,
dst_name: &OsStr,
) -> Result<()>
pub async fn hard_link_at( &self, name: &OsStr, dst: &Dir, dst_name: &OsStr, ) -> Result<()>
Create a hard link at dst/dst_name pointing to this directory’s name.
Uses AtFlags::empty() (flags=0, no AT_SYMLINK_FOLLOW), so if name is a
symlink, the link target is the symlink inode itself — the target file
gains no new hard link.
Fails with EINVAL if either name or dst_name is not a single path
component.
Sourcepub async fn hard_link_handle_at(
&self,
src_handle: &Handle,
dst_name: &OsStr,
) -> Result<()>
pub async fn hard_link_handle_at( &self, src_handle: &Handle, dst_name: &OsStr, ) -> Result<()>
Create a hard link at self/dst_name pointing to the EXACT inode that
src_handle pins — never re-resolving the source by name.
self is the DESTINATION directory. The source is identified solely by
src_handle’s O_PATH file descriptor: the link is made via
linkat(AT_FDCWD, "/proc/self/fd/N", dst_fd, dst_name, AT_SYMLINK_FOLLOW),
where N is the handle’s fd. AT_SYMLINK_FOLLOW makes linkat follow the
/proc magic symlink to the handle’s pinned inode, so the new hard link
targets that exact inode regardless of any concurrent rename / symlink swap
of the original directory entry.
§Why /proc and not the source-name linkat or AT_EMPTY_PATH
Dir::hard_link_at re-resolves the source by name, which is a TOCTOU
window: an attacker who controls the source tree can replace name with a
different inode (symlink, FIFO, another file) between classification and the
linkat, so the link would target the replacement. Linking the pinned fd
closes that window. linkat(fd, "", .., AT_EMPTY_PATH) would also be
inode-exact but requires CAP_DAC_READ_SEARCH; the /proc/self/fd form does
not, mirroring chmod_via_proc_fd.
§Behavior
- Inode-exact happy path: a stable regular-file handle links exactly as the by-name path did (same inode, same content).
- Fail-closed under attack: if the pinned inode’s last directory entry was
removed (link count 0, e.g. the attacker renamed
nameaway), the kernel refuses to resurrect it andlinkatfails withENOENT. It never links a swapped-in replacement. - Directories:
linkatrefuses to hard-link a directory (EPERM), exactly as the by-name path did. Callers must only pass a regular-file handle.
§Errors
EINVAL if dst_name is not a single path component; ENOENT if the pinned
inode has no remaining links (fail-closed); EEXIST if an entry at
dst_name already exists; EPERM if the handle refers to a directory.
Requires /proc mounted (same precondition as chmod_via_proc_fd).
Sourcepub async fn create_file(&self, name: &OsStr, mode: u32) -> Result<File>
pub async fn create_file(&self, name: &OsStr, mode: u32) -> Result<File>
Create a new child file, failing if it already exists and never following a symlink.
mode is the creation mode (subject to umask); exact permissions are set
later via fchmod. Returns the open writable File on success.
O_EXCL is the primary guard: combined with O_CREAT, it fails with
EEXIST on any pre-existing entry — including a symlink — without
following it. O_NOFOLLOW is the fallback that would still refuse to
follow a symlink (with ELOOP) should O_EXCL ever be bypassed.
Fails with EINVAL if name is not a single path component, or EEXIST
if a file or symlink at name already exists.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Dir
impl RefUnwindSafe for Dir
impl Send for Dir
impl Sync for Dir
impl Unpin for Dir
impl UnsafeUnpin for Dir
impl UnwindSafe for Dir
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request