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)
|
||||
}
|
||||
Reference in New Issue
Block a user