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