package storage import ( "os" "path/filepath" "runtime" "strings" "testing" ) // The data-bearing partition path binds the disk id + the part number + the fs-UUID (felhom.eu architecture/16 §6, // agent v0.158.0). The wipe re-resolve (localapi antiRetargetResolveExpect) formats only when resolve(id) succeeds AND // DeviceDurableID(that device) == id — the round trip these tests pin, in the three ways a target can move. type partFixture struct{ base, root string } func newPartFixture(t *testing.T) partFixture { t.Helper() if runtime.GOOS == "windows" { t.Skip("symlink fixtures run on Linux") } f := partFixture{base: t.TempDir()} f.root = filepath.Join(f.base, "disk") for _, d := range []string{"sdb", "sdb1", "sdb2", "sdc2"} { if err := os.WriteFile(filepath.Join(f.base, d), []byte("x"), 0o600); err != nil { t.Fatal(err) } } os.MkdirAll(filepath.Join(f.root, "by-id"), 0o755) os.MkdirAll(filepath.Join(f.root, "by-uuid"), 0o755) withDevDiskRoot(t, f.root) return f } func (f partFixture) link(t *testing.T, sub, name, dev string) { t.Helper() p := filepath.Join(f.root, sub, name) os.Remove(p) if err := os.Symlink(filepath.Join(f.base, dev), p); err != nil { t.Fatal(err) } } func (f partFixture) dev(name string) string { return filepath.Join(f.base, name) } func (f partFixture) standard(t *testing.T) { f.link(t, "by-id", "wwn-0x5000aaaa", "sdb") f.link(t, "by-id", "ata-TOSHIBA_MQ04_65NOP-part1", "sdb1") f.link(t, "by-id", "wwn-0x5000aaaa-part1", "sdb1") f.link(t, "by-id", "wwn-0x5000aaaa-part2", "sdb2") f.link(t, "by-uuid", "1111-films", "sdb2") } func roundTrip(id string) (string, bool) { dev, err := ResolveDurableDevice(id) if err != nil { return "", false } again, err := DeviceDurableID(dev) return dev, err == nil && again == id } func TestPartitionWipeID_BindsDiskPartAndFilesystem(t *testing.T) { f := newPartFixture(t) f.standard(t) id, err := DeviceDurableID(f.dev("sdb2")) if err != nil || id != "byid:wwn-0x5000aaaa-part2+fsuuid:1111-films" { t.Fatalf("a part with a filesystem: want disk id + part + fs-UUID, got %q %v", id, err) } if dev, ok := roundTrip(id); !ok || dev != f.dev("sdb2") { t.Fatalf("the unchanged part must round-trip, got %q %v", dev, ok) } // A blank part (no filesystem) and a whole disk keep the plain by-id binding. if id1, _ := DeviceDurableID(f.dev("sdb1")); id1 != "byid:wwn-0x5000aaaa-part1" { t.Fatalf("a blank part: want the plain part link, got %q", id1) } if idDisk, _ := DeviceDurableID(f.dev("sdb")); idDisk != "byid:wwn-0x5000aaaa" { t.Fatalf("a whole disk keeps today's id, got %q", idDisk) } } func TestPartitionWipeID_ReformattedPartRefuses(t *testing.T) { f := newPartFixture(t) f.standard(t) id, _ := DeviceDurableID(f.dev("sdb2")) os.Remove(filepath.Join(f.root, "by-uuid", "1111-films")) f.link(t, "by-uuid", "2222-other", "sdb2") // someone reformatted the part since the household confirmed if _, ok := roundTrip(id); ok { t.Fatalf("a reformatted part must not round-trip (the wipe would hit a different filesystem)") } } func TestPartitionWipeID_LinkMovedToAnotherDiskRefuses(t *testing.T) { f := newPartFixture(t) f.standard(t) id, _ := DeviceDurableID(f.dev("sdb2")) f.link(t, "by-id", "wwn-0x5000aaaa-part2", "sdc2") // a different node took the part link; the fs is still sdb2 if _, err := ResolveDurableDevice(id); err == nil || !strings.Contains(err.Error(), "different devices") { t.Fatalf("the part link and its filesystem on different devices must refuse, got %v", err) } } func TestPartitionWipeID_RefusesAForgedCompound(t *testing.T) { f := newPartFixture(t) f.standard(t) f.link(t, "by-uuid", "3333-whole", "sdb") // a filesystem straight on the whole disk: both halves name sdb for _, id := range []string{"byid:wwn-0x5000aaaa+fsuuid:3333-whole", "byid:wwn-0x5000aaaa-part2+fsuuid:../x"} { if _, err := ResolveDurableDevice(id); err == nil { t.Fatalf("%q must be refused", id) } } }