Files
mesh-host/internal/apply/library_linux.go
T
jschoubben 281837847e
mesh/merge-gate head 281837847e03 merged into trunk 303d4037e84e — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head 281837847e03 merged into trunk 303d4037e84e — pass: its merge-check.sh passed
mesh/delivery delivered
Put busybox's account removal back, test the sample, and read a held lock as kept only for a running walk (hq ADR 0328 review)
An earlier change dropped alpine's RemoveAccount and RemoveGroup, so alpine made no
module identity at all. The sample beneath a root in shape had no test of its own.
And an apply that found the library's lock held read the library as kept even when
what held it was a take act about to be refused for a missing disk; it now does so
only while a take walk is running, and otherwise waits and judges as always.
2026-10-12 22:21:42 +02:00

162 lines
6.2 KiB
Go

//go:build linux
package apply
import (
"context"
"errors"
"fmt"
"time"
"github.com/novox/mesh-host/internal/declaration"
"github.com/novox/mesh-host/internal/store"
)
// The take act on a library, and the walk that goes on with one stopped at its bound (novox/hq ADR 0328 §7).
// actRecord is a walk's counts as the record keeps them.
func actRecord(warrant, by, on string, started time.Time, w *treeWalk, err error) *store.KeptAct {
c := w.counts
rec := &store.KeptAct{Act: "take", Warrant: warrant, By: by, On: on, State: "done", Started: started,
Ended: time.Now().UTC(), Folders: c.Folders, Files: c.Files, Kept: c.Kept, Links: c.Links, Other: c.Other,
Mounts: c.Mounts, Shared: c.Shared, Linked: c.Linked, Stripped: c.Stripped}
if c.Shared+c.Linked+c.Stripped > 0 {
rec.Why = fmt.Sprintf("%d file(s) with another name given the group, %d root's left as they are, %d whose "+
"setuid or setgid bit the group change took", c.Linked, c.Shared, c.Stripped)
}
switch {
case errors.Is(err, ErrWalkBound):
rec.State, rec.Why = "stopped", "it met its bound of entries or time; it goes on from where it stopped"
case err != nil:
rec.State, rec.Why = "failed", err.Error()
case w.Failed != "":
rec.State = "failed"
rec.Why = fmt.Sprintf("%d entries could not be given the group: %s; walked again on the next look", c.Failed,
w.Failed)
}
return rec
}
// TakeLibrary is the take act (ADR 0328 §7): on the operator's warrant, the library's group, group read and write,
// and on folders group enter and setgid, set on its root and everything under it — no link followed, no other
// filesystem entered, the owner and the owner's and others' bits unchanged, a file with another name elsewhere left
// as it is. It judges the ask against the declaration last applied and the root as it is, and starts the walk: what
// it answers at once, and a channel closed when the walk is through or stopped at its bound. Refused, nothing is
// changed.
func TakeLibrary(ctx context.Context, d *declaration.Declaration, access, path, warrant, by string) (<-chan struct{},
string, error) {
if d == nil {
return nil, "", errors.New("this engine has applied no declaration, and takes no library it was not told of")
}
var lib *declaration.Library
for i := range d.Libraries {
if LibraryAccess(d.Libraries[i].Library) == access {
lib = &d.Libraries[i]
}
}
switch {
case lib == nil:
return nil, "", fmt.Errorf("the access %s names no library declared on this machine", access)
case !lib.Home:
return nil, "", fmt.Errorf("this machine is not the home of the library %s, and a library is taken on its "+
"home", lib.Library)
case lib.Root != path:
return nil, "", fmt.Errorf("the library %s is declared at another root than the one asked", lib.Library)
}
lock := libraryLock(lib.Library)
if !lock.TryLock() {
return nil, "", errors.New("a walk of the library is running; asked again once it is through, the act goes on")
}
rec, _ := libraryRecord(lib.Library)
v := assessLibrary(ctx, *lib, rec)
var refused error
switch {
case v.err != nil:
refused = v.err
case v.kind == libraryWaitDisk:
refused = errors.New(v.why)
case v.kind == libraryUnknown:
refused = errors.New(v.why + ", so a warrant could name no filesystem it was given for")
}
if refused != nil {
lock.Unlock()
return nil, "", refused
}
if _, _, err := keepFolders(lib.Root, lib.Paths, lib.GID); err != nil {
lock.Unlock()
return nil, "", err
}
started := time.Now().UTC()
_ = Libraries.Update(lib.Library, func(r *store.LibraryRecord, _ bool) bool {
r.Root, r.Mount, r.Placed, r.Fs, r.GID = lib.Root, v.mount, v.placed, v.fs, lib.GID
r.Kept, r.KeptOn, r.How, r.At = true, v.placed, store.KeptByWarrant, started
r.Warrant, r.By, r.Cursor = warrant, by, nil
r.Act = &store.KeptAct{Act: "take", Warrant: warrant, By: by, On: v.placed, State: "running", Started: started}
return true
})
done := make(chan struct{})
go func() {
defer close(done)
defer lock.Unlock()
walkLibrary(lib.Library, lib.Root, lib.GID, warrant, by, v.placed, nil)
}()
return done, fmt.Sprintf("the take act started under the warrant %s: %s's group, group read and write, and "+
"setgid on its folders are set on everything under its root, following no link and entering no other "+
"filesystem", warrant, lib.Library), nil
}
// walkLibrary walks a library's root from resume with its group, and records what it did: a walk stopped at its
// bound keeps its cursor for the next. The caller holds the library's lock.
func walkLibrary(name, root string, gid int, warrant, by, on string, resume []string) {
libraryWalks.Store(name, on)
defer libraryWalks.Delete(name)
started := time.Now().UTC()
w, err := walkRoot(root, ActLimits, machineStats, resume, groupVisit(gid))
rec := actRecord(warrant, by, on, started, w, err)
_ = Libraries.Update(name, func(r *store.LibraryRecord, _ bool) bool {
r.Act = rec
r.Cursor = nil
if errors.Is(err, ErrWalkBound) {
r.Cursor = w.Cursor
}
return true
})
}
// ReconcileLibraries goes on with every take act that stopped at its bound or left entries it could not change,
// from where it stopped — only while the library is still kept on the filesystem it was taken on (judged as an apply
// judges it). What it did, in words, for the engine to say.
func ReconcileLibraries(ctx context.Context, d *declaration.Declaration) []string {
if d == nil {
return nil
}
var said []string
for _, l := range d.Libraries {
if ctx.Err() != nil {
break
}
if !l.Home {
continue
}
rec, _ := libraryRecord(l.Library)
if rec.Act == nil || (rec.Act.State != "stopped" && rec.Act.State != "failed") || rec.How != store.KeptByWarrant {
continue
}
lock := libraryLock(l.Library)
if !lock.TryLock() {
continue
}
v := assessLibrary(ctx, l, rec)
if v.err != nil || v.kind != libraryKeep || v.placed != rec.KeptOn || v.moved {
lock.Unlock()
continue
}
walkLibrary(l.Library, l.Root, l.GID, rec.Warrant, rec.By, rec.KeptOn, rec.Cursor)
lock.Unlock()
after, _ := libraryRecord(l.Library)
said = append(said, fmt.Sprintf("the library %s: the take act went on, %s (%d folders and %d files changed)",
l.Library, after.Act.State, after.Act.Folders, after.Act.Files))
}
return said
}