Files
felhom-controller/controller/internal/system/fsclass.go
T
admin c0f3e12483 v0.117.0: consuming-namespace NAS verification + deploy-view truth (RCA fixes 2+4)
statfs fsclass helper (network/autofs/stub/unknown, fail-open); probe not_network_fs
assertion (stub can never verify — red-proven); deploy-time stub refusal (idle autofs
proceeds — red-proven); distinct stub badge, stub wins over unreachable (unreachable line
byte-identical); deployed select shows stored HDD_PATH (red-proven vs IsDefault-only).
MinAgent unchanged 0.81.0. Gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 21:12:48 +02:00

65 lines
2.7 KiB
Go

package system
import "time"
// Consuming-namespace filesystem classification (RCA AUDIT-nas-cwa-rca-2026-07-11 fix 2).
//
// THE LESSON: the add-time probe, the deploy flow and the dashboard all trusted host-side (agent)
// health for network storage — while the namespace the apps actually consume the path in (the
// controller container's, an rslave copy of the guest's) can silently hold a plain local stub
// after a guest reboot. This classifier answers "what IS this path in THIS process's namespace"
// from statfs f_type, so verification happens where consumption happens.
// FS classes.
const (
// FSClassNetwork: a real network filesystem is mounted here (nfs/cifs/smb2).
FSClassNetwork = "network"
// FSClassAutofs: an idle automount trigger — HEALTHY (first access mounts it). Callers must
// NOT force-mount to "check deeper"; waking the NAS defeats the idle-unmount design.
FSClassAutofs = "autofs"
// FSClassStub: anything else (ext4/tmpfs/plain dir on the system device) — the RCA's silent
// local stub. For a registered network path this is always a defect.
FSClassStub = "stub"
// FSClassUnknown: statfs failed or timed out — no verdict (callers fail open; a wedged share
// is the agent-unreachable branch's business).
FSClassUnknown = "unknown"
)
// classifyFSMagic maps a statfs f_type (linux/magic.h) to an FS class. Pure — unit-tested against
// the magic table. Compared through the unsigned-32 view: the kernel returns f_type as a signed
// long, so CIFS_MAGIC_NUMBER (0xFF534D42) can arrive negative depending on how it was widened.
func classifyFSMagic(ftype int64) string {
u := uint64(ftype) & 0xFFFFFFFF
switch u {
case 0x0187: // AUTOFS_SUPER_MAGIC
return FSClassAutofs
case 0x6969: // NFS_SUPER_MAGIC (all nfs versions)
return FSClassNetwork
case 0xFF534D42: // CIFS_MAGIC_NUMBER
return FSClassNetwork
case 0xFE534D42: // SMB2_MAGIC_NUMBER
return FSClassNetwork
default:
return FSClassStub
}
}
// fsClassTimeout bounds a classification statfs — a mounted-but-dead network fs can block statfs
// for the NFS soft-timeout window; the dashboard/deploy paths must not hang on it.
const fsClassTimeout = 3 * time.Second
// ClassifyPathFSTimeout classifies path in this process's mount namespace, bounded by
// fsClassTimeout. Timeout or statfs error → FSClassUnknown (no verdict — fail open). This is the
// entry point for the deploy-time gate and the dashboard stub badge; the probe child uses the
// unbounded ClassifyPathFS (its whole run is already deadline-bounded by the parent).
func ClassifyPathFSTimeout(path string) string {
ch := make(chan string, 1)
go func() { ch <- ClassifyPathFS(path) }()
select {
case c := <-ch:
return c
case <-time.After(fsClassTimeout):
return FSClassUnknown
}
}