agent v0.87.0: SystemDisks device-mapper walk — legacy-boot hosts get a working drive wizard (IA finding 2, MEDIUM)
Operator ruling 2026-07-13: walk the root's backing device through /sys/block/<dev>/slaves recursively down to physical disks (dm AND md; topology, never VG names); those + any mounted-ESP holder are system; the all-system fail-safe returns to being the WALK-FAILURE error case only. SAFETY DIRECTION: a root-backing disk classified candidate is made impossible — per-branch conservatism (any unresolvable slave fails the WHOLE walk -> ok=false -> the unchanged all-system path). - physicalDisksOf/walkSlaves in role.go (symlink canon -> wholeDiskOf fast path -> recursive slaves walk; cycle/depth guard; non-/dev sources unwalkable) - HostReader.BlockSlaves(name) — the ONE new seam method; ProcHostReader reads /sys/block/<name>/slaves; all four test fakes mirror it - role_walk_test.go: signature table (root-backing disk ALWAYS system across legacy-LVM / md-raid / EFI+raw / EFI+LVM / nested dm-on-md — NEVER weaken) + dead-wizard-lives + dangling-slave fail-safe (real sysKnown=false path) + cycle + empty-slaves; red-proofs A/B/D run->fail->revert (recorded in REPORT) - §3 spike transcripts (drill legacy: dm-1->sda3->sda; felhom-pve: ESP+walk agree on sda -> byte-identical regression); caller audit: none relied on all-system as a feature - format/mkfs paths, data-bearing guards, wizard UI untouched
This commit is contained in:
@@ -29,6 +29,11 @@ type HostReader interface {
|
||||
// Removable reads the backing disk's removable flag (true => a USB/hot-plug device).
|
||||
// ok=false when it cannot be determined.
|
||||
Removable(device string) (removable bool, ok bool)
|
||||
// BlockSlaves lists the component (slave) device names beneath /sys/block/<name>/slaves —
|
||||
// non-empty for VIRTUAL block devices (device-mapper dm-*, md-raid md*), empty for a
|
||||
// physical disk (the dir exists but has no entries). hasDir=false when <name> has no
|
||||
// /sys/block entry at all (partitions, unknown names). Root-free (sysfs is world-readable).
|
||||
BlockSlaves(name string) (slaves []string, hasDir bool)
|
||||
}
|
||||
|
||||
// Mount is one active-mount-table entry.
|
||||
@@ -156,6 +161,25 @@ func (r *ProcHostReader) Removable(device string) (bool, bool) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
// BlockSlaves lists /sys/block/<name>/slaves. Every whole device in /sys/block carries the
|
||||
// slaves/ directory (empty on physical disks); partitions have no /sys/block entry at all —
|
||||
// they resolve via the partition regexes in role.go, never through here.
|
||||
func (r *ProcHostReader) BlockSlaves(name string) ([]string, bool) {
|
||||
name = filepath.Base(strings.TrimSpace(name))
|
||||
if name == "" || name == "." || name == "/" {
|
||||
return nil, false
|
||||
}
|
||||
entries, err := os.ReadDir(filepath.Join(r.sysBlockDir(), name, "slaves"))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
out := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
out = append(out, e.Name())
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// parentDisk maps a device path (possibly a partition like /dev/sdb1 or /dev/nvme0n1p2)
|
||||
// to its parent disk's sysfs name (sdb / nvme0n1). It uses /sys/class/block/<name>, whose
|
||||
// real path ends in .../<disk>/<partition> for a partition and .../<disk> for a whole disk.
|
||||
|
||||
Reference in New Issue
Block a user