Media build part 1 (unreleased): real restart (R-1026), media apps on every PRESENT drive via an owned compose override (R-1023), dead-drive check from mountinfo, removal never deletes shared/:ro binds (R-1027), safety list knows userdata (R-1032), no drive question for media apps (R-1031)
gates / gates (push) Successful in 46s
gates / gates (push) Successful in 46s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
//go:build linux
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MountIsShutdown reports whether the TOP mount at path is a dead filesystem — ext4's `shutdown` super option, set when
|
||||
// the device under it went away (a pulled drive). It reads /proc/self/mountinfo only: NO disk I/O, so it never hangs on
|
||||
// the dead device (gates.md: health checks issue no block I/O). A timed `stat` is NOT a substitute: it answered „ok" on
|
||||
// a dead drive from the cache (measured 2026-10-14, `15` §10 K4). Pinned by TestMountIsShutdown_*.
|
||||
func MountIsShutdown(path string) bool {
|
||||
data, err := os.ReadFile(mountinfoPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return mountinfoShutdown(string(data), path)
|
||||
}
|
||||
|
||||
var mountinfoPath = "/proc/self/mountinfo"
|
||||
|
||||
// mountinfoShutdown: the LAST mountinfo line whose mount point is path (the top of a stack) carries `shutdown` in its
|
||||
// super options (the third field after " - ").
|
||||
func mountinfoShutdown(mountinfo, path string) bool {
|
||||
path = filepath.Clean(path)
|
||||
dead := false
|
||||
found := false
|
||||
for _, line := range strings.Split(mountinfo, "\n") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 5 || unescapeMountinfo(f[4]) != path {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
dead = false
|
||||
if i := strings.Index(line, " - "); i >= 0 {
|
||||
tail := strings.Fields(line[i+3:])
|
||||
if len(tail) >= 3 {
|
||||
for _, o := range strings.Split(tail[2], ",") {
|
||||
if o == "shutdown" {
|
||||
dead = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found && dead
|
||||
}
|
||||
|
||||
// unescapeMountinfo undoes mountinfo's octal escapes for space, tab, newline and backslash.
|
||||
func unescapeMountinfo(s string) string {
|
||||
r := strings.NewReplacer(`\040`, " ", `\011`, "\t", `\012`, "\n", `\134`, `\`)
|
||||
return r.Replace(s)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//go:build !linux
|
||||
|
||||
package system
|
||||
|
||||
// MountIsShutdown is linux-only; elsewhere nothing is ever dead.
|
||||
func MountIsShutdown(_ string) bool { return false }
|
||||
@@ -0,0 +1,26 @@
|
||||
//go:build linux
|
||||
|
||||
package system
|
||||
|
||||
import "testing"
|
||||
|
||||
// Lines measured on Tester 1 2026-10-14 (K4): a live ext4, a dead one (`shutdown`), and a returned drive stacked on
|
||||
// top of the dead one — only the TOP mount decides.
|
||||
const k4Mountinfo = `120 99 8:33 / /mnt/felhom-drives/media_a rw,relatime shared:60 master:1 - ext4 /dev/sdc1 rw
|
||||
121 99 8:49 / /mnt/felhom-drives/media_b rw,relatime shared:61 master:2 - ext4 /dev/sdd1 rw,shutdown
|
||||
122 99 8:65 / /mnt/felhom-drives/media_c rw,relatime shared:62 master:3 - ext4 /dev/sde1 rw,shutdown
|
||||
130 122 8:81 / /mnt/felhom-drives/media_c rw,relatime shared:70 master:4 - ext4 /dev/sdf1 rw`
|
||||
|
||||
func TestMountIsShutdown_K4(t *testing.T) {
|
||||
for path, want := range map[string]bool{
|
||||
"/mnt/felhom-drives/media_a": false, // live
|
||||
"/mnt/felhom-drives/media_b": true, // pulled: dead
|
||||
"/mnt/felhom-drives/media_c": false, // came back ON TOP of the dead mount: the top is live
|
||||
"/mnt/felhom-drives/media_c/": false,
|
||||
"/mnt/felhom-drives/nothing": false, // not a mount at all: not „dead"
|
||||
} {
|
||||
if got := mountinfoShutdown(k4Mountinfo, path); got != want {
|
||||
t.Errorf("%s: dead=%v, want %v", path, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user