//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) }