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