github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/gadget/validate_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 "os" 26 "path/filepath" 27 "regexp" 28 "strings" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/gadget" 33 "github.com/snapcore/snapd/gadget/quantity" 34 "github.com/snapcore/snapd/kernel" 35 ) 36 37 type validateGadgetTestSuite struct { 38 dir string 39 } 40 41 var _ = Suite(&validateGadgetTestSuite{}) 42 43 func (s *validateGadgetTestSuite) SetUpTest(c *C) { 44 s.dir = c.MkDir() 45 } 46 47 func (s *validateGadgetTestSuite) TestRuleValidateStructureReservedLabels(c *C) { 48 for _, tc := range []struct { 49 role, label, err string 50 model gadget.Model 51 }{ 52 {label: "ubuntu-seed", err: `label "ubuntu-seed" is reserved`}, 53 {label: "ubuntu-data", err: `label "ubuntu-data" is reserved`}, 54 // ok to allow hybrid 20-ready devices 55 {label: "ubuntu-boot"}, 56 {label: "ubuntu-save"}, 57 // reserved only if seed present/expected 58 {label: "ubuntu-boot", err: `label "ubuntu-boot" is reserved`, model: uc20Mod}, 59 {label: "ubuntu-save", err: `label "ubuntu-save" is reserved`, model: uc20Mod}, 60 // these are ok 61 {role: "system-boot", label: "ubuntu-boot"}, 62 {label: "random-ubuntu-label"}, 63 } { 64 gi := &gadget.Info{ 65 Volumes: map[string]*gadget.Volume{ 66 "vol0": { 67 Structure: []gadget.VolumeStructure{{ 68 Type: "21686148-6449-6E6F-744E-656564454649", 69 Role: tc.role, 70 Filesystem: "ext4", 71 Label: tc.label, 72 Size: 10 * 1024, 73 }}, 74 }, 75 }, 76 } 77 err := gadget.Validate(gi, tc.model, nil) 78 if tc.err == "" { 79 c.Check(err, IsNil) 80 } else { 81 c.Check(err, ErrorMatches, ".*: "+tc.err) 82 } 83 } 84 85 } 86 87 // rolesYaml produces gadget metadata with volumes with structure withs the given 88 // role if data, seed or save are != "-", and with their label set to the value 89 func rolesYaml(c *C, data, seed, save string) *gadget.Info { 90 h := `volumes: 91 roles: 92 schema: gpt 93 bootloader: grub 94 structure: 95 ` 96 if data != "-" { 97 h += ` 98 - name: data 99 size: 1G 100 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 101 role: system-data 102 ` 103 if data != "" { 104 h += fmt.Sprintf(" filesystem-label: %s\n", data) 105 } 106 } 107 if seed != "-" { 108 h += ` 109 - name: seed 110 size: 1G 111 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 112 role: system-seed 113 ` 114 if seed != "" { 115 h += fmt.Sprintf(" filesystem-label: %s\n", seed) 116 } 117 } 118 119 if save != "-" { 120 h += ` 121 - name: save 122 size: 32M 123 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 124 role: system-save 125 ` 126 if save != "" { 127 h += fmt.Sprintf(" filesystem-label: %s\n", save) 128 } 129 } 130 131 gi, err := gadget.InfoFromGadgetYaml([]byte(h), nil) 132 c.Assert(err, IsNil) 133 return gi 134 } 135 136 func (s *validateGadgetTestSuite) TestVolumeRulesConsistencyNoModel(c *C) { 137 ginfo := func(hasSeed bool, dataLabel string) *gadget.Info { 138 seed := "-" 139 if hasSeed { 140 seed = "" 141 } 142 return rolesYaml(c, dataLabel, seed, "-") 143 } 144 145 for i, tc := range []struct { 146 gi *gadget.Info 147 err string 148 }{ 149 150 // we have the system-seed role 151 {ginfo(true, ""), ""}, 152 {ginfo(true, "foobar"), `.* must have an implicit label or "ubuntu-data", not "foobar"`}, 153 {ginfo(true, "writable"), `.* must have an implicit label or "ubuntu-data", not "writable"`}, 154 {ginfo(true, "ubuntu-data"), ""}, 155 156 // we don't have the system-seed role (old systems) 157 {ginfo(false, ""), ""}, // implicit is ok 158 {ginfo(false, "foobar"), `.* must have an implicit label or "writable", not "foobar"`}, 159 {ginfo(false, "writable"), ""}, 160 {ginfo(false, "ubuntu-data"), `.* must have an implicit label or "writable", not "ubuntu-data"`}, 161 } { 162 c.Logf("tc: %d %v", i, tc.gi.Volumes["roles"]) 163 164 err := gadget.Validate(tc.gi, nil, nil) 165 if tc.err != "" { 166 c.Check(err, ErrorMatches, tc.err) 167 } else { 168 c.Check(err, IsNil) 169 } 170 } 171 172 // Check system-seed label 173 for i, tc := range []struct { 174 l string 175 err string 176 }{ 177 {"", ""}, 178 {"foobar", `system-seed structure must have an implicit label or "ubuntu-seed", not "foobar"`}, 179 {"ubuntu-seed", ""}, 180 } { 181 c.Logf("tc: %v %v", i, tc.l) 182 gi := rolesYaml(c, "", tc.l, "-") 183 err := gadget.Validate(gi, nil, nil) 184 if tc.err != "" { 185 c.Check(err, ErrorMatches, tc.err) 186 } else { 187 c.Check(err, IsNil) 188 } 189 } 190 191 // Check system-seed without system-data 192 gi := rolesYaml(c, "-", "-", "-") 193 err := gadget.Validate(gi, nil, nil) 194 c.Assert(err, IsNil) 195 gi = rolesYaml(c, "-", "", "-") 196 err = gadget.Validate(gi, nil, nil) 197 c.Assert(err, ErrorMatches, "the system-seed role requires system-data to be defined") 198 199 // Check system-save 200 giWithSave := rolesYaml(c, "", "", "") 201 err = gadget.Validate(giWithSave, nil, nil) 202 c.Assert(err, IsNil) 203 // use illegal label on system-save 204 giWithSave = rolesYaml(c, "", "", "foo") 205 err = gadget.Validate(giWithSave, nil, nil) 206 c.Assert(err, ErrorMatches, `system-save structure must have an implicit label or "ubuntu-save", not "foo"`) 207 // complains when save is alone 208 giWithSave = rolesYaml(c, "", "-", "") 209 err = gadget.Validate(giWithSave, nil, nil) 210 c.Assert(err, ErrorMatches, "model does not support the system-save role") 211 giWithSave = rolesYaml(c, "-", "-", "") 212 err = gadget.Validate(giWithSave, nil, nil) 213 c.Assert(err, ErrorMatches, "model does not support the system-save role") 214 } 215 216 func (s *validateGadgetTestSuite) TestValidateConsistencyWithoutModelCharateristics(c *C) { 217 for i, tc := range []struct { 218 role string 219 label string 220 err string 221 }{ 222 // when model is nil, the system-seed role and ubuntu-data label on the 223 // system-data structure should be consistent 224 {"system-seed", "", ""}, 225 {"system-seed", "writable", `must have an implicit label or "ubuntu-data", not "writable"`}, 226 {"system-seed", "ubuntu-data", ""}, 227 {"", "", ""}, 228 {"", "writable", ""}, 229 {"", "ubuntu-data", `must have an implicit label or "writable", not "ubuntu-data"`}, 230 } { 231 c.Logf("tc: %v %v %v", i, tc.role, tc.label) 232 b := &bytes.Buffer{} 233 234 fmt.Fprintf(b, ` 235 volumes: 236 pc: 237 bootloader: grub 238 schema: mbr 239 structure:`) 240 241 if tc.role == "system-seed" { 242 fmt.Fprintf(b, ` 243 - name: Recovery 244 size: 10M 245 type: 83 246 role: system-seed`) 247 } 248 249 fmt.Fprintf(b, ` 250 - name: Data 251 size: 10M 252 type: 83 253 role: system-data 254 filesystem-label: %s`, tc.label) 255 256 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes()) 257 ginfo, err := gadget.ReadInfo(s.dir, nil) 258 c.Assert(err, IsNil) 259 err = gadget.Validate(ginfo, nil, nil) 260 if tc.err != "" { 261 c.Check(err, ErrorMatches, ".* "+tc.err) 262 } else { 263 c.Check(err, IsNil) 264 } 265 } 266 } 267 268 func (s *validateGadgetTestSuite) TestValidateConsistencyWithModelCharateristics(c *C) { 269 bloader := ` 270 volumes: 271 pc: 272 bootloader: grub 273 schema: mbr 274 structure:` 275 276 for i, tc := range []struct { 277 addSeed bool 278 dataLabel string 279 noData bool 280 requireSeed bool 281 addSave bool 282 saveLabel string 283 err string 284 }{ 285 {addSeed: true, noData: true, requireSeed: true, err: "the system-seed role requires system-data to be defined"}, 286 {addSeed: true, noData: true, requireSeed: false, err: "the system-seed role requires system-data to be defined"}, 287 {addSeed: true, requireSeed: true}, 288 {addSeed: true, err: `model does not support the system-seed role`}, 289 {addSeed: true, dataLabel: "writable", requireSeed: true, 290 err: `system-data structure must have an implicit label or "ubuntu-data", not "writable"`}, 291 {addSeed: true, dataLabel: "writable", 292 err: `model does not support the system-seed role`}, 293 {addSeed: true, dataLabel: "ubuntu-data", requireSeed: true}, 294 {addSeed: true, dataLabel: "ubuntu-data", 295 err: `model does not support the system-seed role`}, 296 {dataLabel: "writable", requireSeed: true, 297 err: `model requires system-seed structure, but none was found`}, 298 {dataLabel: "writable"}, 299 {dataLabel: "ubuntu-data", requireSeed: true, 300 err: `model requires system-seed structure, but none was found`}, 301 {dataLabel: "ubuntu-data", err: `system-data structure must have an implicit label or "writable", not "ubuntu-data"`}, 302 {addSave: true, requireSeed: true, addSeed: true}, 303 {addSave: true, err: `model does not support the system-save role`}, 304 {addSeed: true, requireSeed: true, addSave: true, saveLabel: "foo", 305 err: `system-save structure must have an implicit label or "ubuntu-save", not "foo"`}, 306 } { 307 c.Logf("tc: %v %v %v %v", i, tc.addSeed, tc.dataLabel, tc.requireSeed) 308 b := &bytes.Buffer{} 309 310 fmt.Fprintf(b, bloader) 311 if tc.addSeed { 312 fmt.Fprintf(b, ` 313 - name: Recovery 314 size: 10M 315 type: 83 316 role: system-seed`) 317 } 318 319 if !tc.noData { 320 fmt.Fprintf(b, ` 321 - name: Data 322 size: 10M 323 type: 83 324 role: system-data 325 filesystem-label: %s`, tc.dataLabel) 326 } 327 328 if tc.addSave { 329 fmt.Fprintf(b, ` 330 - name: Save 331 size: 10M 332 type: 83 333 role: system-save`) 334 if tc.saveLabel != "" { 335 fmt.Fprintf(b, ` 336 filesystem-label: %s`, tc.saveLabel) 337 338 } 339 } 340 341 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes()) 342 343 mod := &modelCharateristics{ 344 classic: false, 345 systemSeed: tc.requireSeed, 346 } 347 ginfo, err := gadget.ReadInfo(s.dir, mod) 348 c.Assert(err, IsNil) 349 err = gadget.Validate(ginfo, mod, nil) 350 if tc.err != "" { 351 c.Check(err, ErrorMatches, tc.err) 352 } else { 353 c.Check(err, IsNil) 354 } 355 } 356 357 // test error with no volumes 358 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(bloader)) 359 360 mod := &modelCharateristics{ 361 systemSeed: true, 362 } 363 364 ginfo, err := gadget.ReadInfo(s.dir, mod) 365 c.Assert(err, IsNil) 366 err = gadget.Validate(ginfo, mod, nil) 367 c.Assert(err, ErrorMatches, "model requires system-seed partition, but no system-seed or system-data partition found") 368 } 369 370 func (s *validateGadgetTestSuite) TestValidateSystemRoleSplitAcrossVolumes(c *C) { 371 // ATM this is not allowed for UC20 372 const gadgetYamlContent = ` 373 volumes: 374 pc1: 375 # bootloader configuration is shipped and managed by snapd 376 bootloader: grub 377 structure: 378 - name: mbr 379 type: mbr 380 size: 440 381 update: 382 edition: 1 383 content: 384 - image: pc-boot.img 385 - name: BIOS Boot 386 type: DA,21686148-6449-6E6F-744E-656564454649 387 size: 1M 388 offset: 1M 389 offset-write: mbr+92 390 update: 391 edition: 2 392 content: 393 - image: pc-core.img 394 - name: ubuntu-seed 395 role: system-seed 396 filesystem: vfat 397 # UEFI will boot the ESP partition by default first 398 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 399 size: 1200M 400 update: 401 edition: 2 402 content: 403 - source: grubx64.efi 404 target: EFI/boot/grubx64.efi 405 - source: shim.efi.signed 406 target: EFI/boot/bootx64.efi 407 - name: ubuntu-boot 408 role: system-boot 409 filesystem: ext4 410 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 411 # whats the appropriate size? 412 size: 750M 413 update: 414 edition: 1 415 content: 416 - source: grubx64.efi 417 target: EFI/boot/grubx64.efi 418 - source: shim.efi.signed 419 target: EFI/boot/bootx64.efi 420 pc2: 421 structure: 422 - name: ubuntu-save 423 role: system-save 424 filesystem: ext4 425 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 426 size: 16M 427 - name: ubuntu-data 428 role: system-data 429 filesystem: ext4 430 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 431 size: 1G 432 ` 433 434 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 435 436 ginfo, err := gadget.ReadInfo(s.dir, nil) 437 c.Assert(err, IsNil) 438 err = gadget.Validate(ginfo, nil, nil) 439 c.Assert(err, ErrorMatches, `system-boot, system-data, and system-save are expected to share the same volume as system-seed`) 440 } 441 442 func (s *validateGadgetTestSuite) TestValidateRoleDuplicated(c *C) { 443 444 for _, role := range []string{"system-seed", "system-data", "system-boot", "system-save"} { 445 gadgetYamlContent := fmt.Sprintf(` 446 volumes: 447 pc: 448 bootloader: grub 449 structure: 450 - name: foo 451 type: DA,21686148-6449-6E6F-744E-656564454649 452 size: 1M 453 role: %[1]s 454 - name: bar 455 type: DA,21686148-6449-6E6F-744E-656564454649 456 size: 1M 457 role: %[1]s 458 `, role) 459 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 460 461 ginfo, err := gadget.ReadInfo(s.dir, nil) 462 c.Assert(err, IsNil) 463 err = gadget.Validate(ginfo, nil, nil) 464 c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot have more than one partition with %s role`, role)) 465 } 466 } 467 468 func (s *validateGadgetTestSuite) TestValidateSystemSeedRoleTwiceAcrossVolumes(c *C) { 469 470 for _, role := range []string{"system-seed", "system-data", "system-boot", "system-save"} { 471 gadgetYamlContent := fmt.Sprintf(` 472 volumes: 473 pc: 474 bootloader: grub 475 structure: 476 - name: foo 477 type: DA,21686148-6449-6E6F-744E-656564454649 478 size: 1M 479 role: %[1]s 480 other: 481 structure: 482 - name: bar 483 type: DA,21686148-6449-6E6F-744E-656564454649 484 size: 1M 485 role: %[1]s 486 `, role) 487 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 488 489 ginfo, err := gadget.ReadInfo(s.dir, nil) 490 c.Assert(err, IsNil) 491 err = gadget.Validate(ginfo, nil, nil) 492 c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot have more than one partition with %s role across volumes`, role)) 493 } 494 } 495 496 func (s *validateGadgetTestSuite) TestRuleValidateHybridGadget(c *C) { 497 // this is the kind of volumes setup recommended to be 498 // prepared for a possible UC18 -> UC20 transition 499 hybridyGadgetYaml := []byte(`volumes: 500 hybrid: 501 bootloader: grub 502 structure: 503 - name: mbr 504 type: mbr 505 size: 440 506 content: 507 - image: pc-boot.img 508 - name: BIOS Boot 509 type: DA,21686148-6449-6E6F-744E-656564454649 510 size: 1M 511 offset: 1M 512 offset-write: mbr+92 513 content: 514 - image: pc-core.img 515 - name: EFI System 516 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 517 filesystem: vfat 518 filesystem-label: system-boot 519 size: 1200M 520 content: 521 - source: grubx64.efi 522 target: EFI/boot/grubx64.efi 523 - source: shim.efi.signed 524 target: EFI/boot/bootx64.efi 525 - source: mmx64.efi 526 target: EFI/boot/mmx64.efi 527 - source: grub.cfg 528 target: EFI/ubuntu/grub.cfg 529 - name: Ubuntu Boot 530 type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 531 filesystem: ext4 532 filesystem-label: ubuntu-boot 533 size: 750M 534 `) 535 536 mod := &modelCharateristics{ 537 classic: false, 538 } 539 giMeta, err := gadget.InfoFromGadgetYaml(hybridyGadgetYaml, mod) 540 c.Assert(err, IsNil) 541 542 err = gadget.Validate(giMeta, mod, nil) 543 c.Check(err, IsNil) 544 } 545 546 func (s *validateGadgetTestSuite) TestRuleValidateHybridGadgetBrokenDupRole(c *C) { 547 // this is consistency-wise broken because of the duplicated 548 // system-boot role, of which one is implicit 549 brokenGadgetYaml := []byte(`volumes: 550 hybrid: 551 bootloader: grub 552 structure: 553 - name: mbr 554 type: mbr 555 size: 440 556 content: 557 - image: pc-boot.img 558 - name: BIOS Boot 559 type: DA,21686148-6449-6E6F-744E-656564454649 560 size: 1M 561 offset: 1M 562 offset-write: mbr+92 563 content: 564 - image: pc-core.img 565 - name: EFI System 566 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 567 filesystem: vfat 568 filesystem-label: system-boot 569 size: 1200M 570 content: 571 - source: grubx64.efi 572 target: EFI/boot/grubx64.efi 573 - source: shim.efi.signed 574 target: EFI/boot/bootx64.efi 575 - source: mmx64.efi 576 target: EFI/boot/mmx64.efi 577 - source: grub.cfg 578 target: EFI/ubuntu/grub.cfg 579 - name: Ubuntu Boot 580 type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 581 filesystem: ext4 582 filesystem-label: ubuntu-boot 583 role: system-boot 584 size: 750M 585 `) 586 587 mod := &modelCharateristics{ 588 classic: false, 589 } 590 giMeta, err := gadget.InfoFromGadgetYaml(brokenGadgetYaml, mod) 591 c.Assert(err, IsNil) 592 593 err = gadget.Validate(giMeta, mod, nil) 594 c.Check(err, ErrorMatches, `cannot have more than one partition with system-boot role`) 595 } 596 597 func (s *validateGadgetTestSuite) TestValidateContentMissingRawContent(c *C) { 598 var gadgetYamlContent = ` 599 volumes: 600 pc: 601 bootloader: grub 602 structure: 603 - name: foo 604 type: DA,21686148-6449-6E6F-744E-656564454649 605 size: 1M 606 offset: 1M 607 content: 608 - image: foo.img 609 610 ` 611 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 612 613 ginfo, err := gadget.ReadInfo(s.dir, nil) 614 c.Assert(err, IsNil) 615 err = gadget.ValidateContent(ginfo, s.dir, "") 616 c.Assert(err, ErrorMatches, `invalid layout of volume "pc": cannot lay out structure #0 \("foo"\): content "foo.img": stat .*/foo.img: no such file or directory`) 617 } 618 619 func (s *validateGadgetTestSuite) TestValidateContentMultiVolumeContent(c *C) { 620 var gadgetYamlContent = ` 621 volumes: 622 first: 623 bootloader: grub 624 structure: 625 - name: first-foo 626 type: DA,21686148-6449-6E6F-744E-656564454649 627 size: 1M 628 content: 629 - image: first.img 630 second: 631 structure: 632 - name: second-foo 633 type: DA,21686148-6449-6E6F-744E-656564454649 634 size: 1M 635 content: 636 - image: second.img 637 638 ` 639 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 640 // only content for the first volume 641 makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil) 642 643 ginfo, err := gadget.ReadInfo(s.dir, nil) 644 c.Assert(err, IsNil) 645 err = gadget.ValidateContent(ginfo, s.dir, "") 646 c.Assert(err, ErrorMatches, `invalid layout of volume "second": cannot lay out structure #0 \("second-foo"\): content "second.img": stat .*/second.img: no such file or directory`) 647 } 648 649 func (s *validateGadgetTestSuite) TestValidateContentFilesystemContent(c *C) { 650 var gadgetYamlContent = ` 651 volumes: 652 bad: 653 bootloader: grub 654 structure: 655 - name: bad-struct 656 type: DA,21686148-6449-6E6F-744E-656564454649 657 size: 1M 658 filesystem: ext4 659 content: 660 - source: foo/ 661 target: / 662 663 ` 664 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 665 666 ginfo, err := gadget.ReadInfo(s.dir, nil) 667 c.Assert(err, IsNil) 668 err = gadget.ValidateContent(ginfo, s.dir, "") 669 c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: source path does not exist`) 670 671 // make it a file, which conflicts with foo/ as 'source' 672 fooPath := filepath.Join(s.dir, "foo") 673 makeSizedFile(c, fooPath, 1, nil) 674 err = gadget.ValidateContent(ginfo, s.dir, "") 675 c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: cannot specify trailing / for a source which is not a directory`) 676 677 // make it a directory 678 err = os.Remove(fooPath) 679 c.Assert(err, IsNil) 680 err = os.Mkdir(fooPath, 0755) 681 c.Assert(err, IsNil) 682 // validate should no longer complain 683 err = gadget.ValidateContent(ginfo, s.dir, "") 684 c.Assert(err, IsNil) 685 } 686 687 var gadgetYamlContentNoSave = ` 688 volumes: 689 vol1: 690 bootloader: grub 691 structure: 692 - name: ubuntu-seed 693 role: system-seed 694 type: DA,21686148-6449-6E6F-744E-656564454649 695 size: 1M 696 filesystem: ext4 697 - name: ubuntu-boot 698 type: DA,21686148-6449-6E6F-744E-656564454649 699 size: 1M 700 filesystem: ext4 701 - name: ubuntu-data 702 role: system-data 703 type: DA,21686148-6449-6E6F-744E-656564454649 704 size: 1M 705 filesystem: ext4 706 ` 707 708 var gadgetYamlContentWithSave = gadgetYamlContentNoSave + ` 709 - name: ubuntu-save 710 role: system-save 711 type: DA,21686148-6449-6E6F-744E-656564454649 712 size: 1M 713 filesystem: ext4 714 ` 715 716 func (s *validateGadgetTestSuite) TestValidateEncryptionSupportErr(c *C) { 717 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentNoSave)) 718 719 mod := &modelCharateristics{systemSeed: true} 720 ginfo, err := gadget.ReadInfo(s.dir, mod) 721 c.Assert(err, IsNil) 722 err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{ 723 EncryptedData: true, 724 }) 725 c.Assert(err, ErrorMatches, `gadget does not support encrypted data: volume "vol1" has no structure with system-save role`) 726 } 727 728 func (s *validateGadgetTestSuite) TestValidateEncryptionSupportHappy(c *C) { 729 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentWithSave)) 730 mod := &modelCharateristics{systemSeed: true} 731 ginfo, err := gadget.ReadInfo(s.dir, mod) 732 c.Assert(err, IsNil) 733 err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{ 734 EncryptedData: true, 735 }) 736 c.Assert(err, IsNil) 737 } 738 739 var gadgetYamlContentKernelRef = gadgetYamlContentNoSave + ` 740 - name: other 741 type: DA,21686148-6449-6E6F-744E-656564454649 742 size: 10M 743 filesystem: ext4 744 content: 745 - source: REPLACE_WITH_TC 746 target: / 747 ` 748 749 func (s *validateGadgetTestSuite) TestValidateContentKernelAssetsRef(c *C) { 750 for _, tc := range []struct { 751 source, asset, content string 752 good bool 753 }{ 754 {"$kernel:a/b", "a", "b", true}, 755 {"$kernel:A/b", "A", "b", true}, 756 {"$kernel:a-a/bb", "a-a", "bb", true}, 757 {"$kernel:a-a/b-b", "a-a", "b-b", true}, 758 {"$kernel:aB-0/cD-3", "aB-0", "cD-3", true}, 759 {"$kernel:aB-0/foo-21B.dtb", "aB-0", "foo-21B.dtb", true}, 760 {"$kernel:aB-0/nested/bar-77A.raw", "aB-0", "nested/bar-77A.raw", true}, 761 {"$kernel:a/a/", "a", "a/", true}, 762 // no starting with "-" 763 {source: "$kernel:-/-"}, 764 // assets and content need to be there 765 {source: "$kernel:ab"}, 766 {source: "$kernel:/"}, 767 {source: "$kernel:a/"}, 768 {source: "$kernel:/a"}, 769 // invalid asset name 770 {source: "$kernel:#garbage/a"}, 771 // invalid content part 772 {source: "$kernel:a//"}, 773 {source: "$kernel:a///"}, 774 {source: "$kernel:a////"}, 775 {source: "$kernel:a/a/../"}, 776 } { 777 gadgetYaml := strings.Replace(gadgetYamlContentKernelRef, "REPLACE_WITH_TC", tc.source, -1) 778 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYaml)) 779 ginfo, err := gadget.ReadInfoAndValidate(s.dir, nil, nil) 780 c.Assert(err, IsNil) 781 err = gadget.ValidateContent(ginfo, s.dir, "") 782 if tc.good { 783 c.Check(err, IsNil, Commentf(tc.source)) 784 // asset validates correctly, so let's make sure that 785 // individual pieces are correct too 786 assetName, content, err := gadget.SplitKernelRef(tc.source) 787 c.Assert(err, IsNil) 788 c.Check(assetName, Equals, tc.asset) 789 c.Check(content, Equals, tc.content) 790 } else { 791 errStr := fmt.Sprintf(`invalid volume "vol1": cannot use kernel reference "%s": .*`, regexp.QuoteMeta(tc.source)) 792 c.Check(err, ErrorMatches, errStr, Commentf(tc.source)) 793 } 794 } 795 } 796 797 func (s *validateGadgetTestSuite) TestSplitKernelRefErrors(c *C) { 798 for _, tc := range []struct { 799 kernelRef string 800 errStr string 801 }{ 802 {"no-kernel-ref", `internal error: splitKernelRef called for non kernel ref "no-kernel-ref"`}, 803 {"$kernel:a", `invalid asset and content in kernel ref "\$kernel:a"`}, 804 {"$kernel:a/", `missing asset name or content in kernel ref "\$kernel:a/"`}, 805 {"$kernel:/b", `missing asset name or content in kernel ref "\$kernel:/b"`}, 806 {"$kernel:a!invalid/b", `invalid asset name in kernel ref "\$kernel:a!invalid/b"`}, 807 {"$kernel:a/b/..", `invalid content in kernel ref "\$kernel:a/b/.."`}, 808 {"$kernel:a/b//", `invalid content in kernel ref "\$kernel:a/b//"`}, 809 {"$kernel:a/b/./", `invalid content in kernel ref "\$kernel:a/b/./"`}, 810 } { 811 _, _, err := gadget.SplitKernelRef(tc.kernelRef) 812 c.Check(err, ErrorMatches, tc.errStr, Commentf("kernelRef: %s", tc.kernelRef)) 813 } 814 } 815 816 func (s *validateGadgetTestSuite) TestCanResolveOneVolumeKernelRef(c *C) { 817 lv := &gadget.LaidOutVolume{ 818 Volume: &gadget.Volume{ 819 Bootloader: "grub", 820 Schema: "gpt", 821 Structure: []gadget.VolumeStructure{ 822 { 823 Name: "foo", 824 Size: 5 * quantity.SizeMiB, 825 Filesystem: "ext4", 826 }, 827 }, 828 }, 829 } 830 831 contentNoKernelRef := []gadget.VolumeContent{ 832 {UnresolvedSource: "/content", Target: "/"}, 833 } 834 contentOneKernelRef := []gadget.VolumeContent{ 835 {UnresolvedSource: "/content", Target: "/"}, 836 {UnresolvedSource: "$kernel:ref/foo", Target: "/"}, 837 } 838 contentTwoKernelRefs := []gadget.VolumeContent{ 839 {UnresolvedSource: "/content", Target: "/"}, 840 {UnresolvedSource: "$kernel:ref/foo", Target: "/"}, 841 {UnresolvedSource: "$kernel:ref2/bar", Target: "/"}, 842 } 843 844 kInfoNoRefs := &kernel.Info{} 845 kInfoOneRefButUpdateFlagFalse := &kernel.Info{ 846 Assets: map[string]*kernel.Asset{ 847 "ref": { 848 // note that update is false here 849 Update: false, 850 Content: []string{"some-file"}, 851 }, 852 }, 853 } 854 kInfoOneRef := &kernel.Info{ 855 Assets: map[string]*kernel.Asset{ 856 "ref": { 857 Update: true, 858 Content: []string{"some-file"}, 859 }, 860 }, 861 } 862 kInfoOneRefDifferentName := &kernel.Info{ 863 Assets: map[string]*kernel.Asset{ 864 "ref-other": { 865 Update: true, 866 Content: []string{"some-file"}, 867 }, 868 }, 869 } 870 kInfoTwoRefs := &kernel.Info{ 871 Assets: map[string]*kernel.Asset{ 872 "ref": { 873 Update: true, 874 Content: []string{"some-file"}, 875 }, 876 "ref2": { 877 Update: true, 878 Content: []string{"other-file"}, 879 }, 880 }, 881 } 882 883 for _, tc := range []struct { 884 volumeContent []gadget.VolumeContent 885 kinfo *kernel.Info 886 expectedErr string 887 }{ 888 // happy case: trivial 889 {contentNoKernelRef, kInfoNoRefs, ""}, 890 891 // happy case: if kernel asset has "Update: false" 892 {contentNoKernelRef, kInfoOneRefButUpdateFlagFalse, ""}, 893 894 // unhappy case: kernel has one or more unresolved references in gadget 895 {contentNoKernelRef, kInfoOneRef, `gadget does not consume any of the kernel assets needing synced update "ref"`}, 896 {contentNoKernelRef, kInfoTwoRefs, `gadget does not consume any of the kernel assets needing synced update "ref", "ref2"`}, 897 898 // unhappy case: gadget needs different asset than kernel provides 899 {contentOneKernelRef, kInfoOneRefDifferentName, `gadget does not consume any of the kernel assets needing synced update "ref-other"`}, 900 901 // happy case: exactly one matching kernel ref 902 {contentOneKernelRef, kInfoOneRef, ""}, 903 // happy case: one matching, one missing kernel ref, still considered fine 904 {contentTwoKernelRefs, kInfoTwoRefs, ""}, 905 } { 906 lv.Structure[0].Content = tc.volumeContent 907 err := gadget.GadgetVolumeConsumesOneKernelUpdateAsset(lv.Volume, tc.kinfo) 908 if tc.expectedErr == "" { 909 c.Check(err, IsNil, Commentf("should not fail %v", tc.volumeContent)) 910 } else { 911 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("should fail %v", tc.volumeContent)) 912 } 913 } 914 } 915 916 func (s *validateGadgetTestSuite) TestValidateContentKernelRefMissing(c *C) { 917 var gadgetYamlContent = ` 918 volumes: 919 first: 920 bootloader: grub 921 structure: 922 - name: first-foo 923 type: DA,21686148-6449-6E6F-744E-656564454649 924 size: 1M 925 content: 926 - image: first.img 927 second: 928 structure: 929 - name: second-foo 930 filesystem: ext4 931 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 932 size: 128M 933 content: 934 - source: $kernel:ref/foo 935 target: / 936 ` 937 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 938 makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil) 939 940 // note that there is no kernel.yaml 941 kernelUnpackDir := c.MkDir() 942 943 ginfo, err := gadget.ReadInfo(s.dir, nil) 944 c.Assert(err, IsNil) 945 err = gadget.ValidateContent(ginfo, s.dir, kernelUnpackDir) 946 c.Assert(err, ErrorMatches, `.*cannot find "ref" in kernel info.*`) 947 } 948 949 func (s *validateGadgetTestSuite) TestValidateContentKernelRefNotInGadget(c *C) { 950 var gadgetYamlContent = ` 951 volumes: 952 first: 953 bootloader: grub 954 structure: 955 - name: first-foo 956 type: DA,21686148-6449-6E6F-744E-656564454649 957 size: 1M 958 content: 959 - image: first.img 960 second: 961 structure: 962 - name: second-foo 963 filesystem: ext4 964 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 965 size: 128M 966 content: 967 - source: foo 968 target: / 969 ` 970 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 971 makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil) 972 makeSizedFile(c, filepath.Join(s.dir, "foo"), 1, nil) 973 974 kernelUnpackDir := c.MkDir() 975 kernelYamlContent := ` 976 assets: 977 ref: 978 update: true 979 content: 980 - dtbs/` 981 makeSizedFile(c, filepath.Join(kernelUnpackDir, "meta/kernel.yaml"), 0, []byte(kernelYamlContent)) 982 makeSizedFile(c, filepath.Join(kernelUnpackDir, "dtbs/foo.dtb"), 0, []byte("foo.dtb content")) 983 984 ginfo, err := gadget.ReadInfo(s.dir, nil) 985 c.Assert(err, IsNil) 986 err = gadget.ValidateContent(ginfo, s.dir, kernelUnpackDir) 987 c.Assert(err, ErrorMatches, `no asset from the kernel.yaml needing synced update is consumed by the gadget at "/.*"`) 988 }