| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 1 | package gpt |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "crypto/sha256" |
| 6 | "io" |
| 7 | "os" |
| Jan Schär | e479eee | 2024-08-21 16:01:39 +0200 | [diff] [blame] | 8 | "strings" |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 9 | "testing" |
| 10 | |
| 11 | "github.com/google/uuid" |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 12 | |
| Tim Windelschmidt | 9f21f53 | 2024-05-07 15:14:20 +0200 | [diff] [blame] | 13 | "source.monogon.dev/osbase/blockdev" |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 14 | ) |
| 15 | |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 16 | func TestFreeSpaces(t *testing.T) { |
| 17 | cases := []struct { |
| 18 | name string |
| 19 | parts []*Partition |
| 20 | expected [][2]int64 |
| 21 | expectedOverlap bool |
| 22 | }{ |
| 23 | {"Empty", []*Partition{}, [][2]int64{{34, 2015}}, false}, |
| 24 | {"OnePart", []*Partition{ |
| 25 | {Type: PartitionTypeEFISystem, FirstBlock: 200, LastBlock: 1499}, |
| 26 | }, [][2]int64{ |
| 27 | {34, 200}, |
| 28 | {1500, 2015}, |
| 29 | }, false}, |
| 30 | {"TwoOverlappingParts", []*Partition{ |
| 31 | {Type: PartitionTypeEFISystem, FirstBlock: 200, LastBlock: 1499}, |
| 32 | {Type: PartitionTypeEFISystem, FirstBlock: 1000, LastBlock: 1999}, |
| 33 | }, [][2]int64{ |
| 34 | {34, 200}, |
| 35 | {2000, 2015}, |
| 36 | }, true}, |
| 37 | {"Full", []*Partition{ |
| 38 | {Type: PartitionTypeEFISystem, FirstBlock: 34, LastBlock: 999}, |
| 39 | {Type: PartitionTypeEFISystem, FirstBlock: 1000, LastBlock: 2014}, |
| 40 | }, [][2]int64{}, false}, |
| 41 | {"TwoSpacedParts", []*Partition{ |
| 42 | {Type: PartitionTypeEFISystem, FirstBlock: 500, LastBlock: 899}, |
| 43 | {Type: PartitionTypeEFISystem, FirstBlock: 1200, LastBlock: 1799}, |
| 44 | }, [][2]int64{ |
| 45 | {34, 500}, |
| 46 | {900, 1200}, |
| 47 | {1800, 2015}, |
| 48 | }, false}, |
| 49 | } |
| 50 | |
| 51 | // Partitions are created manually as AddPartition calls FreeSpaces itself, |
| 52 | // which makes the test unreliable as well as making failures very hard to |
| 53 | // debug. |
| 54 | for _, c := range cases { |
| 55 | t.Run(c.name, func(t *testing.T) { |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 56 | d := blockdev.MustNewMemory(512, 2048) // 1MiB |
| 57 | g, err := New(d) |
| 58 | if err != nil { |
| 59 | panic(err) |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 60 | } |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 61 | g.Partitions = c.parts |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 62 | fs, overlap, err := g.GetFreeSpaces() |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | if overlap != c.expectedOverlap { |
| 67 | t.Errorf("expected overlap %v, got %v", c.expectedOverlap, overlap) |
| 68 | } |
| 69 | if len(fs) != len(c.expected) { |
| 70 | t.Fatalf("expected %v, got %v", c.expected, fs) |
| 71 | } |
| 72 | for i := range fs { |
| 73 | if fs[i] != c.expected[i] { |
| 74 | t.Errorf("free space mismatch at pos %d: got [%d, %d), expected [%d, %d)", i, fs[i][0], fs[i][1], c.expected[i][0], c.expected[i][1]) |
| 75 | } |
| 76 | } |
| 77 | }) |
| 78 | } |
| 79 | } |
| 80 | |
| Jan Schär | e479eee | 2024-08-21 16:01:39 +0200 | [diff] [blame] | 81 | func TestAddPartition(t *testing.T) { |
| 82 | if os.Getenv("IN_KTEST") == "true" { |
| 83 | t.Skip("In ktest") |
| 84 | } |
| 85 | cases := []struct { |
| 86 | name string |
| 87 | parts []*Partition |
| 88 | addSize int64 |
| 89 | addOptions []AddOption |
| 90 | expectErr string |
| 91 | expectParts []*Partition |
| 92 | }{ |
| 93 | { |
| 94 | name: "empty-basic", |
| 95 | addSize: 9 * 512, |
| 96 | expectParts: []*Partition{{Name: "added", FirstBlock: 2048, LastBlock: 2048 + 9 - 1}}, |
| 97 | }, |
| 98 | { |
| 99 | name: "empty-fill", |
| 100 | addSize: -1, |
| Jan Schär | 02d7217 | 2024-09-02 17:47:27 +0200 | [diff] [blame] | 101 | expectParts: []*Partition{{Name: "added", FirstBlock: 2048, LastBlock: 5*2048 - 2048 - 1}}, |
| Jan Schär | e479eee | 2024-08-21 16:01:39 +0200 | [diff] [blame] | 102 | }, |
| 103 | { |
| 104 | name: "empty-end", |
| 105 | addSize: 9 * 512, |
| 106 | addOptions: []AddOption{WithPreferEnd()}, |
| 107 | expectParts: []*Partition{{Name: "added", FirstBlock: 4 * 2048, LastBlock: 4*2048 + 9 - 1}}, |
| 108 | }, |
| 109 | { |
| 110 | name: "empty-align0", |
| 111 | addSize: 9 * 512, |
| 112 | addOptions: []AddOption{WithAlignment(0)}, |
| 113 | expectErr: "must be positive", |
| 114 | }, |
| 115 | { |
| 116 | name: "empty-align512", |
| 117 | addSize: 9 * 512, |
| 118 | addOptions: []AddOption{WithAlignment(512)}, |
| 119 | expectParts: []*Partition{{Name: "added", FirstBlock: 2 + 16384/512, LastBlock: 2 + 16384/512 + 9 - 1}}, |
| 120 | }, |
| 121 | { |
| 122 | name: "empty-zero-size", |
| 123 | addSize: 0, |
| 124 | expectErr: "must be positive", |
| 125 | }, |
| 126 | { |
| 127 | name: "full-fill", |
| 128 | parts: []*Partition{{Name: "full", FirstBlock: 2048, LastBlock: 5*2048 - 16384/512 - 2}}, |
| 129 | addSize: -1, |
| 130 | expectErr: "no free space", |
| 131 | expectParts: []*Partition{{Name: "full", FirstBlock: 2048, LastBlock: 5*2048 - 16384/512 - 2}}, |
| 132 | }, |
| 133 | { |
| 134 | name: "haveone-basic", |
| 135 | parts: []*Partition{{Name: "one", FirstBlock: 2048, LastBlock: 2048 + 5}}, |
| 136 | addSize: 9 * 512, |
| 137 | expectParts: []*Partition{ |
| 138 | {Name: "one", FirstBlock: 2048, LastBlock: 2048 + 5}, |
| 139 | {Name: "added", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 9 - 1}, |
| 140 | }, |
| 141 | }, |
| 142 | { |
| 143 | name: "havemiddle-basic", |
| 144 | parts: []*Partition{{Name: "middle", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 5}}, |
| 145 | addSize: 9 * 512, |
| 146 | expectParts: []*Partition{ |
| 147 | {Name: "middle", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 5}, |
| 148 | {Name: "added", FirstBlock: 2048, LastBlock: 2048 + 9 - 1}, |
| 149 | }, |
| 150 | }, |
| 151 | { |
| 152 | name: "havemiddle-end", |
| 153 | parts: []*Partition{{Name: "middle", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 5}}, |
| 154 | addSize: 9 * 512, |
| 155 | addOptions: []AddOption{WithPreferEnd()}, |
| 156 | expectParts: []*Partition{ |
| 157 | {Name: "middle", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 5}, |
| 158 | {Name: "added", FirstBlock: 4 * 2048, LastBlock: 4*2048 + 9 - 1}, |
| 159 | }, |
| 160 | }, |
| 161 | { |
| 162 | name: "end-aligned", |
| 163 | parts: []*Partition{{Name: "endalign", FirstBlock: 4 * 2048, LastBlock: 4*2048 + 8}}, |
| 164 | addSize: 2048 * 512, |
| 165 | addOptions: []AddOption{WithPreferEnd()}, |
| 166 | expectParts: []*Partition{ |
| 167 | {Name: "endalign", FirstBlock: 4 * 2048, LastBlock: 4*2048 + 8}, |
| 168 | {Name: "added", FirstBlock: 3 * 2048, LastBlock: 4*2048 - 1}, |
| 169 | }, |
| 170 | }, |
| 171 | { |
| 172 | name: "emptyslots-basic", |
| 173 | parts: []*Partition{ |
| 174 | {Name: "one", FirstBlock: 2048, LastBlock: 2048}, |
| 175 | nil, nil, |
| 176 | {Name: "two", FirstBlock: 2048 + 1, LastBlock: 2048 + 1}, |
| 177 | }, |
| 178 | addSize: 9 * 512, |
| 179 | expectParts: []*Partition{ |
| 180 | {Name: "one", FirstBlock: 2048, LastBlock: 2048}, |
| 181 | {Name: "added", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 9 - 1}, |
| 182 | nil, |
| 183 | {Name: "two", FirstBlock: 2048 + 1, LastBlock: 2048 + 1}, |
| 184 | }, |
| 185 | }, |
| 186 | { |
| 187 | name: "emptyslots-keep", |
| 188 | parts: []*Partition{ |
| 189 | {Name: "one", FirstBlock: 2048, LastBlock: 2048}, |
| 190 | nil, nil, |
| 191 | {Name: "two", FirstBlock: 2048 + 1, LastBlock: 2048 + 1}, |
| 192 | }, |
| 193 | addSize: 9 * 512, |
| 194 | addOptions: []AddOption{WithKeepEmptyEntries()}, |
| 195 | expectParts: []*Partition{ |
| 196 | {Name: "one", FirstBlock: 2048, LastBlock: 2048}, |
| 197 | nil, nil, |
| 198 | {Name: "two", FirstBlock: 2048 + 1, LastBlock: 2048 + 1}, |
| 199 | {Name: "added", FirstBlock: 2 * 2048, LastBlock: 2*2048 + 9 - 1}, |
| 200 | }, |
| 201 | }, |
| 202 | } |
| 203 | |
| 204 | for _, c := range cases { |
| 205 | t.Run(c.name, func(t *testing.T) { |
| 206 | for _, part := range c.parts { |
| 207 | if part != nil { |
| 208 | part.Type = PartitionTypeEFISystem |
| 209 | } |
| 210 | } |
| 211 | addPart := &Partition{Name: "added", Type: PartitionTypeEFISystem} |
| 212 | d := blockdev.MustNewMemory(512, 5*2048) // 5MiB |
| 213 | g, err := New(d) |
| 214 | if err != nil { |
| 215 | panic(err) |
| 216 | } |
| 217 | g.Partitions = c.parts |
| 218 | err = g.AddPartition(addPart, c.addSize, c.addOptions...) |
| 219 | if (err == nil) != (c.expectErr == "") || (err != nil && !strings.Contains(err.Error(), c.expectErr)) { |
| 220 | t.Errorf("expected %q, got %v", c.expectErr, err) |
| 221 | } |
| 222 | _, overlap, err := g.GetFreeSpaces() |
| 223 | if err != nil { |
| 224 | t.Fatal(err) |
| 225 | } |
| 226 | if overlap { |
| 227 | t.Errorf("partitions overlap") |
| 228 | } |
| 229 | if len(g.Partitions) != len(c.expectParts) { |
| 230 | t.Fatalf("expected %+v, got %+v", c.expectParts, g.Partitions) |
| 231 | } |
| 232 | for i := range g.Partitions { |
| 233 | gotPart, wantPart := g.Partitions[i], c.expectParts[i] |
| 234 | if (gotPart == nil) != (wantPart == nil) { |
| 235 | t.Errorf("partition %d: got %+v, expected %+v", i, gotPart, wantPart) |
| 236 | } |
| 237 | if gotPart == nil || wantPart == nil { |
| 238 | continue |
| 239 | } |
| 240 | if gotPart.Name != wantPart.Name { |
| 241 | t.Errorf("partition %d: got Name %q, expected %q", i, gotPart.Name, wantPart.Name) |
| 242 | } |
| 243 | if gotPart.FirstBlock != wantPart.FirstBlock { |
| 244 | t.Errorf("partition %d: got FirstBlock %d, expected %d", i, gotPart.FirstBlock, wantPart.FirstBlock) |
| 245 | } |
| 246 | if gotPart.LastBlock != wantPart.LastBlock { |
| 247 | t.Errorf("partition %d: got LastBlock %d, expected %d", i, gotPart.LastBlock, wantPart.LastBlock) |
| 248 | } |
| 249 | } |
| 250 | }) |
| 251 | } |
| 252 | } |
| 253 | |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 254 | func TestRoundTrip(t *testing.T) { |
| 255 | if os.Getenv("IN_KTEST") == "true" { |
| 256 | t.Skip("In ktest") |
| 257 | } |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 258 | d := blockdev.MustNewMemory(512, 2048) // 1 MiB |
| 259 | |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 260 | g := Table{ |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 261 | ID: uuid.NewSHA1(uuid.Nil, []byte("test")), |
| 262 | BootCode: []byte("just some test code"), |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 263 | Partitions: []*Partition{ |
| 264 | nil, |
| 265 | // This emoji is very complex and exercises UTF16 surrogate encoding |
| 266 | // and composing. |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 267 | {Name: "Test 🏃♂️", FirstBlock: 10, LastBlock: 19, Type: PartitionTypeEFISystem, ID: uuid.NewSHA1(uuid.Nil, []byte("test1")), Attributes: AttrNoBlockIOProto}, |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 268 | nil, |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 269 | {Name: "Test2", FirstBlock: 20, LastBlock: 49, Type: PartitionTypeEFISystem, ID: uuid.NewSHA1(uuid.Nil, []byte("test2")), Attributes: 0}, |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 270 | }, |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 271 | b: d, |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 272 | } |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 273 | if err := g.Write(); err != nil { |
| 274 | t.Fatalf("Error while writing Table: %v", err) |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 275 | } |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 276 | |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 277 | originalHash := sha256.New() |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 278 | sr1 := io.NewSectionReader(d, 0, d.BlockSize()*d.BlockCount()) |
| 279 | if _, err := io.CopyBuffer(originalHash, sr1, make([]byte, d.OptimalBlockSize())); err != nil { |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 280 | panic(err) |
| 281 | } |
| 282 | |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 283 | g2, err := Read(d) |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 284 | if err != nil { |
| 285 | t.Fatalf("Failed to read back GPT: %v", err) |
| 286 | } |
| 287 | if g2.ID != g.ID { |
| 288 | t.Errorf("Disk UUID changed when reading back: %v", err) |
| 289 | } |
| 290 | // Destroy primary GPT |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 291 | d.Zero(1*d.BlockSize(), 5*d.BlockSize()) |
| 292 | g3, err := Read(d) |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 293 | if err != nil { |
| 294 | t.Fatalf("Failed to read back GPT with primary GPT destroyed: %v", err) |
| 295 | } |
| 296 | if g3.ID != g.ID { |
| 297 | t.Errorf("Disk UUID changed when reading back: %v", err) |
| 298 | } |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 299 | if err := g3.Write(); err != nil { |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 300 | t.Fatalf("Error while writing back GPT: %v", err) |
| 301 | } |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 302 | rewrittenHash := sha256.New() |
| Lorenz Brun | ad13188 | 2023-06-28 16:42:20 +0200 | [diff] [blame] | 303 | sr2 := io.NewSectionReader(d, 0, d.BlockSize()*d.BlockCount()) |
| 304 | if _, err := io.CopyBuffer(rewrittenHash, sr2, make([]byte, d.OptimalBlockSize())); err != nil { |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 305 | panic(err) |
| 306 | } |
| Tim Windelschmidt | 4beb06e | 2024-04-17 00:56:53 +0200 | [diff] [blame] | 307 | if !bytes.Equal(originalHash.Sum(nil), rewrittenHash.Sum(nil)) { |
| 308 | t.Errorf("Write/Read/Write test was not reproducible: %x != %x", originalHash.Sum(nil), rewrittenHash.Sum(nil)) |
| Lorenz Brun | ee17d83 | 2022-10-18 12:02:45 +0000 | [diff] [blame] | 309 | } |
| 310 | } |