//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 }