Files
mesh-host/internal/apply/walk.go
T
jschoubben 2026ef36f3 Judge a kept folder before every walk, and keep nothing a disk mounted over (hq ADR 0325 review)
A walk run while a dataset was unmounted wrote onto root's empty mount point, or
"dropped" entries from it and forgot the ones on the data; a folder kept while empty
below an unnamed mount was then swept whole once the disk mounted, with no warrant.
Every walk is now judged as an apply is, a changed mount judges the folder afresh, and
systemd .mount units and mounts seen are read for the first-sight rule. Widening a
mask caps the entries it limited, so the owning group gains nothing. The watch lets
go of removed folders, re-arms on lost notice, reads users under its lock and ends
with its context. A drop that cannot read an ACL is counted, a broken folder's words
carry no path, a grant's group that could not be left is tried again, and a folder's
stat is bounded.
2026-10-12 18:55:47 +02:00

67 lines
3.2 KiB
Go

package apply
import (
"errors"
"path/filepath"
"time"
)
// A bounded walk under a kept folder (novox/hq ADR 0325 §8, §10, issue 567).
//
// **Walked by descriptor.** The folder is opened as a mesh directory is (meshWalk: no link another account could
// make, the folder itself no link), and everything under it is reached from its parent's descriptor with
// O_NOFOLLOW: a link met is passed by, a folder whose device is not the kept folder's is not entered, and an entry
// opened is checked to be the one that was looked at. An entry's ACL is read and written through its own
// descriptor, so a name swapped for a link between the look and the change changes nothing.
//
// **Bounded, and resumed.** A media library is millions of files, and a walk that never ends is a machine that
// cannot say what it did. Each walk reads at most so many entries for at most so long; one that meets its bound
// says how far it got (a cursor, the names of each level below the folder, kept in the engine's state and never
// said) and the next goes on from there, in the order of the names, so successive looks cover a folder whole.
// WalkLimits bound one walk under a folder: how many entries it reads, and for how long.
type WalkLimits struct {
Entries int
Time time.Duration
}
// ActLimits bound the take act and the walk that adds or drops a user's entries. One stopped goes on at the next
// apply, from the start: what already has its entries is passed quickly.
var ActLimits = WalkLimits{Entries: 5_000_000, Time: 30 * time.Minute}
// SweepLimits bound the sweep of one kept folder on one look (ADR 0325 §10): the look must come back within its
// own bound whatever the folder holds.
var SweepLimits = WalkLimits{Entries: 2000, Time: 2 * time.Second}
// WalkCounts is what one walk did or found. Counts only: never a path below the folder.
type WalkCounts struct {
// Folders and Files are the entries changed; Kept the ones that already had what they need.
Folders, Files, Kept int
// Links are links passed by, Other entries neither a file nor a folder, Mounts folders on another
// filesystem not entered.
Links, Other, Mounts int
// Read is every entry looked at.
Read int
// Narrowed is the entries that gave a user less than its right (judged by entry and mask together) and
// were put back; Unrepaired the ones that could not be — the owner's bits, which the mesh never changes.
Narrowed, Unrepaired int
}
// ErrWalkBound is a walk that met its bound before it was through: what it did stays, and the cursor says where
// the next one goes on.
var ErrWalkBound = errors.New("the walk met its bound")
// ErrNoACL is a filesystem that allows no ACLs here (ENOTSUP): a ZFS dataset whose acltype is off, say. The access
// waits for the take act, which sets it where it can (ADR 0325 §8).
var ErrNoACL = errors.New("the filesystem allows no access entries")
// keptPath refuses a folder that is not stated exactly: absolute and clean, so `..` and a doubled separator
// never name it.
func keptPath(path string) error {
if !filepath.IsAbs(path) || filepath.Clean(path) != path {
return errors.New("the folder is not stated as the engine states one (absolute, no `..`, no doubled or " +
"trailing separator); nothing was changed")
}
return nil
}