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