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
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.
162 lines
6.2 KiB
Go
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
|
|
}
|