Keep libraries under groups of their own, and give modules users in them (hq ADR 0328, issue 567)
ADR 0328 supersedes ADR 0325: shared data is a library with one home, kept usable by its group rather than by ACL entries. The engine now makes each library's group and each module's user in its libraries' groups; keeps a library's root and requested sub-paths in its group with group write and setgid on its home, making missing sub-paths as the root is; waits for the operator's word before touching a root that holds data, and on the take act walks it by descriptor (no link, same filesystem, bounded and resumed, files with other names left); holds the containers of a library whose disk or mount is not there; and puts the tool runner's account in the group. The ACL keeping, mask repair and watch are gone; an access is again as before ADR 0325.
This commit is contained in:
+170
-292
@@ -2,7 +2,7 @@
|
||||
|
||||
package apply
|
||||
|
||||
// The descriptor half of walk.go and acl.go (novox/hq ADR 0325 §5, §8, §9, §10).
|
||||
// The descriptor half of walk.go and library.go (novox/hq ADR 0328 §6, §7, §9).
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -29,29 +30,42 @@ var machineStats = walkStats{
|
||||
fd: unix.Fstat,
|
||||
}
|
||||
|
||||
// visitFunc is what a walk does to one folder or file, open on fd, whose stat is st: whether it changed it, and
|
||||
// whether it found the entry narrowed and could not put it back. An error stops the walk.
|
||||
type visitFunc func(fd int, st unix.Stat_t, folder bool) (changed, narrowed, unrepaired bool, err error)
|
||||
// What a visit did to one entry.
|
||||
const (
|
||||
visitKept = iota // it had what it needs
|
||||
visitChanged // it was changed
|
||||
visitShared // a file with another name elsewhere: left as it is
|
||||
)
|
||||
|
||||
// aclWalk is one walk under a kept folder, from the descriptor of the folder itself.
|
||||
type aclWalk struct {
|
||||
// visitFunc is what a walk does to one folder or file, open on fd, whose stat is st: what it did, or the error
|
||||
// counted on that entry alone (errStopWalk stops the walk).
|
||||
type visitFunc func(fd int, st unix.Stat_t, folder bool) (int, error)
|
||||
|
||||
// errStopWalk wraps an error that stops the whole walk, never one entry's alone.
|
||||
type errStopWalk struct{ err error }
|
||||
|
||||
func (e errStopWalk) Error() string { return e.err.Error() }
|
||||
func (e errStopWalk) Unwrap() error { return e.err }
|
||||
|
||||
// treeWalk is one walk under a library's root, from the descriptor of the root itself.
|
||||
type treeWalk struct {
|
||||
dev uint64
|
||||
deadline time.Time
|
||||
limit int
|
||||
stats walkStats
|
||||
visit visitFunc
|
||||
counts WalkCounts
|
||||
// at is the names of the levels below the folder down to the entry being walked; Cursor, once the walk met
|
||||
// its bound, where the next goes on.
|
||||
// at is the names of the levels below the root down to the entry being walked; Cursor, once the walk met its
|
||||
// bound, where the next goes on.
|
||||
at []string
|
||||
Cursor []string
|
||||
// Failed is the first failure on one entry, in words with no path.
|
||||
Failed string
|
||||
}
|
||||
|
||||
// openKept opens a kept folder as a mesh directory is opened (no link another account could make, the folder
|
||||
// itself no link) and starts a walk from it on the folder's own device.
|
||||
func openKept(path string, limits WalkLimits, stats walkStats, visit visitFunc) (*aclWalk, int, unix.Stat_t, error) {
|
||||
// openRoot opens a library's root as a mesh directory is opened (no link another account could make, the root
|
||||
// itself no link) and starts a walk from it on the root's own device.
|
||||
func openRoot(path string, limits WalkLimits, stats walkStats, visit visitFunc) (*treeWalk, int, unix.Stat_t, error) {
|
||||
var st unix.Stat_t
|
||||
if err := keptPath(path); err != nil {
|
||||
return nil, -1, st, err
|
||||
@@ -59,36 +73,36 @@ func openKept(path string, limits WalkLimits, stats walkStats, visit visitFunc)
|
||||
mw := &meshWalk{want: path}
|
||||
fd, err := mw.open()
|
||||
if errors.Is(err, errNotThereYet) {
|
||||
return nil, -1, st, errors.New("the folder is not there; nothing was changed")
|
||||
return nil, -1, st, errors.New("the root is not there; nothing was changed")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, -1, st, errors.New("the folder is not walked: " + pathless(err))
|
||||
return nil, -1, st, errors.New("the root is not walked: " + pathless(err))
|
||||
}
|
||||
if mw.At != path {
|
||||
unix.Close(fd)
|
||||
return nil, -1, st, errors.New("the folder is reached through a link, and the engine walks no folder " +
|
||||
"through one; nothing was changed")
|
||||
return nil, -1, st, errors.New("the root is reached through a link, and the engine walks no root through " +
|
||||
"one; nothing was changed")
|
||||
}
|
||||
if err := stats.fd(fd, &st); err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, -1, st, fmt.Errorf("the folder cannot be read: %v; nothing was changed", err)
|
||||
return nil, -1, st, fmt.Errorf("the root cannot be read: %v; nothing was changed", err)
|
||||
}
|
||||
return &aclWalk{dev: uint64(st.Dev), deadline: time.Now().Add(limits.Time), limit: limits.Entries,
|
||||
return &treeWalk{dev: uint64(st.Dev), deadline: time.Now().Add(limits.Time), limit: limits.Entries,
|
||||
stats: stats, visit: visit}, fd, st, nil
|
||||
}
|
||||
|
||||
// walkKept walks path whole, or from resume, with visit: the walk, with what it did and the cursor where it
|
||||
// stopped when it met its bound (with ErrWalkBound), and the walk's own error otherwise.
|
||||
func walkKept(path string, limits WalkLimits, stats walkStats, resume []string, visit visitFunc) (*aclWalk, error) {
|
||||
w, fd, st, err := openKept(path, limits, stats, visit)
|
||||
// walkRoot walks path whole, or from resume, with visit: the walk, with what it did and the cursor where it stopped
|
||||
// when it met its bound (with ErrWalkBound), and the walk's own error otherwise.
|
||||
func walkRoot(path string, limits WalkLimits, stats walkStats, resume []string, visit visitFunc) (*treeWalk, error) {
|
||||
w, fd, st, err := openRoot(path, limits, stats, visit)
|
||||
if err != nil {
|
||||
return &aclWalk{}, err
|
||||
return &treeWalk{}, err
|
||||
}
|
||||
return w, w.dir(fd, st, resume)
|
||||
}
|
||||
|
||||
// read counts one entry, and says the bound when it is met, keeping where the walk stood as the cursor.
|
||||
func (w *aclWalk) read() error {
|
||||
func (w *treeWalk) read() error {
|
||||
if w.counts.Read >= w.limit || time.Now().After(w.deadline) {
|
||||
w.Cursor = append([]string(nil), w.at...)
|
||||
return ErrWalkBound
|
||||
@@ -97,27 +111,9 @@ func (w *aclWalk) read() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// count adds one visit's result.
|
||||
func (w *aclWalk) count(folder, changed, narrowed, unrepaired bool) {
|
||||
switch {
|
||||
case changed && folder:
|
||||
w.counts.Folders++
|
||||
case changed:
|
||||
w.counts.Files++
|
||||
default:
|
||||
w.counts.Kept++
|
||||
}
|
||||
if narrowed {
|
||||
w.counts.Narrowed++
|
||||
}
|
||||
if unrepaired {
|
||||
w.counts.Unrepaired++
|
||||
}
|
||||
}
|
||||
|
||||
// dir is one folder, open on fd, whose stat is st: the folder itself (unless the walk resumes below it, which an
|
||||
// earlier walk visited), then every entry in it in the order of their names. It closes fd.
|
||||
func (w *aclWalk) dir(fd int, st unix.Stat_t, resume []string) error {
|
||||
func (w *treeWalk) dir(fd int, st unix.Stat_t, resume []string) error {
|
||||
f := os.NewFile(uintptr(fd), "")
|
||||
defer f.Close()
|
||||
if len(resume) == 0 {
|
||||
@@ -154,9 +150,9 @@ func (w *aclWalk) dir(fd int, st unix.Stat_t, resume []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// entry is one name in the folder open on dirfd: a link passed by, a folder on this device walked, a file
|
||||
// visited through its own descriptor, anything else left.
|
||||
func (w *aclWalk) entry(dirfd int, name string, resume []string) error {
|
||||
// entry is one name in the folder open on dirfd: a link passed by, a folder on this device walked, a file visited
|
||||
// through its own descriptor, anything else left.
|
||||
func (w *treeWalk) entry(dirfd int, name string, resume []string) error {
|
||||
var st unix.Stat_t
|
||||
if err := w.stats.at(dirfd, name, &st); err != nil {
|
||||
if errors.Is(err, unix.ENOENT) {
|
||||
@@ -212,192 +208,65 @@ func (w *aclWalk) entry(dirfd int, name string, resume []string) error {
|
||||
return w.visited(fd, opened, false)
|
||||
}
|
||||
|
||||
// visited visits one entry and counts what it did. A filesystem that allows no ACLs stops the walk; any other
|
||||
// failure on one entry (an immutable file, say) is counted unrepaired, its words kept once, and the walk goes on.
|
||||
func (w *aclWalk) visited(fd int, st unix.Stat_t, folder bool) error {
|
||||
changed, narrowed, unrepaired, err := w.visit(fd, st, folder)
|
||||
if errors.Is(err, ErrNoACL) {
|
||||
return err
|
||||
// visited visits one entry and counts what it did. An error that stops the walk stops it; any other failure on
|
||||
// one entry (an immutable file, say) is counted failed, its words kept once, and the walk goes on.
|
||||
func (w *treeWalk) visited(fd int, st unix.Stat_t, folder bool) error {
|
||||
did, err := w.visit(fd, st, folder)
|
||||
var stop errStopWalk
|
||||
if errors.As(err, &stop) {
|
||||
return stop.err
|
||||
}
|
||||
if err != nil {
|
||||
unrepaired = true
|
||||
w.counts.Failed++
|
||||
if w.Failed == "" {
|
||||
w.Failed = err.Error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
w.count(folder, changed, narrowed, unrepaired)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getACL reads one of an entry's ACLs through its descriptor: the access ACL, or a folder's default. An access
|
||||
// ACL that is not there is the one its mode stands for; a default that is not there is nil.
|
||||
func getACL(fd int, name string, mode uint32) (acl, error) {
|
||||
buf := make([]byte, 4+8*32)
|
||||
for {
|
||||
n, err := unix.Fgetxattr(fd, name, buf)
|
||||
switch {
|
||||
case errors.Is(err, unix.ENODATA):
|
||||
if name == aclAccessXattr {
|
||||
return minimalACL(mode), nil
|
||||
}
|
||||
return nil, nil
|
||||
case errors.Is(err, unix.ERANGE):
|
||||
buf = make([]byte, 2*len(buf))
|
||||
continue
|
||||
case errors.Is(err, unix.ENOTSUP), errors.Is(err, unix.EOPNOTSUPP):
|
||||
return nil, ErrNoACL
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("an entry's ACL could not be read: %v", err)
|
||||
}
|
||||
return decodeACL(buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
// setACL writes one of an entry's ACLs through its descriptor, refusing one the kernel would.
|
||||
func setACL(fd int, name string, a acl) error {
|
||||
if err := a.check(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := unix.Fsetxattr(fd, name, a.encode(), 0)
|
||||
switch {
|
||||
case errors.Is(err, unix.ENOTSUP), errors.Is(err, unix.EOPNOTSUPP):
|
||||
return ErrNoACL
|
||||
case err != nil:
|
||||
return fmt.Errorf("an entry's ACL could not be written: %v", err)
|
||||
case did == visitShared:
|
||||
w.counts.Shared++
|
||||
case did == visitChanged && folder:
|
||||
w.counts.Folders++
|
||||
case did == visitChanged:
|
||||
w.counts.Files++
|
||||
default:
|
||||
w.counts.Kept++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sameACL is whether a and b hold the same entries, whatever their order.
|
||||
func sameACL(a, b acl) bool {
|
||||
return string(a.encode()) == string(b.encode())
|
||||
}
|
||||
|
||||
// needs is, for a folder or a file, each kept uid's bits — except its owner's, which its owner's bits give and the
|
||||
// mesh never changes.
|
||||
func needs(users map[uint32]bool, owner uint32, folder bool) map[uint32]uint16 {
|
||||
out := map[uint32]uint16{}
|
||||
for uid, rw := range users {
|
||||
if uid != owner {
|
||||
out[uid] = needOn(rw, folder)
|
||||
// groupVisit gives each entry the library's group gid, group read and write, and on a folder group enter and the
|
||||
// setgid bit, so what is made beneath keeps the group (ADR 0328 §6, §7). The owner, the owner's bits and others'
|
||||
// bits are never changed: a file's own setuid or setgid bit, which the kernel drops when the group changes, is put
|
||||
// back as it was. A file with more than one name is left as it is — its other name may be outside the library, and
|
||||
// giving it the group would give the group that other file.
|
||||
func groupVisit(gid int) visitFunc {
|
||||
return func(fd int, st unix.Stat_t, folder bool) (int, error) {
|
||||
if !folder && st.Nlink > 1 {
|
||||
return visitShared, nil
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// keepVisit makes users' entries on each entry (ADR 0325 §5): the access ACL with each uid's entry and a mask
|
||||
// that covers it, and on a folder the same as its default, so what is made under it inherits them. An entry that
|
||||
// had them is not written. A user that owns the entry is judged by its owner's bits, never given an entry: an
|
||||
// owner whose bits fall short is unrepaired, said, and never changed.
|
||||
func keepVisit(users map[uint32]bool) visitFunc {
|
||||
return func(fd int, st unix.Stat_t, folder bool) (bool, bool, bool, error) {
|
||||
mode := uint32(st.Mode) & 0o7777
|
||||
need := needs(users, st.Uid, folder)
|
||||
access, err := getACL(fd, aclAccessXattr, mode)
|
||||
if err != nil {
|
||||
return false, false, false, err
|
||||
}
|
||||
narrowed, unrepaired := judgeACL(access, users, st, folder)
|
||||
changed := false
|
||||
if kept := keptACL(access, need); !sameACL(kept, access) {
|
||||
if err := setACL(fd, aclAccessXattr, kept); err != nil {
|
||||
return false, narrowed, true, err
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
want := mode | 0o060
|
||||
if folder {
|
||||
def, err := getACL(fd, aclDefaultXattr, mode)
|
||||
if err != nil {
|
||||
return changed, narrowed, unrepaired, err
|
||||
}
|
||||
base := def
|
||||
if base == nil {
|
||||
// No default yet: one whose owner's, owning group's and others' entries are the folder's own, as
|
||||
// setfacl -d derives it.
|
||||
base = minimalACL(mode)
|
||||
if g, ok := access.perm(aclGroupObj, 0); ok {
|
||||
base[1].Perm = g
|
||||
}
|
||||
}
|
||||
// The default is for what anyone makes under the folder, never its owner alone: every user's entry.
|
||||
all := map[uint32]uint16{}
|
||||
for uid, rw := range users {
|
||||
all[uid] = needOn(rw, true)
|
||||
}
|
||||
if kept := keptACL(base, all); def == nil || !sameACL(kept, def) {
|
||||
if err := setACL(fd, aclDefaultXattr, kept); err != nil {
|
||||
return changed, narrowed, true, err
|
||||
}
|
||||
changed = true
|
||||
want = mode | 0o070 | unix.S_ISGID
|
||||
}
|
||||
if int(st.Gid) == gid && want == mode {
|
||||
return visitKept, nil
|
||||
}
|
||||
if int(st.Gid) != gid {
|
||||
if err := unix.Fchown(fd, -1, gid); err != nil {
|
||||
return 0, fmt.Errorf("an entry's group could not be set: %v", err)
|
||||
}
|
||||
}
|
||||
return changed, narrowed, unrepaired, nil
|
||||
if err := unix.Fchmod(fd, want); err != nil {
|
||||
return 0, fmt.Errorf("an entry's mode could not be set: %v", err)
|
||||
}
|
||||
return visitChanged, nil
|
||||
}
|
||||
}
|
||||
|
||||
// judgeACL is whether an entry with access ACL a gives a user less than its right — by entry and mask together
|
||||
// (ADR 0325 §6) — and whether that is one the mesh cannot put back: the owner's own bits.
|
||||
func judgeACL(a acl, users map[uint32]bool, st unix.Stat_t, folder bool) (narrowed, unrepaired bool) {
|
||||
mode := uint32(st.Mode) & 0o7777
|
||||
for uid, rw := range users {
|
||||
need := needOn(rw, folder)
|
||||
if effective(a, uid, st.Uid, mode)&need == need {
|
||||
continue
|
||||
}
|
||||
narrowed = true
|
||||
if uid == st.Uid {
|
||||
unrepaired = true
|
||||
}
|
||||
}
|
||||
return narrowed, unrepaired
|
||||
}
|
||||
|
||||
// dropVisit takes exactly the entries of each uid of drop out of each entry's access ACL and a folder's default
|
||||
// (ADR 0325 §8): no other entry, and not the mask, so nobody else loses a right.
|
||||
func dropVisit(drop map[uint32]bool) visitFunc {
|
||||
return func(fd int, st unix.Stat_t, folder bool) (bool, bool, bool, error) {
|
||||
mode := uint32(st.Mode) & 0o7777
|
||||
changed := false
|
||||
for _, name := range []string{aclAccessXattr, aclDefaultXattr} {
|
||||
if name == aclDefaultXattr && !folder {
|
||||
continue
|
||||
}
|
||||
a, err := getACL(fd, name, mode)
|
||||
if errors.Is(err, ErrNoACL) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
// Counted unrepaired and said by the walk, which then walks it again: never read as "nothing to drop".
|
||||
return changed, false, true, err
|
||||
}
|
||||
if a == nil {
|
||||
continue
|
||||
}
|
||||
if left := withoutUsers(a, drop); len(left) != len(a) {
|
||||
if err := setACL(fd, name, left); err != nil {
|
||||
return changed, false, true, err
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed, false, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// dropThenKeep is drop then keep, on one walk: a user whose use changed, or one gone beside one come.
|
||||
func dropThenKeep(drop map[uint32]bool, users map[uint32]bool) visitFunc {
|
||||
d, k := dropVisit(drop), keepVisit(users)
|
||||
return func(fd int, st unix.Stat_t, folder bool) (bool, bool, bool, error) {
|
||||
c1, _, _, err := d(fd, st, folder)
|
||||
if err != nil {
|
||||
return c1, false, false, err
|
||||
}
|
||||
c2, n, u, err := k(fd, st, folder)
|
||||
return c1 || c2, n, u, err
|
||||
}
|
||||
}
|
||||
|
||||
// folderEmpty is whether the folder open on fd holds nothing yet: the data the mesh may keep without asking.
|
||||
// folderEmpty is whether the folder open on fd holds nothing yet: a root the mesh may keep without asking.
|
||||
func folderEmpty(fd int) (bool, error) {
|
||||
dup, err := unix.Dup(fd)
|
||||
if err != nil {
|
||||
@@ -415,20 +284,11 @@ func folderEmpty(fd int) (bool, error) {
|
||||
return false, fmt.Errorf("the folder could not be read: %v", err)
|
||||
}
|
||||
|
||||
// keepTop keeps users' entries on the kept folder itself (ADR 0325 §5): what every apply does, beside the walks.
|
||||
func keepTop(path string, users map[uint32]bool) (changed bool, err error) {
|
||||
w, fd, st, err := openKept(path, WalkLimits{Entries: 1, Time: time.Minute}, machineStats, keepVisit(users))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
changed, _, _, err = w.visit(fd, st, true)
|
||||
return changed, err
|
||||
}
|
||||
|
||||
// FolderFacts is what the engine reads of a folder before keeping it: its owner, group and whether it holds data.
|
||||
// FolderFacts is what the engine reads of a library's root before keeping it: its owner, group, mode and whether
|
||||
// it holds data.
|
||||
type FolderFacts struct {
|
||||
UID, GID uint32
|
||||
Mode uint32
|
||||
Empty bool
|
||||
}
|
||||
|
||||
@@ -455,87 +315,105 @@ func readFolder(path string) (FolderFacts, error) {
|
||||
if err != nil {
|
||||
return FolderFacts{}, err
|
||||
}
|
||||
return FolderFacts{UID: st.Uid, GID: st.Gid, Empty: empty}, nil
|
||||
return FolderFacts{UID: st.Uid, GID: st.Gid, Mode: uint32(st.Mode) & 0o7777, Empty: empty}, nil
|
||||
}
|
||||
|
||||
// CreatePath makes a missing accessed folder (ADR 0325 §9): each missing level made from the nearest existing
|
||||
// ancestor down, each with that ancestor's owner, group and mode. No link is followed (the ancestor is reached as
|
||||
// a mesh directory is, and each level is opened with O_NOFOLLOW), no level is made on another filesystem, no file
|
||||
// is made, and nothing that was there is changed: a level that appears meanwhile stops it. The caller has judged
|
||||
// that the ancestor is on the placed filesystem and not root's. How many levels it made.
|
||||
func CreatePath(path string) (int, error) {
|
||||
return createPath(path, machineStats)
|
||||
}
|
||||
|
||||
func createPath(path string, stats walkStats) (int, error) {
|
||||
m, err := NearestAncestor(path)
|
||||
// keepFolders keeps a library's root and each of its requested sub-paths — the folders themselves, never what is
|
||||
// in them — in the group gid with group read, write and enter and the setgid bit (ADR 0328 §6). A sub-path that is
|
||||
// missing is made, each level with the root's owner, the group, and the root's owner's and others' bits beside
|
||||
// group rwx and setgid (§9). No link is followed (the root is reached as a mesh directory is, and every level below
|
||||
// it by O_NOFOLLOW), nothing is made or changed on another filesystem, and the owner and the owner's and others'
|
||||
// bits of a folder that was there are never changed. How many folders it changed and made.
|
||||
func keepFolders(root string, paths []string, gid int) (changed, made int, err error) {
|
||||
_, fd, rst, err := openRoot(root, WalkLimits{Entries: 1, Time: time.Minute}, machineStats, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("the folder cannot be made: %v; nothing was changed", err)
|
||||
return 0, 0, err
|
||||
}
|
||||
if !m.Info.IsDir() {
|
||||
return 0, errors.New("its nearest existing parent is not a folder (a file or a link), and a folder is " +
|
||||
"made only below a folder; nothing was changed")
|
||||
}
|
||||
mw := &meshWalk{want: m.Ancestor}
|
||||
fd, err := mw.open()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("the nearest existing parent is not walked: %v; nothing was changed", err)
|
||||
}
|
||||
if mw.At != m.Ancestor {
|
||||
unix.Close(fd)
|
||||
return 0, errors.New("the nearest existing parent is reached through a link, and nothing is made " +
|
||||
"through one; nothing was changed")
|
||||
}
|
||||
var anc unix.Stat_t
|
||||
if err := stats.fd(fd, &anc); err != nil {
|
||||
unix.Close(fd)
|
||||
return 0, fmt.Errorf("the nearest existing parent cannot be read: %v; nothing was changed", err)
|
||||
}
|
||||
if why := rootOwned(anc.Uid, anc.Gid); why != "" {
|
||||
unix.Close(fd)
|
||||
return 0, fmt.Errorf("its nearest existing parent is %s, and nothing is made under root's data "+
|
||||
"(novox/hq ADR 0325 §12); nothing was changed", why)
|
||||
}
|
||||
mode := uint32(anc.Mode) & 0o7777
|
||||
made := 0
|
||||
for _, name := range m.Levels {
|
||||
// Made for its maker alone (the engine, as root), so it is nobody else's until it is the ancestor's.
|
||||
if err := unix.Mkdirat(fd, name, 0o700); err != nil {
|
||||
unix.Close(fd)
|
||||
if errors.Is(err, unix.EEXIST) {
|
||||
return made, errors.New("a level of the folder appeared while it was made, and nothing that is " +
|
||||
"there is changed; it stopped there")
|
||||
}
|
||||
return made, fmt.Errorf("a level could not be made: %v", err)
|
||||
defer unix.Close(fd)
|
||||
keep := func(fd int, st unix.Stat_t) error {
|
||||
mode := uint32(st.Mode) & 0o7777
|
||||
want := mode | 0o070 | unix.S_ISGID
|
||||
if int(st.Gid) == gid && want == mode {
|
||||
return nil
|
||||
}
|
||||
next, err := unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
unix.Close(fd)
|
||||
if int(st.Gid) != gid {
|
||||
if err := unix.Fchown(fd, -1, gid); err != nil {
|
||||
return fmt.Errorf("a folder's group could not be set: %v", err)
|
||||
}
|
||||
}
|
||||
if err := unix.Fchmod(fd, want); err != nil {
|
||||
return fmt.Errorf("a folder's mode could not be set: %v", err)
|
||||
}
|
||||
changed++
|
||||
return nil
|
||||
}
|
||||
if err := keep(fd, rst); err != nil {
|
||||
return changed, made, err
|
||||
}
|
||||
madeMode := uint32(rst.Mode)&0o0707 | 0o070 | unix.S_ISGID
|
||||
for _, p := range paths {
|
||||
n, err := keepPath(fd, rst, p, gid, madeMode, keep)
|
||||
made += n
|
||||
if err != nil {
|
||||
return made, fmt.Errorf("a level made could not be opened: %v", err)
|
||||
return changed, made, err
|
||||
}
|
||||
fd = next
|
||||
made++
|
||||
}
|
||||
return changed, made, nil
|
||||
}
|
||||
|
||||
// keepPath walks one sub-path down from the root open on rootFd, making each missing level, and keeps the last.
|
||||
func keepPath(rootFd int, rst unix.Stat_t, p string, gid int, madeMode uint32,
|
||||
keep func(int, unix.Stat_t) error) (int, error) {
|
||||
made := 0
|
||||
cur, err := unix.Dup(rootFd)
|
||||
if err != nil {
|
||||
return made, err
|
||||
}
|
||||
defer func() { unix.Close(cur) }()
|
||||
const dirFlags = unix.O_RDONLY | unix.O_DIRECTORY | unix.O_NOFOLLOW | unix.O_CLOEXEC
|
||||
levels := strings.Split(p, "/")
|
||||
for i, name := range levels {
|
||||
next, err := unix.Openat(cur, name, dirFlags, 0)
|
||||
if errors.Is(err, unix.ENOENT) {
|
||||
// Made for its maker alone (the engine, as root), so it is nobody else's until it is the root's.
|
||||
if mkErr := unix.Mkdirat(cur, name, 0o700); mkErr != nil && !errors.Is(mkErr, unix.EEXIST) {
|
||||
return made, fmt.Errorf("a level of the path %s could not be made: %v", p, mkErr)
|
||||
}
|
||||
if next, err = unix.Openat(cur, name, dirFlags, 0); err == nil {
|
||||
if err = unix.Fchown(next, int(rst.Uid), gid); err == nil {
|
||||
err = unix.Fchmod(next, madeMode)
|
||||
}
|
||||
if err != nil {
|
||||
unix.Close(next)
|
||||
return made, fmt.Errorf("a level of the path %s made could not be given the root's owner "+
|
||||
"and the library's group: %v", p, err)
|
||||
}
|
||||
made++
|
||||
}
|
||||
}
|
||||
if errors.Is(err, unix.ELOOP) || errors.Is(err, unix.ENOTDIR) {
|
||||
return made, fmt.Errorf("the path %s is not a folder below the root (a link, or a file), and the "+
|
||||
"engine keeps no path through one", p)
|
||||
}
|
||||
if err != nil {
|
||||
return made, fmt.Errorf("the path %s could not be opened: %v", p, err)
|
||||
}
|
||||
unix.Close(cur)
|
||||
cur = next
|
||||
var st unix.Stat_t
|
||||
if err := stats.fd(fd, &st); err != nil {
|
||||
unix.Close(fd)
|
||||
return made, fmt.Errorf("a level made could not be read: %v", err)
|
||||
if err := unix.Fstat(cur, &st); err != nil {
|
||||
return made, err
|
||||
}
|
||||
if uint64(st.Dev) != uint64(anc.Dev) {
|
||||
unix.Close(fd)
|
||||
return made, errors.New("a level made is on another filesystem than its parent, and it stopped there")
|
||||
if st.Dev != rst.Dev {
|
||||
return made, fmt.Errorf("the path %s lies on another filesystem than the root, and the engine keeps "+
|
||||
"nothing there", p)
|
||||
}
|
||||
if st.Uid != anc.Uid || st.Gid != anc.Gid {
|
||||
if err := unix.Fchown(fd, int(anc.Uid), int(anc.Gid)); err != nil {
|
||||
unix.Close(fd)
|
||||
return made, fmt.Errorf("a level made could not be given its parent's owner: %v", err)
|
||||
if i == len(levels)-1 {
|
||||
if err := keep(cur, st); err != nil {
|
||||
return made, err
|
||||
}
|
||||
}
|
||||
if err := unix.Fchmod(fd, mode); err != nil {
|
||||
unix.Close(fd)
|
||||
return made, fmt.Errorf("a level made could not be given its parent's mode: %v", err)
|
||||
}
|
||||
}
|
||||
unix.Close(fd)
|
||||
return made, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user