package storage import ( "strings" "testing" ) // Partitions as places (felhom.eu architecture/16 §6, agent v0.158.0): every partition of a free disk with its own // state; Felhom's own drive shown but never offered; anything foreign, layered or a member never listed. const partTree = `{"blockdevices":[ {"name":"sdb","fstype":null,"mountpoint":null,"children":[ {"name":"sdb1","fstype":"ext4","mountpoint":"/mnt/felhom-drives/hdd_1"}, {"name":"sdb2","fstype":null,"mountpoint":null}, {"name":"sdb3","fstype":"ntfs","mountpoint":null}, {"name":"sdb4","fstype":"xfs","mountpoint":null}, {"name":"sdb5","fstype":"ext4","mountpoint":"/mnt/films"}, {"name":"sdb6","fstype":"ext4","mountpoint":"/srv/data"}, {"name":"sdb7","fstype":"LVM2_member","mountpoint":null,"children":[{"name":"vg-lv","fstype":"ext4","mountpoint":null}]}, {"name":"sdb8","fstype":"crypto_LUKS","mountpoint":null} ]}, {"name":"sdc","fstype":null,"mountpoint":null,"children":[{"name":"sdc1","fstype":null,"mountpoint":null}]} ]}` func listParts(owned map[string]bool) map[string]CandidatePartition { got := candidatePartitions([]byte(partTree), "sdb", func(n string) int64 { return int64(len(n)) << 30 }, func(n string) int { return int(n[len(n)-1] - '0') }, func(dev string) (string, bool) { return "uuid-of-" + strings.TrimPrefix(dev, "/dev/"), true }, owned) m := map[string]CandidatePartition{} for _, p := range got { m[p.Device] = p } return m } func TestPartitions_StatesPerPartition(t *testing.T) { m := listParts(map[string]bool{"/mnt/films": true}) want := map[string]string{"/dev/sdb1": PartInUse, "/dev/sdb2": PartEmpty, "/dev/sdb3": PartFilesystem, "/dev/sdb4": PartFilesystem, "/dev/sdb5": PartInUse} if len(m) != len(want) { t.Fatalf("want exactly %d listed partitions, got %+v", len(want), m) } for dev, st := range want { if m[dev].State != st { t.Fatalf("%s: want %s, got %+v", dev, st, m[dev]) } } if m["/dev/sdb2"].DurableID != "" || m["/dev/sdb2"].Mountable { t.Fatalf("an empty part has no attach key: %+v", m["/dev/sdb2"]) } if !m["/dev/sdb4"].Mountable || m["/dev/sdb4"].DurableID != "uuid:uuid-of-sdb4" || m["/dev/sdb4"].Number != 4 { t.Fatalf("an xfs part is usable by its fs-UUID: %+v", m["/dev/sdb4"]) } if m["/dev/sdb3"].Mountable { t.Fatalf("ntfs is a filesystem the agent does not mount as-is: %+v", m["/dev/sdb3"]) } } // A raw /mnt/ mount counts as Felhom's own ONLY when corroborated (felhomOwnedMounts); else it is foreign and // the part is not listed. Positive control: TestPartitions_StatesPerPartition (the same mount, corroborated). func TestPartitions_UncorroboratedMountIsNotOffered(t *testing.T) { m := listParts(nil) if _, ok := m["/dev/sdb5"]; ok { t.Fatalf("an uncorroborated /mnt/films mount must not be listed, got %+v", m["/dev/sdb5"]) } for _, dev := range []string{"/dev/sdb6", "/dev/sdb7", "/dev/sdb8", "/dev/sdc1"} { if _, ok := m[dev]; ok { t.Fatalf("%s must never be listed (foreign mount, LVM, LUKS, another disk)", dev) } } } // The root wrapper refuses any format on a disk with a mounted node (and trusts no „it is Felhom's own" claim), so a // part next to a mounted one is never offered for formatting — only for use. Positive control: an unmounted disk. func TestPartitions_FormattableOnlyWhenNothingOnTheDiskIsMounted(t *testing.T) { m := listParts(map[string]bool{"/mnt/films": true}) for dev, p := range m { if p.Formattable { t.Fatalf("%s: a part of a disk with a mounted part must not be formattable: %+v", dev, p) } } free := `{"blockdevices":[{"name":"sdc","fstype":null,"mountpoint":null,"children":[ {"name":"sdc1","fstype":null,"mountpoint":null},{"name":"sdc2","fstype":"ext4","mountpoint":null}]}]}` got := candidatePartitions([]byte(free), "sdc", func(string) int64 { return 1 << 30 }, func(string) int { return 1 }, func(string) (string, bool) { return "u", true }, nil) if len(got) != 2 || !got[0].Formattable || !got[1].Formattable { t.Fatalf("parts of an unmounted disk are formattable, got %+v", got) } }