Name a filesystem by its UUID, its kernel id or its source, and keep nothing that has no name (hq ADR 0325 fifth review)
NFS and FUSE report a zero f_fsid, so another export or another FUSE mount at the same point was named alike and kept without a warrant; xfs's f_fsid follows the device number. A filesystem is now named by its UUID (its device matched in /dev/disk/by-uuid), else a nonzero f_fsid, else mountinfo's source; one that cannot be named is never kept or taken. Entries left on a filesystem not back within 30 days are forgotten, said once, and the naming read is shared per mount.
This commit is contained in:
+105
-49
@@ -203,6 +203,8 @@ type folderVerdict struct {
|
||||
err error
|
||||
// moved is that the folder's recorded mount is mounted and another holds it: it is judged afresh.
|
||||
moved bool
|
||||
// unnamed is a folder whose filesystem cannot be named: never kept, nor taken.
|
||||
unnamed bool
|
||||
}
|
||||
|
||||
// assessFolder judges a folder as an apply, a look and every walk do (ADR 0325 §7, §9, §12): its disk first, then
|
||||
@@ -244,9 +246,10 @@ func assessFolder(ctx context.Context, path string, rec store.KeptFolder, taken
|
||||
if pl.Away {
|
||||
return folderVerdict{kind: folderWaitDisk, mount: pl.Mount}
|
||||
}
|
||||
// Another filesystem at the same mount point (another pool, another disk) is a move too.
|
||||
// Another filesystem at the same mount point (another pool, another disk) is a move too. A record from an earlier
|
||||
// build names its filesystem another way, and is not compared: the next keep names it this way.
|
||||
here := PlacedID(pl.Mount)
|
||||
moved := pl.Moved || (rec.Placed != "" && rec.Mount == pl.Mount && rec.Placed != here)
|
||||
moved := pl.Moved || (rec.Fs != "" && rec.Mount == pl.Mount && rec.Placed != here)
|
||||
facts, err := readFolder(path)
|
||||
if err != nil {
|
||||
return folderVerdict{err: errors.New("the folder cannot be read: " + pathless(err))}
|
||||
@@ -254,6 +257,11 @@ func assessFolder(ctx context.Context, path string, rec store.KeptFolder, taken
|
||||
if why := rootOwned(facts.UID, facts.GID); why != "" {
|
||||
return folderVerdict{kind: folderRoot, mount: pl.Mount, why: "it is " + why}
|
||||
}
|
||||
// A filesystem that cannot be named is never kept: what is there could be anyone's (§8).
|
||||
if here == "" {
|
||||
return folderVerdict{kind: folderWaitKeep, mount: pl.Mount, moved: moved, unnamed: true,
|
||||
why: "its filesystem cannot be named (no UUID, no filesystem id, no source)"}
|
||||
}
|
||||
// The controller's word that the folder was taken holds for the filesystem the warrant was carried out on: a
|
||||
// folder moved to another is not taken, and asks again (§8).
|
||||
takenHere := taken && (rec.TakenOn == here || (rec.TakenOn == "" && !moved))
|
||||
@@ -279,14 +287,21 @@ func assessFolder(ctx context.Context, path string, rec store.KeptFolder, taken
|
||||
func movedRecord(f store.KeptFolder, mount string) store.KeptFolder {
|
||||
left := f.Left
|
||||
if f.How == store.KeptByWarrant && len(f.Users) > 0 && f.Mount != "" {
|
||||
left = addLeft(left, f.Mount, f.Placed, f.Users)
|
||||
left = addLeft(left, f.Mount, f.Placed, f.Fs, f.Users)
|
||||
}
|
||||
return store.KeptFolder{Path: f.Path, Mount: mount, Placed: PlacedID(mount), Want: f.Want, IDs: f.IDs,
|
||||
placed, fs := placeOf(mount)
|
||||
return store.KeptFolder{Path: f.Path, Mount: mount, Placed: placed, Fs: fs, Want: f.Want, IDs: f.IDs,
|
||||
Act: f.Act, TakenOn: f.TakenOn, Left: left}
|
||||
}
|
||||
|
||||
// placeOf is the name of the filesystem at mount, and what it was made from.
|
||||
func placeOf(mount string) (placed, fs string) {
|
||||
fs = filesystemKey(mount)
|
||||
return placedFrom(mount, fs), fs
|
||||
}
|
||||
|
||||
// addLeft adds users' entries left on mount, once per mount and uid.
|
||||
func addLeft(left []store.KeptLeft, mount, placed string, users []store.KeptUser) []store.KeptLeft {
|
||||
func addLeft(left []store.KeptLeft, mount, placed, fs string, users []store.KeptUser) []store.KeptLeft {
|
||||
for i := range left {
|
||||
if left[i].Mount == mount && left[i].Placed == placed {
|
||||
have := usersOf(left[i].Users)
|
||||
@@ -298,35 +313,57 @@ func addLeft(left []store.KeptLeft, mount, placed string, users []store.KeptUser
|
||||
return left
|
||||
}
|
||||
}
|
||||
return append(left, store.KeptLeft{Mount: mount, Placed: placed, Users: append([]store.KeptUser(nil), users...)})
|
||||
return append(left, store.KeptLeft{Mount: mount, Placed: placed, Fs: fs, Since: time.Now().UTC(),
|
||||
Users: append([]store.KeptUser(nil), users...)})
|
||||
}
|
||||
|
||||
// PlacedID names the filesystem at mount for a wait, with no path in it: the controller asks again when the
|
||||
// folder's filesystem is not the one a warrant was given for. It is built from what stays the same across reboots
|
||||
// for one filesystem — the kernel's filesystem id (statfs f_fsid: ext4 and xfs derive it from the filesystem's
|
||||
// UUID, ZFS from the dataset), the filesystem's type and its mount point — and never from the device name
|
||||
// mountinfo gives as its source, which a reboot may renumber (/dev/sdb1 becoming /dev/sdc1).
|
||||
func PlacedID(mount string) string {
|
||||
if mount == "" {
|
||||
// folder's filesystem is not the one a warrant was given for. Empty when the filesystem cannot be named, and a folder
|
||||
// on it is then never kept (it fails closed, and waits).
|
||||
func PlacedID(mount string) string { return placedFrom(mount, filesystemKey(mount)) }
|
||||
|
||||
// placedFrom is PlacedID from a filesystem key already read.
|
||||
func placedFrom(mount, key string) string {
|
||||
if mount == "" || key == "" {
|
||||
return ""
|
||||
}
|
||||
_, fstype := mountSourceOf(mount)
|
||||
fsid := fsidWithin(mount)
|
||||
sum := sha256.Sum256([]byte(mount + "\x00" + fsid + "\x00" + fstype))
|
||||
sum := sha256.Sum256([]byte(mount + "\x00" + key))
|
||||
return "fs-" + hex.EncodeToString(sum[:6])
|
||||
}
|
||||
|
||||
// fsidWithin is MountFsid within statBound: empty when it does not answer, so a mount that is down is named by its
|
||||
// point and type alone, never held on.
|
||||
func fsidWithin(mount string) string {
|
||||
done := make(chan string, 1)
|
||||
go func() { done <- MountFsid(mount) }()
|
||||
select {
|
||||
case id := <-done:
|
||||
return id
|
||||
case <-time.After(statBound):
|
||||
// filesystemKey is what names the filesystem at mount across reboots, in this order:
|
||||
//
|
||||
// - a filesystem on a block device: its UUID, found by matching the mount's device against the devices in
|
||||
// /dev/disk/by-uuid (never blkid). xfs's statfs id follows the device number, so the UUID comes first;
|
||||
// - else the kernel's filesystem id (statfs f_fsid) when it is not zero: ZFS's from its dataset, btrfs's from its
|
||||
// UUID;
|
||||
// - else mountinfo's source with the filesystem's type: an NFS export's server:/export, a FUSE filesystem's own
|
||||
// source string (both report f_fsid 0).
|
||||
//
|
||||
// Never the mount point and type alone, and never a device's name, which a reboot may renumber. Empty when none of
|
||||
// these answers within statBound: then nothing is kept there. Read once per mount at a time.
|
||||
func filesystemKey(mount string) string {
|
||||
if mount == "" {
|
||||
return ""
|
||||
}
|
||||
v, ok := boundedOnce("fs "+mount, func() any { return readFilesystemKey(mount) })
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
func readFilesystemKey(mount string) string {
|
||||
if u := MountUUID(mount); u != "" {
|
||||
return "uuid:" + u
|
||||
}
|
||||
if id := MountFsid(mount); strings.Trim(id, "0") != "" {
|
||||
return "fsid:" + id
|
||||
}
|
||||
if source, fstype := mountSourceOf(mount); source != "" && source != "none" {
|
||||
return "source:" + fstype + ":" + source
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// statBound is how long a read of a folder may take before it is said not to answer: a hard network mount whose
|
||||
@@ -341,41 +378,57 @@ var errNoAnswer = errors.New("it does not answer: a read of it did not return wi
|
||||
func lstatWithin(path string) (os.FileInfo, error) { return within("lstat", os.Lstat, path) }
|
||||
func statWithin(path string) (os.FileInfo, error) { return within("stat", os.Stat, path) }
|
||||
|
||||
// statRead is one read of a path in flight, shared by everyone who asks while it is: a read that hangs on a dead
|
||||
// mount holds one goroutine per path, never one per apply or look.
|
||||
type statRead struct {
|
||||
// flight is one read in flight, shared by everyone who asks while it is: a read that hangs on a dead mount holds
|
||||
// one goroutine per path, never one per apply or look.
|
||||
type flight struct {
|
||||
done chan struct{}
|
||||
info os.FileInfo
|
||||
err error
|
||||
v any
|
||||
}
|
||||
|
||||
var statsInFlight = struct {
|
||||
var inFlight = struct {
|
||||
sync.Mutex
|
||||
reads map[string]*statRead
|
||||
}{reads: map[string]*statRead{}}
|
||||
reads map[string]*flight
|
||||
}{reads: map[string]*flight{}}
|
||||
|
||||
func within(op string, stat func(string) (os.FileInfo, error), path string) (os.FileInfo, error) {
|
||||
key := op + " " + path
|
||||
statsInFlight.Lock()
|
||||
r, running := statsInFlight.reads[key]
|
||||
// boundedOnce is read's answer within statBound, sharing a read of key already in flight; false when it did not
|
||||
// answer in time.
|
||||
func boundedOnce(key string, read func() any) (any, bool) {
|
||||
inFlight.Lock()
|
||||
f, running := inFlight.reads[key]
|
||||
if !running {
|
||||
r = &statRead{done: make(chan struct{})}
|
||||
statsInFlight.reads[key] = r
|
||||
f = &flight{done: make(chan struct{})}
|
||||
inFlight.reads[key] = f
|
||||
go func() {
|
||||
r.info, r.err = stat(path)
|
||||
statsInFlight.Lock()
|
||||
delete(statsInFlight.reads, key)
|
||||
statsInFlight.Unlock()
|
||||
close(r.done)
|
||||
f.v = read()
|
||||
inFlight.Lock()
|
||||
delete(inFlight.reads, key)
|
||||
inFlight.Unlock()
|
||||
close(f.done)
|
||||
}()
|
||||
}
|
||||
statsInFlight.Unlock()
|
||||
inFlight.Unlock()
|
||||
select {
|
||||
case <-r.done:
|
||||
return r.info, r.err
|
||||
case <-f.done:
|
||||
return f.v, true
|
||||
case <-time.After(statBound):
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func within(op string, stat func(string) (os.FileInfo, error), path string) (os.FileInfo, error) {
|
||||
type read struct {
|
||||
info os.FileInfo
|
||||
err error
|
||||
}
|
||||
v, ok := boundedOnce(op+" "+path, func() any {
|
||||
info, err := stat(path)
|
||||
return read{info, err}
|
||||
})
|
||||
if !ok {
|
||||
return nil, errNoAnswer
|
||||
}
|
||||
r := v.(read)
|
||||
return r.info, r.err
|
||||
}
|
||||
|
||||
// absPath is an absolute path in words: what pathless takes out.
|
||||
@@ -496,7 +549,8 @@ func applyAccessFolder(ctx context.Context, r *declaration.Access, in inputs) (O
|
||||
return out, nil
|
||||
}
|
||||
_ = KeptFolders.Update(path, func(f *store.KeptFolder, _ bool) bool {
|
||||
f.Mount, f.Placed, f.Want, f.IDs = v.mount, PlacedID(v.mount), users, want.ids
|
||||
f.Mount, f.Want, f.IDs = v.mount, users, want.ids
|
||||
f.Placed, f.Fs = placeOf(v.mount)
|
||||
return true
|
||||
})
|
||||
out.Action = "waiting"
|
||||
@@ -524,7 +578,8 @@ func applyAccessFolder(ctx context.Context, r *declaration.Access, in inputs) (O
|
||||
changed, err := keepTop(path, usersOf(users))
|
||||
if errors.Is(err, ErrNoACL) {
|
||||
_ = KeptFolders.Update(path, func(f *store.KeptFolder, _ bool) bool {
|
||||
f.Mount, f.Placed, f.Want = v.mount, PlacedID(v.mount), users
|
||||
f.Mount, f.Want = v.mount, users
|
||||
f.Placed, f.Fs = placeOf(v.mount)
|
||||
return true
|
||||
})
|
||||
out.Action = "waiting"
|
||||
@@ -538,7 +593,8 @@ func applyAccessFolder(ctx context.Context, r *declaration.Access, in inputs) (O
|
||||
var pending string
|
||||
_ = KeptFolders.Update(path, func(f *store.KeptFolder, had bool) bool {
|
||||
first := !f.Kept
|
||||
f.Kept, f.Mount, f.Placed, f.Want, f.IDs = true, v.mount, PlacedID(v.mount), users, want.ids
|
||||
f.Kept, f.Mount, f.Want, f.IDs = true, v.mount, users, want.ids
|
||||
f.Placed, f.Fs = placeOf(v.mount)
|
||||
if v.how == store.KeptByWarrant && f.TakenOn == "" {
|
||||
// The controller says it was taken, and this engine has no record of where: here, now.
|
||||
f.TakenOn = PlacedID(v.mount)
|
||||
|
||||
Reference in New Issue
Block a user