Skip to main content

Module walk_driver

Module walk_driver 

Source
Expand description

Generic single-tree safe-walk driver.

This module owns the recursive directory-walk skeleton that copy, chmod, and rm previously each hand-coded. A tool supplies a WalkVisitor; the driver drives the traversal:

  1. gated read_entries on the open hardened directory,
  2. per child: authoritative-or-hinted walk::filter_is_dir + walk::should_skip_entry filter decision (against the child’s EntryCx::filter_path), then walk::preacquire_leaf_permit per the visitor’s policy,
  3. acquire-then-spawn one task per non-skipped child, joining/folding via join_and_fold (NOT batched: see walk_dir_contents for why the permit is acquired and the task spawned in the same loop step),
  4. in each task: authoritative Dir::child classification, then either WalkVisitor::visit_leaf (holding the permit) or — for a directory — drop the permit, WalkVisitor::dir_pre, recurse, WalkVisitor::dir_post.

§The single invariant home

The “drop the leaf permit before recursing into a directory” invariant — the root cause of the hold-and-wait deadlock class (see walk::LeafPermit) — lives in exactly one place: the directory branch of process_entry. Leaves hold their permit across WalkVisitor::visit_leaf; the directory branch drops it before any further work. No visitor ever hand-drops a leaf permit, so the invariant cannot silently migrate back to N parallel sites.

§Cancellation safety

Spawned tasks must be 'static, and spawn_blocking work is not cancellable, so every per-entry context is owned: EntryCx clones Arc<Dir> plus owned OsString/PathBuf rather than borrowing, exactly as the existing per-tool walks do. A dropped surrounding future (timeout, fail_early abort, Ctrl-C) therefore can never leave a spawned task holding a dangling borrow.

§How copy maps onto this trait

copy is the reference single-tree visitor (rcp::copy::CopyVisitor); its mapping shaped this trait. CopyVisitor holds the run-constant state (dst_root, filter_base, Settings, preserve::Settings, the opened top-level destination parent — the source root needs no field, since each entry’s source path is its EntryCx::real_path) and:

  • type Summary = copy::Summary.
  • type DirContext = the destination parent for one level: { dst_dir: Option<Arc<Dir>>, is_fresh: bool } (None dst = dry-run). This is how the single-tree driver carries copy’s second tree — each child reads its destination parent from parent_ctx rather than the driver modeling two trees. WalkVisitor::root_dir_context returns the opened top-level destination (Some(dst)/None) with the initial is_fresh.
  • type DirState = { dst_dir, dst_parent, dst_name, we_created, src_meta, is_root, base } — what dir_post needs to apply directory metadata (src_meta is taken from dir_pre’s classification Handle, no extra stat), run empty-dir cleanup (dst_parent.rmdir_at(dst_name)), and --delete-prune, plus the base create/unchanged contribution it folds with the children.
  • visit_leaf dispatches on kind: Filecopy_file_fd, Symlinkcopy_symlink_fd, Special → skip-or-error. The pre-acquired permit is the open-files guard copy_file_fd needs; it is dropped for symlink/special. --dereference of a symlink-to-dir stays inside visit_leaf: it drops the permit and calls the path-based copy() recursively (the one deliberately non-fd path), which the trait expresses fine — visit_leaf is a plain async fn that may itself recurse without going through the driver.
  • dir_pre runs resolve_dst_dir: DirResolution::SkipDirAction::Skip (--ignore-existing hit a non-dir); Proceed{dir,..}DirAction::Descend whose dir is the source dir (opened via src_parent.open_dir(name)), child_ctx carries the resolved dst_dir + child is_fresh, and state carries the DirState.
  • dir_post receives the children’s folded Result: on Ok it runs the --delete prune (keep-set = processed.names()), empty-dir cleanup, and set_dir_metadata_fd (post-order); on Err (a non-fail-early child failure) it skips the destructive prune, still applies directory metadata, and returns the combined error — exactly as copy_dir_contents’s tail did.
  • on_skip mirrors copy’s inline filter-skip: report_skip in dry-run + skipped_summary_for(kind).
  • permit_kind = OpenFile; want_permit = “hint is File” (copy only pre-acquires for a regular-file hint — symlinks may deref to dirs, and DT_UNKNOWN might be a dir).

The delegated-subtree case (rlink handing copy an update-only/type-changed subtree rooted below the original filter root) is carried by seeding the root EntryCx::filter_path with the subtree’s logical base, so the filter still matches at the entry’s true path while rel_path/real_path stay relative to the delegated root.

The dry-run “directory” path (no destination dir, contents still traversed for reporting) is just DirContext.dst_dir == None threaded through — the same branch copy already has. No part of copy needs a trait shape this module does not provide, which is why the trait stops here (no second-tree concept leaks into the driver — that asymmetry is what keeps rlink on the substrate, not the visitor; see docs/tocttou.md, “One shared traversal driver”).

Structs§

EntryCx
Owned per-entry context handed to every WalkVisitor method.
ProcessedChildren
The names of the non-skipped children the driver actually spawned for a directory, in enumeration order.

Enums§

DirAction
What a WalkVisitor::dir_pre decided to do with a directory entry.

Traits§

WalkSummary
A per-run summary accumulated by the walk.
WalkVisitor
A tool’s policy for a single-tree safe walk.

Functions§

join_and_fold
Join an already-populated JoinSet of per-child tasks and fold their summaries with fail-early / error-collection semantics — the shared join engine behind the directory walk.
process_entry
Process one already-located entry: classify it authoritatively via Dir::child, then dispatch.
walk_dir_contents
Walk the contents of an open hardened directory.

Type Aliases§

DirPreResult
The Result a WalkVisitor::dir_pre produces: a DirAction over the visitor’s three associated types, or its OperationError. A named alias so the trait method’s signature stays readable.