github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/gadget_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package gadget_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 "testing" 30 31 . "gopkg.in/check.v1" 32 "gopkg.in/yaml.v2" 33 34 "github.com/snapcore/snapd/asserts" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/gadget" 37 "github.com/snapcore/snapd/snap/snapfile" 38 "github.com/snapcore/snapd/snap/snaptest" 39 ) 40 41 type gadgetYamlTestSuite struct { 42 dir string 43 gadgetYamlPath string 44 } 45 46 var _ = Suite(&gadgetYamlTestSuite{}) 47 48 var mockGadgetSnapYaml = ` 49 name: canonical-pc 50 type: gadget 51 ` 52 53 var mockGadgetYaml = []byte(` 54 defaults: 55 system: 56 something: true 57 58 connections: 59 - plug: snapid1:plg1 60 slot: snapid2:slot 61 - plug: snapid3:process-control 62 - plug: snapid4:pctl4 63 slot: system:process-control 64 65 volumes: 66 volumename: 67 schema: mbr 68 bootloader: u-boot 69 id: 0C 70 structure: 71 - filesystem-label: system-boot 72 offset: 12345 73 offset-write: 777 74 size: 88888 75 type: 0C 76 filesystem: vfat 77 content: 78 - source: subdir/ 79 target: / 80 unpack: false 81 - source: foo 82 target: / 83 `) 84 85 var mockMultiVolumeGadgetYaml = []byte(` 86 device-tree: frobinator-3000.dtb 87 device-tree-origin: kernel 88 volumes: 89 frobinator-image: 90 bootloader: u-boot 91 schema: mbr 92 structure: 93 - name: system-boot 94 type: 0C 95 filesystem: vfat 96 filesystem-label: system-boot 97 size: 128M 98 role: system-boot 99 content: 100 - source: splash.bmp 101 target: . 102 - name: writable 103 type: 83 104 filesystem: ext4 105 filesystem-label: writable 106 size: 380M 107 role: system-data 108 u-boot-frobinator: 109 structure: 110 - name: u-boot 111 type: bare 112 size: 623000 113 offset: 0 114 content: 115 - image: u-boot.imz 116 `) 117 118 var mockClassicGadgetYaml = []byte(` 119 defaults: 120 system: 121 something: true 122 otheridididididididididididididi: 123 foo: 124 bar: baz 125 `) 126 127 var mockClassicGadgetCoreDefaultsYaml = []byte(` 128 defaults: 129 99T7MUlRhtI3U0QFgl5mXXESAiSwt776: 130 ssh: 131 disable: true 132 `) 133 134 var mockClassicGadgetMultilineDefaultsYaml = []byte(` 135 defaults: 136 system: 137 something: true 138 otheridididididididididididididi: 139 foosnap: 140 multiline: | 141 foo 142 bar 143 `) 144 145 var mockVolumeUpdateGadgetYaml = []byte(` 146 volumes: 147 bootloader: 148 schema: mbr 149 bootloader: u-boot 150 id: 0C 151 structure: 152 - filesystem-label: system-boot 153 offset: 12345 154 offset-write: 777 155 size: 88888 156 type: 0C 157 filesystem: vfat 158 content: 159 - source: subdir/ 160 target: / 161 unpack: false 162 update: 163 edition: 5 164 preserve: 165 - env.txt 166 - config.txt 167 `) 168 169 var gadgetYamlPC = []byte(` 170 volumes: 171 pc: 172 bootloader: grub 173 structure: 174 - name: mbr 175 type: mbr 176 size: 440 177 content: 178 - image: pc-boot.img 179 - name: BIOS Boot 180 type: DA,21686148-6449-6E6F-744E-656564454649 181 size: 1M 182 offset: 1M 183 offset-write: mbr+92 184 content: 185 - image: pc-core.img 186 - name: EFI System 187 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 188 filesystem: vfat 189 filesystem-label: system-boot 190 size: 50M 191 content: 192 - source: grubx64.efi 193 target: EFI/boot/grubx64.efi 194 - source: shim.efi.signed 195 target: EFI/boot/bootx64.efi 196 - source: grub.cfg 197 target: EFI/ubuntu/grub.cfg 198 `) 199 200 var gadgetYamlRPi = []byte(` 201 device-tree: bcm2709-rpi-2-b 202 volumes: 203 pi: 204 schema: mbr 205 bootloader: u-boot 206 structure: 207 - type: 0C 208 filesystem: vfat 209 filesystem-label: system-boot 210 size: 128M 211 content: 212 - source: boot-assets/ 213 target: / 214 `) 215 216 var gadgetYamlLk = []byte(` 217 volumes: 218 volumename: 219 schema: mbr 220 bootloader: lk 221 structure: 222 - name: BOOTIMG1 223 size: 25165824 224 role: system-boot-image 225 type: 27 226 content: 227 - image: boot.img 228 - name: BOOTIMG2 229 size: 25165824 230 role: system-boot-image 231 type: 27 232 - name: snapbootsel 233 size: 131072 234 role: system-boot-select 235 type: B2 236 content: 237 - image: snapbootsel.bin 238 - name: snapbootselbak 239 size: 131072 240 role: system-boot-select 241 type: B2 242 content: 243 - image: snapbootsel.bin 244 - name: writable 245 type: 83 246 filesystem: ext4 247 filesystem-label: writable 248 size: 500M 249 role: system-data 250 `) 251 252 var gadgetYamlLkLegacy = []byte(` 253 volumes: 254 volumename: 255 schema: mbr 256 bootloader: lk 257 structure: 258 - name: BOOTIMG1 259 size: 25165824 260 role: bootimg 261 type: 27 262 content: 263 - image: boot.img 264 - name: BOOTIMG2 265 size: 25165824 266 role: bootimg 267 type: 27 268 - name: snapbootsel 269 size: 131072 270 role: bootselect 271 type: B2 272 content: 273 - image: snapbootsel.bin 274 - name: snapbootselbak 275 size: 131072 276 role: bootselect 277 type: B2 278 content: 279 - image: snapbootsel.bin 280 - name: writable 281 type: 83 282 filesystem: ext4 283 filesystem-label: writable 284 size: 500M 285 role: system-data 286 `) 287 288 func TestRun(t *testing.T) { TestingT(t) } 289 290 func mustParseGadgetSize(c *C, s string) gadget.Size { 291 gs, err := gadget.ParseSize(s) 292 c.Assert(err, IsNil) 293 return gs 294 } 295 296 func mustParseGadgetRelativeOffset(c *C, s string) *gadget.RelativeOffset { 297 grs, err := gadget.ParseRelativeOffset(s) 298 c.Assert(err, IsNil) 299 c.Assert(grs, NotNil) 300 return grs 301 } 302 303 func (s *gadgetYamlTestSuite) SetUpTest(c *C) { 304 dirs.SetRootDir(c.MkDir()) 305 s.dir = c.MkDir() 306 c.Assert(os.MkdirAll(filepath.Join(s.dir, "meta"), 0755), IsNil) 307 s.gadgetYamlPath = filepath.Join(s.dir, "meta", "gadget.yaml") 308 } 309 310 func (s *gadgetYamlTestSuite) TearDownTest(c *C) { 311 dirs.SetRootDir("/") 312 } 313 314 type modelConstraints struct { 315 classic bool 316 systemSeed bool 317 } 318 319 func (m *modelConstraints) Classic() bool { 320 return m.classic 321 } 322 323 func (m *modelConstraints) Grade() asserts.ModelGrade { 324 if m.systemSeed { 325 return asserts.ModelSigned 326 } 327 return asserts.ModelGradeUnset 328 } 329 330 func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissing(c *C) { 331 // if constraints are nil, we allow a missing yaml 332 _, err := gadget.ReadInfo("bogus-path", nil) 333 c.Assert(err, IsNil) 334 335 _, err = gadget.ReadInfo("bogus-path", &modelConstraints{}) 336 c.Assert(err, ErrorMatches, ".*meta/gadget.yaml: no such file or directory") 337 } 338 339 func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicOptional(c *C) { 340 // no meta/gadget.yaml 341 gi, err := gadget.ReadInfo(s.dir, &modelConstraints{classic: true}) 342 c.Assert(err, IsNil) 343 c.Check(gi, NotNil) 344 } 345 346 func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicEmptyIsValid(c *C) { 347 err := ioutil.WriteFile(s.gadgetYamlPath, nil, 0644) 348 c.Assert(err, IsNil) 349 350 ginfo, err := gadget.ReadInfo(s.dir, &modelConstraints{classic: true}) 351 c.Assert(err, IsNil) 352 c.Assert(ginfo, DeepEquals, &gadget.Info{}) 353 } 354 355 func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicOnylDefaultsIsValid(c *C) { 356 err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetYaml, 0644) 357 c.Assert(err, IsNil) 358 359 ginfo, err := gadget.ReadInfo(s.dir, &modelConstraints{classic: true}) 360 c.Assert(err, IsNil) 361 c.Assert(ginfo, DeepEquals, &gadget.Info{ 362 Defaults: map[string]map[string]interface{}{ 363 "system": {"something": true}, 364 // keep this comment so that gofmt 1.10+ does not 365 // realign this, thus breaking our gofmt 1.9 checks 366 "otheridididididididididididididi": {"foo": map[string]interface{}{"bar": "baz"}}, 367 }, 368 }) 369 } 370 371 func (s *gadgetYamlTestSuite) TestFlatten(c *C) { 372 cfg := map[string]interface{}{ 373 "foo": "bar", 374 "some.option": true, 375 "sub": map[string]interface{}{ 376 "option1": true, 377 "option2": map[string]interface{}{ 378 "deep": "2", 379 }, 380 }, 381 } 382 out := map[string]interface{}{} 383 gadget.Flatten("", cfg, out) 384 c.Check(out, DeepEquals, map[string]interface{}{ 385 "foo": "bar", 386 "some.option": true, 387 "sub.option1": true, 388 "sub.option2.deep": "2", 389 }) 390 } 391 392 func (s *gadgetYamlTestSuite) TestCoreConfigDefaults(c *C) { 393 err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetCoreDefaultsYaml, 0644) 394 c.Assert(err, IsNil) 395 396 ginfo, err := gadget.ReadInfo(s.dir, &modelConstraints{classic: true}) 397 c.Assert(err, IsNil) 398 defaults := gadget.SystemDefaults(ginfo.Defaults) 399 c.Check(defaults, DeepEquals, map[string]interface{}{ 400 "ssh.disable": true, 401 }) 402 403 yaml := string(mockClassicGadgetCoreDefaultsYaml) + ` 404 system: 405 something: true 406 ` 407 408 err = ioutil.WriteFile(s.gadgetYamlPath, []byte(yaml), 0644) 409 c.Assert(err, IsNil) 410 ginfo, err = gadget.ReadInfo(s.dir, &modelConstraints{classic: true}) 411 c.Assert(err, IsNil) 412 413 defaults = gadget.SystemDefaults(ginfo.Defaults) 414 c.Check(defaults, DeepEquals, map[string]interface{}{ 415 "something": true, 416 }) 417 } 418 419 func (s *gadgetYamlTestSuite) TestReadGadgetDefaultsMultiline(c *C) { 420 err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetMultilineDefaultsYaml, 0644) 421 c.Assert(err, IsNil) 422 423 ginfo, err := gadget.ReadInfo(s.dir, &modelConstraints{classic: true}) 424 c.Assert(err, IsNil) 425 c.Assert(ginfo, DeepEquals, &gadget.Info{ 426 Defaults: map[string]map[string]interface{}{ 427 "system": {"something": true}, 428 // keep this comment so that gofmt 1.10+ does not 429 // realign this, thus breaking our gofmt 1.9 checks 430 "otheridididididididididididididi": {"foosnap": map[string]interface{}{"multiline": "foo\nbar\n"}}, 431 }, 432 }) 433 } 434 435 func asSizePtr(size gadget.Size) *gadget.Size { 436 gsz := gadget.Size(size) 437 return &gsz 438 } 439 440 func (s *gadgetYamlTestSuite) TestReadGadgetYamlValid(c *C) { 441 err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYaml, 0644) 442 c.Assert(err, IsNil) 443 444 ginfo, err := gadget.ReadInfo(s.dir, nil) 445 c.Assert(err, IsNil) 446 c.Assert(ginfo, DeepEquals, &gadget.Info{ 447 Defaults: map[string]map[string]interface{}{ 448 "system": {"something": true}, 449 }, 450 Connections: []gadget.Connection{ 451 {Plug: gadget.ConnectionPlug{SnapID: "snapid1", Plug: "plg1"}, Slot: gadget.ConnectionSlot{SnapID: "snapid2", Slot: "slot"}}, 452 {Plug: gadget.ConnectionPlug{SnapID: "snapid3", Plug: "process-control"}, Slot: gadget.ConnectionSlot{SnapID: "system", Slot: "process-control"}}, 453 {Plug: gadget.ConnectionPlug{SnapID: "snapid4", Plug: "pctl4"}, Slot: gadget.ConnectionSlot{SnapID: "system", Slot: "process-control"}}, 454 }, 455 Volumes: map[string]gadget.Volume{ 456 "volumename": { 457 Schema: "mbr", 458 Bootloader: "u-boot", 459 ID: "0C", 460 Structure: []gadget.VolumeStructure{ 461 { 462 Label: "system-boot", 463 Offset: asSizePtr(12345), 464 OffsetWrite: mustParseGadgetRelativeOffset(c, "777"), 465 Size: 88888, 466 Type: "0C", 467 Filesystem: "vfat", 468 Content: []gadget.VolumeContent{ 469 { 470 Source: "subdir/", 471 Target: "/", 472 Unpack: false, 473 }, 474 { 475 Source: "foo", 476 Target: "/", 477 Unpack: false, 478 }, 479 }, 480 }, 481 }, 482 }, 483 }, 484 }) 485 } 486 487 func (s *gadgetYamlTestSuite) TestReadMultiVolumeGadgetYamlValid(c *C) { 488 err := ioutil.WriteFile(s.gadgetYamlPath, mockMultiVolumeGadgetYaml, 0644) 489 c.Assert(err, IsNil) 490 491 ginfo, err := gadget.ReadInfo(s.dir, nil) 492 c.Assert(err, IsNil) 493 c.Check(ginfo.Volumes, HasLen, 2) 494 c.Assert(ginfo, DeepEquals, &gadget.Info{ 495 Volumes: map[string]gadget.Volume{ 496 "frobinator-image": { 497 Schema: "mbr", 498 Bootloader: "u-boot", 499 Structure: []gadget.VolumeStructure{ 500 { 501 Name: "system-boot", 502 Role: "system-boot", 503 Label: "system-boot", 504 Size: mustParseGadgetSize(c, "128M"), 505 Filesystem: "vfat", 506 Type: "0C", 507 Content: []gadget.VolumeContent{ 508 { 509 Source: "splash.bmp", 510 Target: ".", 511 }, 512 }, 513 }, 514 { 515 Role: "system-data", 516 Name: "writable", 517 Label: "writable", 518 Type: "83", 519 Filesystem: "ext4", 520 Size: mustParseGadgetSize(c, "380M"), 521 }, 522 }, 523 }, 524 "u-boot-frobinator": { 525 Structure: []gadget.VolumeStructure{ 526 { 527 Name: "u-boot", 528 Type: "bare", 529 Size: 623000, 530 Offset: asSizePtr(0), 531 Content: []gadget.VolumeContent{ 532 { 533 Image: "u-boot.imz", 534 }, 535 }, 536 }, 537 }, 538 }, 539 }, 540 }) 541 } 542 543 func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidBootloader(c *C) { 544 mockGadgetYamlBroken := []byte(` 545 volumes: 546 name: 547 bootloader: silo 548 `) 549 550 err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYamlBroken, 0644) 551 c.Assert(err, IsNil) 552 553 _, err = gadget.ReadInfo(s.dir, nil) 554 c.Assert(err, ErrorMatches, "bootloader must be one of grub, u-boot, android-boot or lk") 555 } 556 557 func (s *gadgetYamlTestSuite) TestReadGadgetYamlEmptyBootloader(c *C) { 558 mockGadgetYamlBroken := []byte(` 559 volumes: 560 name: 561 bootloader: 562 `) 563 564 err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYamlBroken, 0644) 565 c.Assert(err, IsNil) 566 567 _, err = gadget.ReadInfo(s.dir, &modelConstraints{classic: false}) 568 c.Assert(err, ErrorMatches, "bootloader not declared in any volume") 569 } 570 571 func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissingBootloader(c *C) { 572 err := ioutil.WriteFile(s.gadgetYamlPath, nil, 0644) 573 c.Assert(err, IsNil) 574 575 _, err = gadget.ReadInfo(s.dir, &modelConstraints{classic: false}) 576 c.Assert(err, ErrorMatches, "bootloader not declared in any volume") 577 } 578 579 func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidDefaultsKey(c *C) { 580 mockGadgetYamlBroken := []byte(` 581 defaults: 582 foo: 583 x: 1 584 `) 585 586 err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYamlBroken, 0644) 587 c.Assert(err, IsNil) 588 589 _, err = gadget.ReadInfo(s.dir, nil) 590 c.Assert(err, ErrorMatches, `default stanza not keyed by "system" or snap-id: foo`) 591 } 592 593 func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidConnection(c *C) { 594 mockGadgetYamlBroken := ` 595 connections: 596 - @INVALID@ 597 ` 598 tests := []struct { 599 invalidConn string 600 expectedErr string 601 }{ 602 {``, `gadget connection plug cannot be empty`}, 603 {`foo:bar baz:quux`, `(?s).*unmarshal errors:.*`}, 604 {`plug: foo:`, `.*mapping values are not allowed in this context`}, 605 {`plug: ":"`, `.*in gadget connection plug: expected "\(<snap-id>\|system\):name" not ":"`}, 606 {`slot: "foo:"`, `.*in gadget connection slot: expected "\(<snap-id>\|system\):name" not "foo:"`}, 607 {`slot: foo:bar`, `gadget connection plug cannot be empty`}, 608 } 609 610 for _, t := range tests { 611 mockGadgetYamlBroken := strings.Replace(mockGadgetYamlBroken, "@INVALID@", t.invalidConn, 1) 612 613 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(mockGadgetYamlBroken), 0644) 614 c.Assert(err, IsNil) 615 616 _, err = gadget.ReadInfo(s.dir, nil) 617 c.Check(err, ErrorMatches, t.expectedErr) 618 } 619 } 620 621 func (s *gadgetYamlTestSuite) TestReadGadgetYamlVolumeUpdate(c *C) { 622 err := ioutil.WriteFile(s.gadgetYamlPath, mockVolumeUpdateGadgetYaml, 0644) 623 c.Assert(err, IsNil) 624 625 ginfo, err := gadget.ReadInfo(s.dir, nil) 626 c.Check(err, IsNil) 627 c.Assert(ginfo, DeepEquals, &gadget.Info{ 628 Volumes: map[string]gadget.Volume{ 629 "bootloader": { 630 Schema: "mbr", 631 Bootloader: "u-boot", 632 ID: "0C", 633 Structure: []gadget.VolumeStructure{ 634 { 635 Label: "system-boot", 636 Offset: asSizePtr(12345), 637 OffsetWrite: mustParseGadgetRelativeOffset(c, "777"), 638 Size: 88888, 639 Type: "0C", 640 Filesystem: "vfat", 641 Content: []gadget.VolumeContent{{ 642 Source: "subdir/", 643 Target: "/", 644 Unpack: false, 645 }}, 646 Update: gadget.VolumeUpdate{ 647 Edition: 5, 648 Preserve: []string{ 649 "env.txt", 650 "config.txt", 651 }, 652 }, 653 }, 654 }, 655 }, 656 }, 657 }) 658 } 659 660 func (s *gadgetYamlTestSuite) TestReadGadgetYamlVolumeUpdateUnhappy(c *C) { 661 broken := bytes.Replace(mockVolumeUpdateGadgetYaml, []byte("edition: 5"), []byte("edition: borked"), 1) 662 err := ioutil.WriteFile(s.gadgetYamlPath, broken, 0644) 663 c.Assert(err, IsNil) 664 665 _, err = gadget.ReadInfo(s.dir, nil) 666 c.Check(err, ErrorMatches, `cannot parse gadget metadata: "edition" must be a positive number, not "borked"`) 667 668 broken = bytes.Replace(mockVolumeUpdateGadgetYaml, []byte("edition: 5"), []byte("edition: -5"), 1) 669 err = ioutil.WriteFile(s.gadgetYamlPath, broken, 0644) 670 c.Assert(err, IsNil) 671 672 _, err = gadget.ReadInfo(s.dir, nil) 673 c.Check(err, ErrorMatches, `cannot parse gadget metadata: "edition" must be a positive number, not "-5"`) 674 } 675 676 func (s *gadgetYamlTestSuite) TestUnmarshalGadgetSize(c *C) { 677 type foo struct { 678 Size gadget.Size `yaml:"size"` 679 } 680 681 for i, tc := range []struct { 682 s string 683 sz gadget.Size 684 err string 685 }{ 686 {"1234", 1234, ""}, 687 {"1234M", 1234 * gadget.SizeMiB, ""}, 688 {"1234G", 1234 * gadget.SizeGiB, ""}, 689 {"0", 0, ""}, 690 {"a0M", 0, `cannot parse size "a0M": no numerical prefix.*`}, 691 {"-123", 0, `cannot parse size "-123": size cannot be negative`}, 692 {"123a", 0, `cannot parse size "123a": invalid suffix "a"`}, 693 } { 694 c.Logf("tc: %v", i) 695 696 var f foo 697 err := yaml.Unmarshal([]byte(fmt.Sprintf("size: %s", tc.s)), &f) 698 if tc.err != "" { 699 c.Check(err, ErrorMatches, tc.err) 700 } else { 701 c.Check(err, IsNil) 702 c.Check(f.Size, Equals, tc.sz) 703 } 704 } 705 } 706 707 func (s *gadgetYamlTestSuite) TestUnmarshalGadgetRelativeOffset(c *C) { 708 type foo struct { 709 OffsetWrite gadget.RelativeOffset `yaml:"offset-write"` 710 } 711 712 for i, tc := range []struct { 713 s string 714 sz *gadget.RelativeOffset 715 err string 716 }{ 717 {"1234", &gadget.RelativeOffset{Offset: 1234}, ""}, 718 {"1234M", &gadget.RelativeOffset{Offset: 1234 * gadget.SizeMiB}, ""}, 719 {"4096M", &gadget.RelativeOffset{Offset: 4096 * gadget.SizeMiB}, ""}, 720 {"0", &gadget.RelativeOffset{}, ""}, 721 {"mbr+0", &gadget.RelativeOffset{RelativeTo: "mbr"}, ""}, 722 {"foo+1234M", &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1234 * gadget.SizeMiB}, ""}, 723 {"foo+1G", &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1 * gadget.SizeGiB}, ""}, 724 {"foo+1G", &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1 * gadget.SizeGiB}, ""}, 725 {"foo+4097M", nil, `cannot parse relative offset "foo\+4097M": offset above 4G limit`}, 726 {"foo+", nil, `cannot parse relative offset "foo\+": missing offset`}, 727 {"foo+++12", nil, `cannot parse relative offset "foo\+\+\+12": cannot parse offset "\+\+12": .*`}, 728 {"+12", nil, `cannot parse relative offset "\+12": missing volume name`}, 729 {"a0M", nil, `cannot parse relative offset "a0M": cannot parse offset "a0M": no numerical prefix.*`}, 730 {"-123", nil, `cannot parse relative offset "-123": cannot parse offset "-123": size cannot be negative`}, 731 {"123a", nil, `cannot parse relative offset "123a": cannot parse offset "123a": invalid suffix "a"`}, 732 } { 733 c.Logf("tc: %v", i) 734 735 var f foo 736 err := yaml.Unmarshal([]byte(fmt.Sprintf("offset-write: %s", tc.s)), &f) 737 if tc.err != "" { 738 c.Check(err, ErrorMatches, tc.err) 739 } else { 740 c.Check(err, IsNil) 741 c.Assert(tc.sz, NotNil, Commentf("test case %v data must be not-nil", i)) 742 c.Check(f.OffsetWrite, Equals, *tc.sz) 743 } 744 } 745 } 746 747 var classicModelConstraints = []gadget.Model{ 748 nil, 749 &modelConstraints{classic: false, systemSeed: false}, 750 &modelConstraints{classic: true, systemSeed: false}, 751 } 752 753 func (s *gadgetYamlTestSuite) TestReadGadgetYamlPCHappy(c *C) { 754 err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644) 755 c.Assert(err, IsNil) 756 757 for _, constraints := range classicModelConstraints { 758 _, err = gadget.ReadInfo(s.dir, constraints) 759 c.Assert(err, IsNil) 760 } 761 } 762 763 func (s *gadgetYamlTestSuite) TestReadGadgetYamlRPiHappy(c *C) { 764 err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlRPi, 0644) 765 c.Assert(err, IsNil) 766 767 for _, constraints := range classicModelConstraints { 768 _, err = gadget.ReadInfo(s.dir, constraints) 769 c.Assert(err, IsNil) 770 } 771 } 772 773 func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkHappy(c *C) { 774 err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLk, 0644) 775 c.Assert(err, IsNil) 776 777 for _, constraints := range classicModelConstraints { 778 _, err = gadget.ReadInfo(s.dir, constraints) 779 c.Assert(err, IsNil) 780 } 781 } 782 783 func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkLegacyHappy(c *C) { 784 err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLkLegacy, 0644) 785 c.Assert(err, IsNil) 786 787 for _, constraints := range classicModelConstraints { 788 _, err = gadget.ReadInfo(s.dir, constraints) 789 c.Assert(err, IsNil) 790 } 791 } 792 793 func (s *gadgetYamlTestSuite) TestValidateStructureType(c *C) { 794 for i, tc := range []struct { 795 s string 796 err string 797 schema string 798 }{ 799 // legacy 800 {"mbr", "", ""}, 801 // special case 802 {"bare", "", ""}, 803 // plain MBR type 804 {"0C", "", "mbr"}, 805 // GPT UUID 806 {"21686148-6449-6E6F-744E-656564454649", "", "gpt"}, 807 // GPT UUID (lowercase) 808 {"21686148-6449-6e6f-744e-656564454649", "", "gpt"}, 809 // hybrid ID 810 {"EF,21686148-6449-6E6F-744E-656564454649", "", ""}, 811 // hybrid ID (UUID lowercase) 812 {"EF,21686148-6449-6e6f-744e-656564454649", "", ""}, 813 // hybrid, partially lowercase UUID 814 {"EF,aa686148-6449-6e6f-744E-656564454649", "", ""}, 815 // GPT UUID, partially lowercase 816 {"aa686148-6449-6e6f-744E-656564454649", "", ""}, 817 // no type specified 818 {"", `invalid type "": type is not specified`, ""}, 819 // plain MBR type without mbr schema 820 {"0C", `invalid type "0C": MBR structure type with non-MBR schema ""`, ""}, 821 // GPT UUID with non GPT schema 822 {"21686148-6449-6E6F-744E-656564454649", `invalid type "21686148-6449-6E6F-744E-656564454649": GUID structure type with non-GPT schema "mbr"`, "mbr"}, 823 // invalid 824 {"1234", `invalid type "1234": invalid format`, ""}, 825 // outside of hex range 826 {"FG", `invalid type "FG": invalid format`, ""}, 827 {"GG686148-6449-6E6F-744E-656564454649", `invalid type "GG686148-6449-6E6F-744E-656564454649": invalid format`, ""}, 828 // too long 829 {"AA686148-6449-6E6F-744E-656564454649123", `invalid type "AA686148-6449-6E6F-744E-656564454649123": invalid format`, ""}, 830 // hybrid, missing MBR type 831 {",AA686148-6449-6E6F-744E-656564454649", `invalid type ",AA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, ""}, 832 // hybrid, missing GPT UUID 833 {"EF,", `invalid type "EF,": invalid format of hybrid type`, ""}, 834 // hybrid, MBR type too long 835 {"EFC,AA686148-6449-6E6F-744E-656564454649", `invalid type "EFC,AA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, ""}, 836 // hybrid, GPT UUID too long 837 {"EF,AAAA686148-6449-6E6F-744E-656564454649", `invalid type "EF,AAAA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, ""}, 838 // GPT schema with non GPT type 839 {"EF,AAAA686148-6449-6E6F-744E-656564454649", `invalid type "EF,AAAA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, "gpt"}, 840 } { 841 c.Logf("tc: %v %q", i, tc.s) 842 843 err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{Type: tc.s, Size: 123}, &gadget.Volume{Schema: tc.schema}) 844 if tc.err != "" { 845 c.Check(err, ErrorMatches, tc.err) 846 } else { 847 c.Check(err, IsNil) 848 } 849 } 850 } 851 852 func mustParseStructure(c *C, s string) *gadget.VolumeStructure { 853 var v gadget.VolumeStructure 854 err := yaml.Unmarshal([]byte(s), &v) 855 c.Assert(err, IsNil) 856 return &v 857 } 858 859 func (s *gadgetYamlTestSuite) TestValidateRole(c *C) { 860 uuidType := ` 861 type: 21686148-6449-6E6F-744E-656564454649 862 size: 1023 863 ` 864 bareType := ` 865 type: bare 866 ` 867 mbrTooLarge := bareType + ` 868 role: mbr 869 size: 467` 870 mbrBadOffset := bareType + ` 871 role: mbr 872 size: 446 873 offset: 123` 874 mbrBadID := bareType + ` 875 role: mbr 876 id: 123 877 size: 446` 878 mbrBadFilesystem := bareType + ` 879 role: mbr 880 size: 446 881 filesystem: vfat` 882 mbrNoneFilesystem := ` 883 type: bare 884 role: mbr 885 filesystem: none 886 size: 446` 887 typeConflictsRole := ` 888 type: bare 889 role: system-data 890 size: 1M` 891 validSystemBoot := uuidType + ` 892 role: system-boot 893 ` 894 validSystemSeed := uuidType + ` 895 role: system-seed 896 ` 897 emptyRole := uuidType + ` 898 role: system-boot 899 size: 123M 900 ` 901 bogusRole := uuidType + ` 902 role: foobar 903 size: 123M 904 ` 905 legacyMBR := ` 906 type: mbr 907 size: 446` 908 legacyTypeMatchingRole := ` 909 type: mbr 910 role: mbr 911 size: 446` 912 legacyTypeConflictsRole := ` 913 type: mbr 914 role: system-data 915 size: 446` 916 legacyTypeAsMBRTooLarge := ` 917 type: mbr 918 size: 447` 919 vol := &gadget.Volume{} 920 mbrVol := &gadget.Volume{Schema: "mbr"} 921 for i, tc := range []struct { 922 s *gadget.VolumeStructure 923 v *gadget.Volume 924 err string 925 }{ 926 {mustParseStructure(c, validSystemBoot), vol, ""}, 927 // empty, ok too 928 {mustParseStructure(c, emptyRole), vol, ""}, 929 // invalid role name 930 {mustParseStructure(c, bogusRole), vol, `invalid role "foobar": unsupported role`}, 931 // the system-seed role 932 {mustParseStructure(c, validSystemSeed), vol, ""}, 933 {mustParseStructure(c, validSystemSeed), vol, ""}, 934 {mustParseStructure(c, validSystemSeed), vol, ""}, 935 // mbr 936 {mustParseStructure(c, mbrTooLarge), mbrVol, `invalid role "mbr": mbr structures cannot be larger than 446 bytes`}, 937 {mustParseStructure(c, mbrBadOffset), mbrVol, `invalid role "mbr": mbr structure must start at offset 0`}, 938 {mustParseStructure(c, mbrBadID), mbrVol, `invalid role "mbr": mbr structure must not specify partition ID`}, 939 {mustParseStructure(c, mbrBadFilesystem), mbrVol, `invalid role "mbr": mbr structures must not specify a file system`}, 940 // filesystem: none is ok for MBR 941 {mustParseStructure(c, mbrNoneFilesystem), mbrVol, ""}, 942 // legacy, type: mbr treated like role: mbr 943 {mustParseStructure(c, legacyMBR), mbrVol, ""}, 944 {mustParseStructure(c, legacyTypeMatchingRole), mbrVol, ""}, 945 {mustParseStructure(c, legacyTypeAsMBRTooLarge), mbrVol, `invalid implicit role "mbr": mbr structures cannot be larger than 446 bytes`}, 946 {mustParseStructure(c, legacyTypeConflictsRole), vol, `invalid role "system-data": conflicting legacy type: "mbr"`}, 947 // conflicting type/role 948 {mustParseStructure(c, typeConflictsRole), vol, `invalid role "system-data": conflicting type: "bare"`}, 949 } { 950 c.Logf("tc: %v %+v", i, tc.s) 951 952 err := gadget.ValidateVolumeStructure(tc.s, tc.v) 953 if tc.err != "" { 954 c.Check(err, ErrorMatches, tc.err) 955 } else { 956 c.Check(err, IsNil) 957 } 958 } 959 } 960 961 func (s *gadgetYamlTestSuite) TestValidateFilesystem(c *C) { 962 for i, tc := range []struct { 963 s string 964 err string 965 }{ 966 {"vfat", ""}, 967 {"ext4", ""}, 968 {"none", ""}, 969 {"btrfs", `invalid filesystem "btrfs"`}, 970 } { 971 c.Logf("tc: %v %+v", i, tc.s) 972 973 err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{Filesystem: tc.s, Type: "21686148-6449-6E6F-744E-656564454649", Size: 123}, &gadget.Volume{}) 974 if tc.err != "" { 975 c.Check(err, ErrorMatches, tc.err) 976 } else { 977 c.Check(err, IsNil) 978 } 979 } 980 } 981 982 func (s *gadgetYamlTestSuite) TestValidateVolumeSchema(c *C) { 983 for i, tc := range []struct { 984 s string 985 err string 986 }{ 987 {"gpt", ""}, 988 {"mbr", ""}, 989 // implicit GPT 990 {"", ""}, 991 // invalid 992 {"some", `invalid schema "some"`}, 993 } { 994 c.Logf("tc: %v %+v", i, tc.s) 995 996 err := gadget.ValidateVolume("name", &gadget.Volume{Schema: tc.s}, nil) 997 if tc.err != "" { 998 c.Check(err, ErrorMatches, tc.err) 999 } else { 1000 c.Check(err, IsNil) 1001 } 1002 } 1003 } 1004 1005 func (s *gadgetYamlTestSuite) TestValidateVolumeName(c *C) { 1006 1007 for i, tc := range []struct { 1008 s string 1009 err string 1010 }{ 1011 {"valid", ""}, 1012 {"still-valid", ""}, 1013 {"123volume", ""}, 1014 {"volume123", ""}, 1015 {"PC", ""}, 1016 {"PC123", ""}, 1017 {"UPCASE", ""}, 1018 // invalid 1019 {"-valid", "invalid name"}, 1020 {"in+valid", "invalid name"}, 1021 {"with whitespace", "invalid name"}, 1022 {"", "invalid name"}, 1023 } { 1024 c.Logf("tc: %v %+v", i, tc.s) 1025 1026 err := gadget.ValidateVolume(tc.s, &gadget.Volume{}, nil) 1027 if tc.err != "" { 1028 c.Check(err, ErrorMatches, tc.err) 1029 } else { 1030 c.Check(err, IsNil) 1031 } 1032 } 1033 } 1034 1035 func (s *gadgetYamlTestSuite) TestValidateVolumeDuplicateStructures(c *C) { 1036 err := gadget.ValidateVolume("name", &gadget.Volume{ 1037 Structure: []gadget.VolumeStructure{ 1038 {Name: "duplicate", Type: "bare", Size: 1024}, 1039 {Name: "duplicate", Type: "21686148-6449-6E6F-744E-656564454649", Size: 2048}, 1040 }, 1041 }, nil) 1042 c.Assert(err, ErrorMatches, `structure name "duplicate" is not unique`) 1043 } 1044 1045 func (s *gadgetYamlTestSuite) TestValidateVolumeDuplicateFsLabel(c *C) { 1046 err := gadget.ValidateVolume("name", &gadget.Volume{ 1047 Structure: []gadget.VolumeStructure{ 1048 {Label: "foo", Type: "21686148-6449-6E6F-744E-656564454123", Size: gadget.SizeMiB}, 1049 {Label: "foo", Type: "21686148-6449-6E6F-744E-656564454649", Size: gadget.SizeMiB}, 1050 }, 1051 }, nil) 1052 c.Assert(err, ErrorMatches, `filesystem label "foo" is not unique`) 1053 1054 // writable isn't special 1055 for _, x := range []struct { 1056 systemSeed bool 1057 label string 1058 errMsg string 1059 }{ 1060 {false, "writable", `filesystem label "writable" is not unique`}, 1061 {false, "ubuntu-data", `filesystem label "ubuntu-data" is not unique`}, 1062 {true, "writable", `filesystem label "writable" is not unique`}, 1063 {true, "ubuntu-data", `filesystem label "ubuntu-data" is not unique`}, 1064 } { 1065 for _, constraints := range []*modelConstraints{ 1066 {classic: false, systemSeed: x.systemSeed}, 1067 {classic: true, systemSeed: x.systemSeed}, 1068 } { 1069 err = gadget.ValidateVolume("name", &gadget.Volume{ 1070 Structure: []gadget.VolumeStructure{{ 1071 Name: "data1", 1072 Role: gadget.SystemData, 1073 Label: x.label, 1074 Type: "21686148-6449-6E6F-744E-656564454123", 1075 Size: gadget.SizeMiB, 1076 }, { 1077 Name: "data2", 1078 Role: gadget.SystemData, 1079 Label: x.label, 1080 Type: "21686148-6449-6E6F-744E-656564454649", 1081 Size: gadget.SizeMiB, 1082 }}, 1083 }, constraints) 1084 c.Assert(err, ErrorMatches, x.errMsg) 1085 } 1086 } 1087 1088 // nor is system-boot 1089 err = gadget.ValidateVolume("name", &gadget.Volume{ 1090 Structure: []gadget.VolumeStructure{{ 1091 Name: "boot1", 1092 Label: "system-boot", 1093 Type: "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B", 1094 Size: gadget.SizeMiB, 1095 }, { 1096 Name: "boot2", 1097 Label: "system-boot", 1098 Type: "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B", 1099 Size: gadget.SizeMiB, 1100 }}, 1101 }, nil) 1102 c.Assert(err, ErrorMatches, `filesystem label "system-boot" is not unique`) 1103 } 1104 1105 func (s *gadgetYamlTestSuite) TestValidateVolumeErrorsWrapped(c *C) { 1106 err := gadget.ValidateVolume("name", &gadget.Volume{ 1107 Structure: []gadget.VolumeStructure{ 1108 {Type: "bare", Size: 1024}, 1109 {Type: "bogus", Size: 1024}, 1110 }, 1111 }, nil) 1112 c.Assert(err, ErrorMatches, `invalid structure #1: invalid type "bogus": invalid format`) 1113 1114 err = gadget.ValidateVolume("name", &gadget.Volume{ 1115 Structure: []gadget.VolumeStructure{ 1116 {Type: "bare", Size: 1024}, 1117 {Type: "bogus", Size: 1024, Name: "foo"}, 1118 }, 1119 }, nil) 1120 c.Assert(err, ErrorMatches, `invalid structure #1 \("foo"\): invalid type "bogus": invalid format`) 1121 1122 err = gadget.ValidateVolume("name", &gadget.Volume{ 1123 Structure: []gadget.VolumeStructure{ 1124 {Type: "bare", Name: "foo", Size: 1024, Content: []gadget.VolumeContent{{Source: "foo"}}}, 1125 }, 1126 }, nil) 1127 c.Assert(err, ErrorMatches, `invalid structure #0 \("foo"\): invalid content #0: cannot use non-image content for bare file system`) 1128 } 1129 1130 func (s *gadgetYamlTestSuite) TestValidateStructureContent(c *C) { 1131 bareOnlyOk := ` 1132 type: bare 1133 size: 1M 1134 content: 1135 - image: foo.img 1136 ` 1137 bareMixed := ` 1138 type: bare 1139 size: 1M 1140 content: 1141 - image: foo.img 1142 - source: foo 1143 target: bar 1144 ` 1145 bareMissing := ` 1146 type: bare 1147 size: 1M 1148 content: 1149 - offset: 123 1150 ` 1151 fsOk := ` 1152 type: 21686148-6449-6E6F-744E-656564454649 1153 filesystem: ext4 1154 size: 1M 1155 content: 1156 - source: foo 1157 target: bar 1158 ` 1159 fsMixed := ` 1160 type: 21686148-6449-6E6F-744E-656564454649 1161 filesystem: ext4 1162 size: 1M 1163 content: 1164 - source: foo 1165 target: bar 1166 - image: foo.img 1167 ` 1168 fsMissing := ` 1169 type: 21686148-6449-6E6F-744E-656564454649 1170 filesystem: ext4 1171 size: 1M 1172 content: 1173 - source: foo 1174 ` 1175 1176 for i, tc := range []struct { 1177 s *gadget.VolumeStructure 1178 v *gadget.Volume 1179 err string 1180 }{ 1181 {mustParseStructure(c, bareOnlyOk), nil, ""}, 1182 {mustParseStructure(c, bareMixed), nil, `invalid content #1: cannot use non-image content for bare file system`}, 1183 {mustParseStructure(c, bareMissing), nil, `invalid content #0: missing image file name`}, 1184 {mustParseStructure(c, fsOk), nil, ""}, 1185 {mustParseStructure(c, fsMixed), nil, `invalid content #1: cannot use image content for non-bare file system`}, 1186 {mustParseStructure(c, fsMissing), nil, `invalid content #0: missing source or target`}, 1187 } { 1188 c.Logf("tc: %v %+v", i, tc.s) 1189 1190 err := gadget.ValidateVolumeStructure(tc.s, &gadget.Volume{}) 1191 if tc.err != "" { 1192 c.Check(err, ErrorMatches, tc.err) 1193 } else { 1194 c.Check(err, IsNil) 1195 } 1196 } 1197 } 1198 1199 func (s *gadgetYamlTestSuite) TestValidateStructureAndContentRelativeOffset(c *C) { 1200 gadgetYamlHeader := ` 1201 volumes: 1202 pc: 1203 bootloader: grub 1204 structure: 1205 - name: my-name-is 1206 type: mbr 1207 size: 440 1208 content: 1209 - image: pc-boot.img` 1210 1211 gadgetYamlBadStructureName := gadgetYamlHeader + ` 1212 - name: other-name 1213 type: DA,21686148-6449-6E6F-744E-656564454649 1214 size: 1M 1215 offset: 1M 1216 offset-write: bad-name+92 1217 content: 1218 - image: pc-core.img 1219 ` 1220 gadgetYamlBadContentName := gadgetYamlHeader + ` 1221 - name: other-name 1222 type: DA,21686148-6449-6E6F-744E-656564454649 1223 size: 1M 1224 offset: 1M 1225 offset-write: my-name-is+92 1226 content: 1227 - image: pc-core.img 1228 offset-write: bad-name+123 1229 ` 1230 1231 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYamlBadStructureName), 0644) 1232 c.Assert(err, IsNil) 1233 1234 _, err = gadget.ReadInfo(s.dir, nil) 1235 c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\) refers to an unknown structure "bad-name"`) 1236 1237 err = ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYamlBadContentName), 0644) 1238 c.Assert(err, IsNil) 1239 1240 _, err = gadget.ReadInfo(s.dir, nil) 1241 c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\), content #0 \("pc-core.img"\) refers to an unknown structure "bad-name"`) 1242 1243 } 1244 1245 func (s *gadgetYamlTestSuite) TestValidateStructureUpdatePreserveOnlyForFs(c *C) { 1246 gv := &gadget.Volume{} 1247 1248 err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1249 Type: "bare", 1250 Update: gadget.VolumeUpdate{Preserve: []string{"foo"}}, 1251 Size: 512, 1252 }, gv) 1253 c.Check(err, ErrorMatches, "preserving files during update is not supported for non-filesystem structures") 1254 1255 err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1256 Type: "21686148-6449-6E6F-744E-656564454649", 1257 Update: gadget.VolumeUpdate{Preserve: []string{"foo"}}, 1258 Size: 512, 1259 }, gv) 1260 c.Check(err, ErrorMatches, "preserving files during update is not supported for non-filesystem structures") 1261 1262 err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1263 Type: "21686148-6449-6E6F-744E-656564454649", 1264 Filesystem: "vfat", 1265 Update: gadget.VolumeUpdate{Preserve: []string{"foo"}}, 1266 Size: 512, 1267 }, gv) 1268 c.Check(err, IsNil) 1269 } 1270 1271 func (s *gadgetYamlTestSuite) TestValidateStructureUpdatePreserveDuplicates(c *C) { 1272 gv := &gadget.Volume{} 1273 1274 err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1275 Type: "21686148-6449-6E6F-744E-656564454649", 1276 Filesystem: "vfat", 1277 Update: gadget.VolumeUpdate{Edition: 1, Preserve: []string{"foo", "bar"}}, 1278 Size: 512, 1279 }, gv) 1280 c.Check(err, IsNil) 1281 1282 err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1283 Type: "21686148-6449-6E6F-744E-656564454649", 1284 Filesystem: "vfat", 1285 Update: gadget.VolumeUpdate{Edition: 1, Preserve: []string{"foo", "bar", "foo"}}, 1286 Size: 512, 1287 }, gv) 1288 c.Check(err, ErrorMatches, `duplicate "preserve" entry "foo"`) 1289 } 1290 1291 func (s *gadgetYamlTestSuite) TestValidateStructureSizeRequired(c *C) { 1292 1293 gv := &gadget.Volume{} 1294 1295 err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1296 Type: "bare", 1297 Update: gadget.VolumeUpdate{Preserve: []string{"foo"}}, 1298 }, gv) 1299 c.Check(err, ErrorMatches, "missing size") 1300 1301 err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1302 Type: "21686148-6449-6E6F-744E-656564454649", 1303 Filesystem: "vfat", 1304 Update: gadget.VolumeUpdate{Preserve: []string{"foo"}}, 1305 }, gv) 1306 c.Check(err, ErrorMatches, "missing size") 1307 1308 err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{ 1309 Type: "21686148-6449-6E6F-744E-656564454649", 1310 Filesystem: "vfat", 1311 Size: mustParseGadgetSize(c, "123M"), 1312 Update: gadget.VolumeUpdate{Preserve: []string{"foo"}}, 1313 }, gv) 1314 c.Check(err, IsNil) 1315 } 1316 1317 func (s *gadgetYamlTestSuite) TestValidateLayoutOverlapPreceding(c *C) { 1318 overlappingGadgetYaml := ` 1319 volumes: 1320 pc: 1321 bootloader: grub 1322 structure: 1323 - name: mbr 1324 type: mbr 1325 size: 440 1326 content: 1327 - image: pc-boot.img 1328 - name: other-name 1329 type: DA,21686148-6449-6E6F-744E-656564454649 1330 size: 1M 1331 offset: 200 1332 content: 1333 - image: pc-core.img 1334 ` 1335 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(overlappingGadgetYaml), 0644) 1336 c.Assert(err, IsNil) 1337 1338 _, err = gadget.ReadInfo(s.dir, nil) 1339 c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\) overlaps with the preceding structure #0 \("mbr"\)`) 1340 } 1341 1342 func (s *gadgetYamlTestSuite) TestValidateLayoutOverlapOutOfOrder(c *C) { 1343 outOfOrderGadgetYaml := ` 1344 volumes: 1345 pc: 1346 bootloader: grub 1347 structure: 1348 - name: overlaps-with-foo 1349 type: DA,21686148-6449-6E6F-744E-656564454649 1350 size: 1M 1351 offset: 200 1352 content: 1353 - image: pc-core.img 1354 - name: foo 1355 type: DA,21686148-6449-6E6F-744E-656564454648 1356 size: 1M 1357 offset: 100 1358 filesystem: vfat 1359 ` 1360 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(outOfOrderGadgetYaml), 0644) 1361 c.Assert(err, IsNil) 1362 1363 _, err = gadget.ReadInfo(s.dir, nil) 1364 c.Check(err, ErrorMatches, `invalid volume "pc": structure #0 \("overlaps-with-foo"\) overlaps with the preceding structure #1 \("foo"\)`) 1365 } 1366 1367 func (s *gadgetYamlTestSuite) TestValidateCrossStructureMBRFixedOffset(c *C) { 1368 gadgetYaml := ` 1369 volumes: 1370 pc: 1371 bootloader: grub 1372 structure: 1373 - name: other-name 1374 type: DA,21686148-6449-6E6F-744E-656564454649 1375 size: 1M 1376 offset: 500 1377 content: 1378 - image: pc-core.img 1379 - name: mbr 1380 type: mbr 1381 size: 440 1382 offset: 0 1383 content: 1384 - image: pc-boot.img 1385 ` 1386 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYaml), 0644) 1387 c.Assert(err, IsNil) 1388 1389 _, err = gadget.ReadInfo(s.dir, nil) 1390 c.Check(err, IsNil) 1391 } 1392 1393 func (s *gadgetYamlTestSuite) TestValidateCrossStructureMBRDefaultOffsetInvalid(c *C) { 1394 gadgetYaml := ` 1395 volumes: 1396 pc: 1397 bootloader: grub 1398 structure: 1399 - name: other-name 1400 type: DA,21686148-6449-6E6F-744E-656564454649 1401 size: 1M 1402 offset: 500 1403 content: 1404 - image: pc-core.img 1405 - name: mbr 1406 type: mbr 1407 size: 440 1408 content: 1409 - image: pc-boot.img 1410 ` 1411 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYaml), 0644) 1412 c.Assert(err, IsNil) 1413 1414 _, err = gadget.ReadInfo(s.dir, nil) 1415 c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("mbr"\) has "mbr" role and must start at offset 0`) 1416 } 1417 1418 type gadgetTestSuite struct{} 1419 1420 var _ = Suite(&gadgetTestSuite{}) 1421 1422 func (s *gadgetTestSuite) TestEffectiveRole(c *C) { 1423 // no role set 1424 vs := gadget.VolumeStructure{Role: ""} 1425 c.Check(vs.EffectiveRole(), Equals, "") 1426 1427 // explicitly set role trumps all 1428 vs = gadget.VolumeStructure{Role: "foobar", Type: "mbr", Label: gadget.SystemBoot} 1429 1430 c.Check(vs.EffectiveRole(), Equals, "foobar") 1431 1432 vs = gadget.VolumeStructure{Role: "mbr"} 1433 c.Check(vs.EffectiveRole(), Equals, "mbr") 1434 1435 // legacy fallback 1436 vs = gadget.VolumeStructure{Role: "", Type: "mbr"} 1437 c.Check(vs.EffectiveRole(), Equals, "mbr") 1438 1439 // fallback role based on fs label applies only to system-boot 1440 vs = gadget.VolumeStructure{Role: "", Label: gadget.SystemBoot} 1441 c.Check(vs.EffectiveRole(), Equals, gadget.SystemBoot) 1442 vs = gadget.VolumeStructure{Role: "", Label: gadget.SystemData} 1443 c.Check(vs.EffectiveRole(), Equals, "") 1444 vs = gadget.VolumeStructure{Role: "", Label: gadget.SystemSeed} 1445 c.Check(vs.EffectiveRole(), Equals, "") 1446 } 1447 1448 func (s *gadgetTestSuite) TestEffectiveFilesystemLabel(c *C) { 1449 // no label, and no role set 1450 vs := gadget.VolumeStructure{Role: ""} 1451 c.Check(vs.EffectiveFilesystemLabel(), Equals, "") 1452 1453 // explicitly set label 1454 vs = gadget.VolumeStructure{Label: "my-label"} 1455 c.Check(vs.EffectiveFilesystemLabel(), Equals, "my-label") 1456 1457 // inferred based on role 1458 vs = gadget.VolumeStructure{Role: gadget.SystemData, Label: "unused-label"} 1459 c.Check(vs.EffectiveFilesystemLabel(), Equals, "writable") 1460 vs = gadget.VolumeStructure{Role: gadget.SystemData} 1461 c.Check(vs.EffectiveFilesystemLabel(), Equals, "writable") 1462 1463 // only system-data role is special 1464 vs = gadget.VolumeStructure{Role: gadget.SystemBoot} 1465 c.Check(vs.EffectiveFilesystemLabel(), Equals, "") 1466 } 1467 1468 func (s *gadgetYamlTestSuite) TestEnsureVolumeConsistency(c *C) { 1469 state := func(seed bool, label string) *gadget.ValidationState { 1470 systemDataVolume := &gadget.VolumeStructure{Label: label} 1471 systemSeedVolume := (*gadget.VolumeStructure)(nil) 1472 if seed { 1473 systemSeedVolume = &gadget.VolumeStructure{} 1474 } 1475 return &gadget.ValidationState{ 1476 SystemSeed: systemSeedVolume, 1477 SystemData: systemDataVolume, 1478 } 1479 } 1480 1481 for i, tc := range []struct { 1482 s *gadget.ValidationState 1483 err string 1484 }{ 1485 1486 // we have the system-seed role 1487 {state(true, ""), ""}, 1488 {state(true, "foobar"), "system-data structure must not have a label"}, 1489 {state(true, "writable"), "system-data structure must not have a label"}, 1490 {state(true, "ubuntu-data"), "system-data structure must not have a label"}, 1491 1492 // we don't have the system-seed role (old systems) 1493 {state(false, ""), ""}, // implicit is ok 1494 {state(false, "foobar"), `.* must have an implicit label or "writable", not "foobar"`}, 1495 {state(false, "writable"), ""}, 1496 {state(false, "ubuntu-data"), `.* must have an implicit label or "writable", not "ubuntu-data"`}, 1497 } { 1498 c.Logf("tc: %v %p %v", i, tc.s.SystemSeed, tc.s.SystemData.Label) 1499 1500 err := gadget.EnsureVolumeConsistency(tc.s, nil) 1501 if tc.err != "" { 1502 c.Assert(err, ErrorMatches, tc.err) 1503 } else { 1504 c.Check(err, IsNil) 1505 } 1506 } 1507 1508 // Check system-seed label 1509 for i, tc := range []struct { 1510 l string 1511 err string 1512 }{ 1513 {"", ""}, 1514 {"foobar", "system-seed structure must not have a label"}, 1515 {"ubuntu-seed", "system-seed structure must not have a label"}, 1516 } { 1517 c.Logf("tc: %v %v", i, tc.l) 1518 s := state(true, "") 1519 s.SystemSeed.Label = tc.l 1520 err := gadget.EnsureVolumeConsistency(s, nil) 1521 if tc.err != "" { 1522 c.Assert(err, ErrorMatches, tc.err) 1523 } else { 1524 c.Check(err, IsNil) 1525 } 1526 } 1527 1528 // Check system-seed without system-data 1529 vs := &gadget.ValidationState{} 1530 err := gadget.EnsureVolumeConsistency(vs, nil) 1531 c.Assert(err, IsNil) 1532 vs.SystemSeed = &gadget.VolumeStructure{} 1533 err = gadget.EnsureVolumeConsistency(vs, nil) 1534 c.Assert(err, ErrorMatches, "the system-seed role requires system-data to be defined") 1535 } 1536 1537 func (s *gadgetYamlTestSuite) TestGadgetConsistencyWithoutConstraints(c *C) { 1538 for i, tc := range []struct { 1539 role string 1540 label string 1541 err string 1542 }{ 1543 // when constraints are nil, the system-seed role and ubuntu-data label on the 1544 // system-data structure should be consistent 1545 {"system-seed", "", ""}, 1546 {"system-seed", "writable", ".* system-data structure must not have a label"}, 1547 {"system-seed", "ubuntu-data", ".* system-data structure must not have a label"}, 1548 {"", "", ""}, 1549 {"", "writable", ""}, 1550 {"", "ubuntu-data", `.* must have an implicit label or "writable", not "ubuntu-data"`}, 1551 } { 1552 c.Logf("tc: %v %v %v", i, tc.role, tc.label) 1553 b := &bytes.Buffer{} 1554 1555 fmt.Fprintf(b, ` 1556 volumes: 1557 pc: 1558 bootloader: grub 1559 schema: mbr 1560 structure:`) 1561 1562 if tc.role == "system-seed" { 1563 fmt.Fprintf(b, ` 1564 - name: Recovery 1565 size: 10M 1566 type: 83 1567 role: system-seed`) 1568 } 1569 1570 fmt.Fprintf(b, ` 1571 - name: Data 1572 size: 10M 1573 type: 83 1574 role: system-data 1575 filesystem-label: %s`, tc.label) 1576 1577 err := ioutil.WriteFile(s.gadgetYamlPath, b.Bytes(), 0644) 1578 c.Assert(err, IsNil) 1579 1580 _, err = gadget.ReadInfo(s.dir, nil) 1581 if tc.err != "" { 1582 c.Assert(err, ErrorMatches, tc.err) 1583 1584 } else { 1585 c.Check(err, IsNil) 1586 } 1587 } 1588 } 1589 1590 func (s *gadgetYamlTestSuite) TestGadgetConsistencyWithConstraints(c *C) { 1591 bloader := ` 1592 volumes: 1593 pc: 1594 bootloader: grub 1595 schema: mbr 1596 structure:` 1597 1598 for i, tc := range []struct { 1599 role string 1600 label string 1601 systemSeed bool 1602 err string 1603 }{ 1604 // when constraints are nil, the system-seed role and ubuntu-data label on the 1605 // system-data structure should be consistent 1606 {"system-seed", "", true, ""}, 1607 {"system-seed", "", false, `.* model does not support the system-seed role`}, 1608 {"system-seed", "writable", true, ".* system-data structure must not have a label"}, 1609 {"system-seed", "writable", false, `.* model does not support the system-seed role`}, 1610 {"system-seed", "ubuntu-data", true, ".* system-data structure must not have a label"}, 1611 {"system-seed", "ubuntu-data", false, `.* model does not support the system-seed role`}, 1612 {"", "writable", true, `.* model requires system-seed structure, but none was found`}, 1613 {"", "writable", false, ""}, 1614 {"", "ubuntu-data", true, `.* model requires system-seed structure, but none was found`}, 1615 {"", "ubuntu-data", false, `.* must have an implicit label or "writable", not "ubuntu-data"`}, 1616 } { 1617 c.Logf("tc: %v %v %v %v", i, tc.role, tc.label, tc.systemSeed) 1618 b := &bytes.Buffer{} 1619 1620 fmt.Fprintf(b, bloader) 1621 if tc.role == "system-seed" { 1622 fmt.Fprintf(b, ` 1623 - name: Recovery 1624 size: 10M 1625 type: 83 1626 role: system-seed`) 1627 } 1628 1629 fmt.Fprintf(b, ` 1630 - name: Data 1631 size: 10M 1632 type: 83 1633 role: system-data 1634 filesystem-label: %s`, tc.label) 1635 1636 err := ioutil.WriteFile(s.gadgetYamlPath, b.Bytes(), 0644) 1637 c.Assert(err, IsNil) 1638 1639 constraints := &modelConstraints{ 1640 classic: false, 1641 systemSeed: tc.systemSeed, 1642 } 1643 1644 _, err = gadget.ReadInfo(s.dir, constraints) 1645 if tc.err != "" { 1646 c.Assert(err, ErrorMatches, tc.err) 1647 } else { 1648 c.Check(err, IsNil) 1649 } 1650 } 1651 1652 // test error with no volumes 1653 err := ioutil.WriteFile(s.gadgetYamlPath, []byte(bloader), 0644) 1654 c.Assert(err, IsNil) 1655 constraints := &modelConstraints{ 1656 systemSeed: true, 1657 } 1658 _, err = gadget.ReadInfo(s.dir, constraints) 1659 c.Assert(err, ErrorMatches, ".*: model requires system-seed partition, but no system-seed or system-data partition found") 1660 } 1661 1662 func (s *gadgetYamlTestSuite) TestGadgetReadInfoVsFromMeta(c *C) { 1663 err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644) 1664 c.Assert(err, IsNil) 1665 1666 constraints := &modelConstraints{ 1667 classic: false, 1668 } 1669 1670 giRead, err := gadget.ReadInfo(s.dir, constraints) 1671 c.Check(err, IsNil) 1672 1673 giMeta, err := gadget.InfoFromGadgetYaml(gadgetYamlPC, constraints) 1674 c.Check(err, IsNil) 1675 1676 c.Assert(giRead, DeepEquals, giMeta) 1677 } 1678 1679 var ( 1680 classicConstraints = &modelConstraints{ 1681 classic: true, 1682 } 1683 coreConstraints = &modelConstraints{ 1684 classic: false, 1685 } 1686 ) 1687 1688 func (s *gadgetYamlTestSuite) TestGadgetFromMetaEmpty(c *C) { 1689 // this is ok for classic 1690 giClassic, err := gadget.InfoFromGadgetYaml([]byte(""), classicConstraints) 1691 c.Check(err, IsNil) 1692 c.Assert(giClassic, DeepEquals, &gadget.Info{}) 1693 1694 // but not so much for core 1695 giCore, err := gadget.InfoFromGadgetYaml([]byte(""), coreConstraints) 1696 c.Check(err, ErrorMatches, "bootloader not declared in any volume") 1697 c.Assert(giCore, IsNil) 1698 } 1699 1700 func (s *gadgetYamlTestSuite) TestPositionedVolumeFromGadgetMultiVolume(c *C) { 1701 err := ioutil.WriteFile(s.gadgetYamlPath, mockMultiVolumeGadgetYaml, 0644) 1702 c.Assert(err, IsNil) 1703 1704 _, err = gadget.PositionedVolumeFromGadget(s.dir) 1705 c.Assert(err, ErrorMatches, "cannot position multiple volumes yet") 1706 } 1707 1708 func (s *gadgetYamlTestSuite) TestPositionedVolumeFromGadgetHappy(c *C) { 1709 err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644) 1710 c.Assert(err, IsNil) 1711 for _, fn := range []string{"pc-boot.img", "pc-core.img"} { 1712 err = ioutil.WriteFile(filepath.Join(s.dir, fn), nil, 0644) 1713 c.Assert(err, IsNil) 1714 } 1715 1716 lv, err := gadget.PositionedVolumeFromGadget(s.dir) 1717 c.Assert(err, IsNil) 1718 c.Assert(lv.Volume.Bootloader, Equals, "grub") 1719 // mbr, bios-boot, efi-system 1720 c.Assert(lv.LaidOutStructure, HasLen, 3) 1721 } 1722 1723 func (s *gadgetYamlTestSuite) TestStructureBareFilesystem(c *C) { 1724 bareType := ` 1725 type: bare 1726 size: 1M` 1727 mbr := ` 1728 role: mbr 1729 size: 446` 1730 mbrLegacy := ` 1731 type: mbr 1732 size: 446` 1733 fs := ` 1734 type: 21686148-6449-6E6F-744E-656564454649 1735 filesystem: vfat` 1736 rawFsNoneExplicit := ` 1737 type: 21686148-6449-6E6F-744E-656564454649 1738 filesystem: none 1739 size: 1M` 1740 raw := ` 1741 type: 21686148-6449-6E6F-744E-656564454649 1742 size: 1M` 1743 for i, tc := range []struct { 1744 s *gadget.VolumeStructure 1745 hasFs bool 1746 isPartition bool 1747 }{ 1748 {mustParseStructure(c, bareType), false, false}, 1749 {mustParseStructure(c, mbr), false, false}, 1750 {mustParseStructure(c, mbrLegacy), false, false}, 1751 {mustParseStructure(c, fs), true, true}, 1752 {mustParseStructure(c, rawFsNoneExplicit), false, true}, 1753 {mustParseStructure(c, raw), false, true}, 1754 } { 1755 c.Logf("tc: %v %+v", i, tc.s) 1756 c.Check(tc.s.HasFilesystem(), Equals, tc.hasFs) 1757 c.Check(tc.s.IsPartition(), Equals, tc.isPartition) 1758 } 1759 } 1760 1761 var mockSnapYaml = `name: pc 1762 type: gadget 1763 version: 1.0 1764 ` 1765 1766 func (s *gadgetYamlTestSuite) TestReadGadgetYamlFromSnapFileMissing(c *C) { 1767 snapPath := snaptest.MakeTestSnapWithFiles(c, string(mockSnapYaml), nil) 1768 snapf, err := snapfile.Open(snapPath) 1769 c.Assert(err, IsNil) 1770 1771 // if constraints are nil, we allow a missing gadget.yaml 1772 _, err = gadget.ReadInfoFromSnapFile(snapf, nil) 1773 c.Assert(err, IsNil) 1774 1775 _, err = gadget.ReadInfoFromSnapFile(snapf, &modelConstraints{}) 1776 c.Assert(err, ErrorMatches, ".*meta/gadget.yaml: no such file or directory") 1777 } 1778 1779 var minimalMockGadgetYaml = ` 1780 volumes: 1781 pc: 1782 bootloader: grub 1783 ` 1784 1785 func (s *gadgetYamlTestSuite) TestReadGadgetYamlFromSnapFileValid(c *C) { 1786 snapPath := snaptest.MakeTestSnapWithFiles(c, mockSnapYaml, [][]string{ 1787 {"meta/gadget.yaml", string(minimalMockGadgetYaml)}, 1788 }) 1789 snapf, err := snapfile.Open(snapPath) 1790 c.Assert(err, IsNil) 1791 1792 ginfo, err := gadget.ReadInfoFromSnapFile(snapf, nil) 1793 c.Assert(err, IsNil) 1794 c.Assert(ginfo, DeepEquals, &gadget.Info{ 1795 Volumes: map[string]gadget.Volume{ 1796 "pc": { 1797 Bootloader: "grub", 1798 }, 1799 }, 1800 }) 1801 } 1802 1803 type gadgetCompatibilityTestSuite struct{} 1804 1805 var _ = Suite(&gadgetCompatibilityTestSuite{}) 1806 1807 func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleSelf(c *C) { 1808 giPC1, err := gadget.InfoFromGadgetYaml(gadgetYamlPC, coreConstraints) 1809 c.Assert(err, IsNil) 1810 giPC2, err := gadget.InfoFromGadgetYaml(gadgetYamlPC, coreConstraints) 1811 c.Assert(err, IsNil) 1812 1813 err = gadget.IsCompatible(giPC1, giPC2) 1814 c.Check(err, IsNil) 1815 } 1816 1817 func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleBadVolume(c *C) { 1818 var mockYaml = []byte(` 1819 volumes: 1820 volumename: 1821 schema: mbr 1822 bootloader: u-boot 1823 id: 0C 1824 `) 1825 1826 var mockOtherYaml = []byte(` 1827 volumes: 1828 volumename-other: 1829 schema: mbr 1830 bootloader: u-boot 1831 id: 0C 1832 `) 1833 var mockManyYaml = []byte(` 1834 volumes: 1835 volumename: 1836 schema: mbr 1837 bootloader: u-boot 1838 id: 0C 1839 volumename-many: 1840 schema: mbr 1841 id: 0C 1842 `) 1843 var mockBadIDYaml = []byte(` 1844 volumes: 1845 volumename: 1846 schema: mbr 1847 bootloader: u-boot 1848 id: 0D 1849 `) 1850 var mockSchemaYaml = []byte(` 1851 volumes: 1852 volumename: 1853 schema: gpt 1854 bootloader: u-boot 1855 id: 0C 1856 `) 1857 var mockBootloaderYaml = []byte(` 1858 volumes: 1859 volumename: 1860 schema: mbr 1861 bootloader: grub 1862 id: 0C 1863 `) 1864 var mockBadStructureSizeYaml = []byte(` 1865 volumes: 1866 volumename: 1867 schema: mbr 1868 bootloader: grub 1869 id: 0C 1870 structure: 1871 - name: bad-size 1872 size: 99999 1873 type: 0C 1874 `) 1875 for _, tc := range []struct { 1876 gadgetYaml []byte 1877 err string 1878 }{ 1879 {mockOtherYaml, `cannot find entry for volume "volumename" in updated gadget info`}, 1880 {mockManyYaml, "gadgets with multiple volumes are unsupported"}, 1881 {mockBadStructureSizeYaml, `cannot lay out the new volume: cannot lay out volume, structure #0 \("bad-size"\) size is not a multiple of sector size 512`}, 1882 {mockBadIDYaml, "incompatible layout change: incompatible ID change from 0C to 0D"}, 1883 {mockSchemaYaml, "incompatible layout change: incompatible schema change from mbr to gpt"}, 1884 {mockBootloaderYaml, "incompatible layout change: incompatible bootloader change from u-boot to grub"}, 1885 } { 1886 c.Logf("trying: %v\n", string(tc.gadgetYaml)) 1887 gi, err := gadget.InfoFromGadgetYaml(mockYaml, coreConstraints) 1888 c.Assert(err, IsNil) 1889 giNew, err := gadget.InfoFromGadgetYaml(tc.gadgetYaml, coreConstraints) 1890 c.Assert(err, IsNil) 1891 err = gadget.IsCompatible(gi, giNew) 1892 if tc.err == "" { 1893 c.Check(err, IsNil) 1894 } else { 1895 c.Check(err, ErrorMatches, tc.err) 1896 } 1897 1898 } 1899 } 1900 1901 func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleBadStructure(c *C) { 1902 var baseYaml = ` 1903 volumes: 1904 volumename: 1905 schema: gpt 1906 bootloader: grub 1907 id: 0C 1908 structure:` 1909 var mockYaml = baseYaml + ` 1910 - name: legit 1911 size: 2M 1912 type: 00000000-0000-0000-0000-0000deadbeef 1913 filesystem: ext4 1914 filesystem-label: fs-legit 1915 ` 1916 var mockBadStructureTypeYaml = baseYaml + ` 1917 - name: legit 1918 size: 2M 1919 type: 00000000-0000-0000-0000-0000deadcafe 1920 filesystem: ext4 1921 filesystem-label: fs-legit 1922 ` 1923 var mockBadFsYaml = baseYaml + ` 1924 - name: legit 1925 size: 2M 1926 type: 00000000-0000-0000-0000-0000deadbeef 1927 filesystem: vfat 1928 filesystem-label: fs-legit 1929 ` 1930 var mockBadOffsetYaml = baseYaml + ` 1931 - name: legit 1932 size: 2M 1933 type: 00000000-0000-0000-0000-0000deadbeef 1934 filesystem: ext4 1935 offset: 1M 1936 filesystem-label: fs-legit 1937 ` 1938 var mockBadLabelYaml = baseYaml + ` 1939 - name: legit 1940 size: 2M 1941 type: 00000000-0000-0000-0000-0000deadbeef 1942 filesystem: ext4 1943 filesystem-label: fs-non-legit 1944 ` 1945 var mockGPTBadNameYaml = baseYaml + ` 1946 - name: non-legit 1947 size: 2M 1948 type: 00000000-0000-0000-0000-0000deadbeef 1949 filesystem: ext4 1950 filesystem-label: fs-legit 1951 ` 1952 1953 for i, tc := range []struct { 1954 gadgetYaml string 1955 err string 1956 }{ 1957 {mockYaml, ``}, 1958 {mockBadStructureTypeYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change structure type from "00000000-0000-0000-0000-0000deadbeef" to "00000000-0000-0000-0000-0000deadcafe"`}, 1959 {mockBadFsYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change filesystem from "ext4" to "vfat"`}, 1960 {mockBadOffsetYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change structure offset from unspecified to 1048576`}, 1961 {mockBadLabelYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change filesystem label from "fs-legit" to "fs-non-legit"`}, 1962 {mockGPTBadNameYaml, `incompatible layout change: incompatible structure #0 \("non-legit"\) change: cannot change structure name from "legit" to "non-legit"`}, 1963 } { 1964 c.Logf("trying: %d %v\n", i, string(tc.gadgetYaml)) 1965 gi, err := gadget.InfoFromGadgetYaml([]byte(mockYaml), coreConstraints) 1966 c.Assert(err, IsNil) 1967 giNew, err := gadget.InfoFromGadgetYaml([]byte(tc.gadgetYaml), coreConstraints) 1968 c.Assert(err, IsNil) 1969 err = gadget.IsCompatible(gi, giNew) 1970 if tc.err == "" { 1971 c.Check(err, IsNil) 1972 } else { 1973 c.Check(err, ErrorMatches, tc.err) 1974 } 1975 1976 } 1977 } 1978 1979 func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleStructureNameMBR(c *C) { 1980 var baseYaml = ` 1981 volumes: 1982 volumename: 1983 schema: mbr 1984 bootloader: grub 1985 id: 0C 1986 structure:` 1987 var mockYaml = baseYaml + ` 1988 - name: legit 1989 size: 2M 1990 type: 0A 1991 ` 1992 var mockMBRNameOkYaml = baseYaml + ` 1993 - name: non-legit 1994 size: 2M 1995 type: 0A 1996 ` 1997 1998 gi, err := gadget.InfoFromGadgetYaml([]byte(mockYaml), coreConstraints) 1999 c.Assert(err, IsNil) 2000 giNew, err := gadget.InfoFromGadgetYaml([]byte(mockMBRNameOkYaml), coreConstraints) 2001 c.Assert(err, IsNil) 2002 err = gadget.IsCompatible(gi, giNew) 2003 c.Check(err, IsNil) 2004 } 2005 2006 type gadgetSizeTestSuite struct{} 2007 2008 var _ = Suite(&gadgetSizeTestSuite{}) 2009 2010 func (s *gadgetSizeTestSuite) TestIECString(c *C) { 2011 for _, tc := range []struct { 2012 size gadget.Size 2013 exp string 2014 }{ 2015 {512, "512 B"}, 2016 {1000, "1000 B"}, 2017 {1030, "1.01 KiB"}, 2018 {gadget.SizeKiB + 512, "1.50 KiB"}, 2019 {123 * gadget.SizeKiB, "123 KiB"}, 2020 {512 * gadget.SizeKiB, "512 KiB"}, 2021 {578 * gadget.SizeMiB, "578 MiB"}, 2022 {1*gadget.SizeGiB + 123*gadget.SizeMiB, "1.12 GiB"}, 2023 {1024 * gadget.SizeGiB, "1 TiB"}, 2024 {2 * 1024 * 1024 * 1024 * gadget.SizeGiB, "2048 PiB"}, 2025 } { 2026 c.Check(tc.size.IECString(), Equals, tc.exp) 2027 } 2028 }