78c894234c
gates / gates (push) Successful in 46s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
57 lines
1.7 KiB
Go
57 lines
1.7 KiB
Go
//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)
|
|
}
|