A mesh directory outside every home was chmodded and chowned by path, so a parent turned into a link between two applies carried root's chmod and chown to the link's target. It is now made and changed through the descriptor of the walk from / that follows a link only root alone could have made. A directory that walk refuses is a refusal like the placement guard's (review 47): judged before anything is applied, and again when the directory is applied, used as found or not; refused at either point it gates its module and refuses what is below it, so no file is written into the link's target and the report does not say everything was attempted.
115 lines
4.4 KiB
Go
115 lines
4.4 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 := os.FileMode(st.Mode&0o777), int(st.Uid), int(st.Gid)
|
|
// Set explicitly even when it existed: a mode given at creation is not a mode maintained.
|
|
if err := unix.Fchmod(fd, uint32(mode.Perm())); 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})
|
|
}
|
|
}
|
|
|
|
// 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 := os.FileMode(st.Mode & 0o777); got != mode.Perm() {
|
|
return out, fmt.Errorf("%s is mode %o after setting %o", r.Path, got, mode.Perm())
|
|
}
|
|
|
|
out.Action = "unchanged"
|
|
switch {
|
|
case created || !existed:
|
|
out.Action = "created"
|
|
case !ownedAlready:
|
|
out.Action = "updated"
|
|
case beforeMode != mode.Perm():
|
|
out.Action = "updated"
|
|
out.Detail = fmt.Sprintf("mode %o to %o", beforeMode, mode.Perm())
|
|
}
|
|
return out, nil
|
|
}
|