Files
mesh-host/internal/apply/walk.go
T
jschoubben c7551162d6 Give no account a home, keep old data's sub-paths for the warrant, and never hand out setgid (hq ADR 0328 review)
usermod --gid regroups every file under an account's home, and accounts were made
with / as home: they now have /nonexistent, and an older account's home is moved
there (without its files) before its group changes. A sub-path asked for later that
holds data not in the group waits for the operator as the root would. The take act
gives the group to hardlinked files an account owns (the *arr apps' downloads) and
leaves root's; a file's setuid or setgid bit the regroup takes is never put back, so
no setgid file hands out the library's group; each is counted. An apply no longer
waits for a running take walk.
2026-10-12 22:18:16 +02:00

56 lines
2.7 KiB
Go

package apply
import (
"errors"
"path/filepath"
"time"
)
// A bounded walk under a library's root (novox/hq ADR 0328 §7, issue 567).
//
// **Walked by descriptor.** The root is opened as a mesh directory is (meshWalk: no link another account could
// make, the root 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 root's is not entered, and an entry opened is checked
// to be the one that was looked at. An entry's group and mode are set through its own descriptor, so a name
// swapped for a link between the look and the change changes nothing.
//
// **Bounded, and resumed.** A 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 root, kept in the engine's state and never said) and the
// next goes on from there, in the order of the names, so successive walks cover the library whole.
// WalkLimits bound one walk under a root: how many entries it reads, and for how long.
type WalkLimits struct {
Entries int
Time time.Duration
}
// ActLimits bound one walk of the take act; one stopped at its bound goes on from its cursor at the next look.
var ActLimits = WalkLimits{Entries: 5_000_000, Time: 30 * time.Minute}
// WalkCounts is what one walk did or found. Counts only: never a path below the root.
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; Linked files with another name an account owns, given the group; Shared files with another
// name root owns, left as they are; Stripped files whose setuid or setgid bit the group change took.
Links, Other, Mounts, Linked, Shared, Stripped int
// Read is every entry looked at; Failed the entries that could not be changed (an immutable file, say).
Read, Failed 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")
// keptPath refuses a root 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 root is not stated as the engine states one (absolute, no `..`, no doubled or " +
"trailing separator); nothing was changed")
}
return nil
}