Files
mesh-host/internal/apply/share.go
T
jschoubben fc3975d535 WIP: judge an access by who writes it, and act on its path only on a warrant (issue 567)
Paused: ADR 0325 replaces runs-as and the group grant. Kept for reuse: the
bounded descriptor walks, the warrant-backed access act (share, create),
the access wait and the missing-path waiting state.
2026-10-12 17:00:41 +02:00

87 lines
3.8 KiB
Go

package apply
import (
"errors"
"fmt"
"os/user"
"path/filepath"
"strconv"
"time"
)
// The share act on a read-write access's path (novox/hq ADR 0322 §6, issue 567).
//
// **The one change the mesh makes on an accessed path, and only on a warrant.** ADR 0312 §2 changes nothing on
// an accessed path. When the identities that write one path are more than one account, they share it only
// through its group: the group may write it, and every folder carries setgid, so a folder one of them makes keeps
// the group and the next can write in it too (§3). The operator lets that happen by approving a warrant for it
// (ADR 0259); the controller then asks this engine, signed, to run the act. The act adds group write to the path
// and every file and folder under it, and setgid to every folder: it changes no owner, no group and no other bit,
// follows no link, enters no other filesystem, and runs only for the path's own group, never a root group's.
//
// **Walked by descriptor.** The path is opened as a mesh directory is (meshWalk: no link another account could
// make, the path 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 path's is not entered, and an entry
// opened is checked to be the one that was looked at. A file is changed through its own descriptor, so a name
// swapped for a link between the look and the change changes nothing.
// ShareLimits bound one walk under an access's path: how many entries it reads, and for how long.
type ShareLimits struct {
Entries int
Time time.Duration
}
// ShareActLimits bound the act: a media library is millions of files, and an act that never ends is a machine
// that cannot say what it did. One stopped is asked again and goes on: what has the bits already is kept.
var ShareActLimits = ShareLimits{Entries: 5_000_000, Time: 30 * time.Minute}
// ShareCounts is what one walk did or found. Counts only: never a path below the access.
type ShareCounts struct {
// Folders and Files are the entries changed; Kept the ones that had the bits already.
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
}
// ErrShareBound is a walk that met its bound before it was through: what it did stays, what it did not reach is
// not known.
var ErrShareBound = errors.New("the walk met its bound")
// SharedVerdict is the read-only judgement of a shared path (ADR 0322 §3).
type SharedVerdict struct {
// Shared is that the path may be written by its group and it and every folder under it carry setgid.
Shared bool
// Why is what keeps it from being shared, in words with no path.
Why string
Counts ShareCounts
}
// ShareRefusal is why the act is never run for gid on a path whose group is pathGID, or nil.
func ShareRefusal(pathGID, gid int) error {
if gid != pathGID {
return fmt.Errorf("the share act runs for the path's own group, gid %d, and was asked for gid %d; nothing "+
"was changed", pathGID, gid)
}
g := PathGroup{GID: pathGID}
if found, err := user.LookupGroupId(strconv.Itoa(pathGID)); err == nil {
g.Name = found.Name
}
if err := GrantRefusal(g); err != nil {
return fmt.Errorf("the share act never runs for this group: %w", err)
}
return nil
}
// shareablePath refuses a path that is not stated exactly: absolute and clean, so `..` and a doubled separator
// never name it.
func shareablePath(path string) error {
if !filepath.IsAbs(path) || filepath.Clean(path) != path {
return fmt.Errorf("%q is not an access's path as the engine states it (absolute, no `..`, no doubled or "+
"trailing separator); nothing was changed", path)
}
return nil
}