mesh/merge-gate head c7b534a08c98 merged into trunk 6fe82c61bd32 — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head c7b534a08c98 merged into trunk 6fe82c61bd32 — pass: its merge-check.sh passed
mesh/delivery delivered
A module's own directories need setgid so what is made beneath keeps its own group, and a mode of 2750 refused the whole declaration while every apply dropped the bit: a directory's mode now carries setgid (never setuid or sticky), set, kept and said. A tool runner's account that is missing or root's failed the whole apply and dropped the library's outcome: it is now a warning, said as runner:<module> or runner:library:<name>. And a runner that leaves a group is taken out before the resources apply, so its unit is restarted in that apply to lose it.
120 lines
4.6 KiB
Go
120 lines
4.6 KiB
Go
//go:build linux
|
|
|
|
package apply
|
|
|
|
// The apply half of mesh_dirs.go (novox/hq issue 358): a mesh directory outside every home made and given its mode
|
|
// and owner through the walk's descriptor.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
)
|
|
|
|
// meshDirsByDescriptor says this system can open a directory component by component without following a link.
|
|
const meshDirsByDescriptor = true
|
|
|
|
// openMeshDir opens the directory at path, from /, one component at a time with O_DIRECTORY|O_NOFOLLOW; a missing
|
|
// component is made in its parent's descriptor with mode, and made is called with each one made and its descriptor.
|
|
// A link in a parent is followed only when couldReplace and linkReplacer find that root alone could have made it,
|
|
// and its target is walked again from /; any other link is a LinkInPathError, and the last component being one is
|
|
// refused as not placed. Answers the directory's descriptor, which the caller closes, with the walk: what it made
|
|
// and the path it reached the directory at.
|
|
func openMeshDir(path string, mode os.FileMode, made func(dir string, fd int) error) (int, *meshWalk, error) {
|
|
w := &meshWalk{want: filepath.Clean(path), create: true, mode: mode, made: made}
|
|
fd, err := w.open()
|
|
return fd, w, err
|
|
}
|
|
|
|
// applyMeshDirectory gives a mesh directory outside every home its declared mode and owner through a descriptor
|
|
// (novox/hq issue 358): opened by openMeshDir, made there when missing, judged a directory by fstat on it, changed
|
|
// with fchmod and fchown on it, and read back from it. The parents it makes inside the owner's home are the owner's,
|
|
// as makeDirs gives them (novox/hq ADR 0182).
|
|
func applyMeshDirectory(r *declaration.Directory, out Outcome, mode os.FileMode, existed bool) (Outcome, error) {
|
|
uid, gid, chown := 0, 0, r.Owner != ""
|
|
var err error
|
|
if chown {
|
|
if uid, gid, err = idsOf(r.Owner); err != nil {
|
|
return out, fmt.Errorf("%s should belong to %q: %w", r.Path, r.Owner, err)
|
|
}
|
|
}
|
|
clean := filepath.Clean(r.Path)
|
|
home := ""
|
|
if chown {
|
|
if h, herr := homeOf(r.Owner); herr == nil && h != "" {
|
|
home = filepath.Clean(h)
|
|
}
|
|
}
|
|
giveMade := func(dir string, fd int) error {
|
|
if dir == clean || home == "" || (dir != home && !strings.HasPrefix(dir, home+string(os.PathSeparator))) {
|
|
return nil
|
|
}
|
|
if err := unix.Fchown(fd, uid, gid); err != nil {
|
|
return fmt.Errorf("cannot give %s to %q: %w", dir, r.Owner, &os.PathError{Op: "chown", Path: dir, Err: err})
|
|
}
|
|
return nil
|
|
}
|
|
fd, walk, err := openMeshDir(clean, mode, giveMade)
|
|
if err != nil {
|
|
var link *LinkInPathError
|
|
if errors.As(err, &link) {
|
|
return out, &PlacementRefusedError{Path: r.Path, Why: "it " + link.why()}
|
|
}
|
|
return out, err
|
|
}
|
|
defer unix.Close(fd)
|
|
// Made by this walk when its last component is: at the path the walk reached it at, which root-only links in its
|
|
// path may have moved from the path declared.
|
|
created := len(walk.Made) > 0 && walk.Made[len(walk.Made)-1] == walk.At
|
|
|
|
var st unix.Stat_t
|
|
if err := unix.Fstat(fd, &st); err != nil {
|
|
return out, &os.PathError{Op: "fstat", Path: r.Path, Err: err}
|
|
}
|
|
beforeMode, beforeUID, beforeGID := statDirMode(st.Mode), int(st.Uid), int(st.Gid)
|
|
// Set explicitly even when it existed: a mode given at creation is not a mode maintained — its setgid bit
|
|
// with it (novox/hq ADR 0334 §1).
|
|
if err := unix.Fchmod(fd, unixDirMode(mode)); err != nil {
|
|
return out, &os.PathError{Op: "chmod", Path: r.Path, Err: err}
|
|
}
|
|
ownedAlready := !chown || (beforeUID == uid && beforeGID == gid)
|
|
if !ownedAlready {
|
|
if err := unix.Fchown(fd, uid, gid); err != nil {
|
|
return out, fmt.Errorf("cannot give %s to %q: %w", r.Path, r.Owner, &os.PathError{Op: "chown", Path: r.Path, Err: err})
|
|
}
|
|
// A chown may take the setgid bit: the mode is set again after it.
|
|
if err := unix.Fchmod(fd, unixDirMode(mode)); err != nil {
|
|
return out, &os.PathError{Op: "chmod", Path: r.Path, Err: err}
|
|
}
|
|
}
|
|
|
|
// Read back, from the descriptor that was changed.
|
|
if err := unix.Fstat(fd, &st); err != nil {
|
|
return out, fmt.Errorf("made %s and cannot stat it: %w", r.Path, err)
|
|
}
|
|
if st.Mode&unix.S_IFMT != unix.S_IFDIR {
|
|
return out, fmt.Errorf("%s is not a directory after applying", r.Path)
|
|
}
|
|
if got := statDirMode(st.Mode); got != dirBits(mode) {
|
|
return out, fmt.Errorf("%s is mode %s after setting %s", r.Path, modeText(got), modeText(mode))
|
|
}
|
|
|
|
out.Action = "unchanged"
|
|
switch {
|
|
case created || !existed:
|
|
out.Action = "created"
|
|
case !ownedAlready:
|
|
out.Action = "updated"
|
|
case beforeMode != dirBits(mode):
|
|
out.Action = "updated"
|
|
out.Detail = fmt.Sprintf("mode %s to %s", modeText(beforeMode), modeText(mode))
|
|
}
|
|
return out, nil
|
|
}
|