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 }