github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/gadget/validate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2021 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: required partition with system-save role is missing`) 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 func (s *validateGadgetTestSuite) TestValidateEncryptionSupportNoUC20(c *C) { 740 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlPC)) 741 742 mod := &modelCharateristics{systemSeed: false} 743 ginfo, err := gadget.ReadInfo(s.dir, mod) 744 c.Assert(err, IsNil) 745 err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{ 746 EncryptedData: true, 747 }) 748 c.Assert(err, ErrorMatches, `internal error: cannot support encrypted data without requiring system-seed`) 749 } 750 751 func (s *validateGadgetTestSuite) TestValidateEncryptionSupportMultiVolumeHappy(c *C) { 752 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(mockMultiVolumeUC20GadgetYaml)) 753 mod := &modelCharateristics{systemSeed: true} 754 ginfo, err := gadget.ReadInfo(s.dir, mod) 755 c.Assert(err, IsNil) 756 err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{ 757 EncryptedData: true, 758 }) 759 c.Assert(err, IsNil) 760 } 761 762 var gadgetYamlContentKernelRef = gadgetYamlContentNoSave + ` 763 - name: other 764 type: DA,21686148-6449-6E6F-744E-656564454649 765 size: 10M 766 filesystem: ext4 767 content: 768 - source: REPLACE_WITH_TC 769 target: / 770 ` 771 772 func (s *validateGadgetTestSuite) TestValidateContentKernelAssetsRef(c *C) { 773 for _, tc := range []struct { 774 source, asset, content string 775 good bool 776 }{ 777 {"$kernel:a/b", "a", "b", true}, 778 {"$kernel:A/b", "A", "b", true}, 779 {"$kernel:a-a/bb", "a-a", "bb", true}, 780 {"$kernel:a-a/b-b", "a-a", "b-b", true}, 781 {"$kernel:aB-0/cD-3", "aB-0", "cD-3", true}, 782 {"$kernel:aB-0/foo-21B.dtb", "aB-0", "foo-21B.dtb", true}, 783 {"$kernel:aB-0/nested/bar-77A.raw", "aB-0", "nested/bar-77A.raw", true}, 784 {"$kernel:a/a/", "a", "a/", true}, 785 // no starting with "-" 786 {source: "$kernel:-/-"}, 787 // assets and content need to be there 788 {source: "$kernel:ab"}, 789 {source: "$kernel:/"}, 790 {source: "$kernel:a/"}, 791 {source: "$kernel:/a"}, 792 // invalid asset name 793 {source: "$kernel:#garbage/a"}, 794 // invalid content part 795 {source: "$kernel:a//"}, 796 {source: "$kernel:a///"}, 797 {source: "$kernel:a////"}, 798 {source: "$kernel:a/a/../"}, 799 } { 800 gadgetYaml := strings.Replace(gadgetYamlContentKernelRef, "REPLACE_WITH_TC", tc.source, -1) 801 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYaml)) 802 ginfo, err := gadget.ReadInfoAndValidate(s.dir, nil, nil) 803 c.Assert(err, IsNil) 804 err = gadget.ValidateContent(ginfo, s.dir, "") 805 if tc.good { 806 c.Check(err, IsNil, Commentf(tc.source)) 807 // asset validates correctly, so let's make sure that 808 // individual pieces are correct too 809 assetName, content, err := gadget.SplitKernelRef(tc.source) 810 c.Assert(err, IsNil) 811 c.Check(assetName, Equals, tc.asset) 812 c.Check(content, Equals, tc.content) 813 } else { 814 errStr := fmt.Sprintf(`invalid volume "vol1": cannot use kernel reference "%s": .*`, regexp.QuoteMeta(tc.source)) 815 c.Check(err, ErrorMatches, errStr, Commentf(tc.source)) 816 } 817 } 818 } 819 820 func (s *validateGadgetTestSuite) TestSplitKernelRefErrors(c *C) { 821 for _, tc := range []struct { 822 kernelRef string 823 errStr string 824 }{ 825 {"no-kernel-ref", `internal error: splitKernelRef called for non kernel ref "no-kernel-ref"`}, 826 {"$kernel:a", `invalid asset and content in kernel ref "\$kernel:a"`}, 827 {"$kernel:a/", `missing asset name or content in kernel ref "\$kernel:a/"`}, 828 {"$kernel:/b", `missing asset name or content in kernel ref "\$kernel:/b"`}, 829 {"$kernel:a!invalid/b", `invalid asset name in kernel ref "\$kernel:a!invalid/b"`}, 830 {"$kernel:a/b/..", `invalid content in kernel ref "\$kernel:a/b/.."`}, 831 {"$kernel:a/b//", `invalid content in kernel ref "\$kernel:a/b//"`}, 832 {"$kernel:a/b/./", `invalid content in kernel ref "\$kernel:a/b/./"`}, 833 } { 834 _, _, err := gadget.SplitKernelRef(tc.kernelRef) 835 c.Check(err, ErrorMatches, tc.errStr, Commentf("kernelRef: %s", tc.kernelRef)) 836 } 837 } 838 839 func (s *validateGadgetTestSuite) TestCanResolveOneVolumeKernelRef(c *C) { 840 lv := &gadget.LaidOutVolume{ 841 Volume: &gadget.Volume{ 842 Bootloader: "grub", 843 Schema: "gpt", 844 Structure: []gadget.VolumeStructure{ 845 { 846 Name: "foo", 847 Size: 5 * quantity.SizeMiB, 848 Filesystem: "ext4", 849 }, 850 }, 851 }, 852 } 853 854 contentNoKernelRef := []gadget.VolumeContent{ 855 {UnresolvedSource: "/content", Target: "/"}, 856 } 857 contentOneKernelRef := []gadget.VolumeContent{ 858 {UnresolvedSource: "/content", Target: "/"}, 859 {UnresolvedSource: "$kernel:ref/foo", Target: "/"}, 860 } 861 contentTwoKernelRefs := []gadget.VolumeContent{ 862 {UnresolvedSource: "/content", Target: "/"}, 863 {UnresolvedSource: "$kernel:ref/foo", Target: "/"}, 864 {UnresolvedSource: "$kernel:ref2/bar", Target: "/"}, 865 } 866 867 kInfoNoRefs := &kernel.Info{} 868 kInfoOneRefButUpdateFlagFalse := &kernel.Info{ 869 Assets: map[string]*kernel.Asset{ 870 "ref": { 871 // note that update is false here 872 Update: false, 873 Content: []string{"some-file"}, 874 }, 875 }, 876 } 877 kInfoOneRef := &kernel.Info{ 878 Assets: map[string]*kernel.Asset{ 879 "ref": { 880 Update: true, 881 Content: []string{"some-file"}, 882 }, 883 }, 884 } 885 kInfoOneRefDifferentName := &kernel.Info{ 886 Assets: map[string]*kernel.Asset{ 887 "ref-other": { 888 Update: true, 889 Content: []string{"some-file"}, 890 }, 891 }, 892 } 893 kInfoTwoRefs := &kernel.Info{ 894 Assets: map[string]*kernel.Asset{ 895 "ref": { 896 Update: true, 897 Content: []string{"some-file"}, 898 }, 899 "ref2": { 900 Update: true, 901 Content: []string{"other-file"}, 902 }, 903 }, 904 } 905 906 for _, tc := range []struct { 907 volumeContent []gadget.VolumeContent 908 kinfo *kernel.Info 909 expectedErr string 910 }{ 911 // happy case: trivial 912 {contentNoKernelRef, kInfoNoRefs, ""}, 913 914 // happy case: if kernel asset has "Update: false" 915 {contentNoKernelRef, kInfoOneRefButUpdateFlagFalse, ""}, 916 917 // unhappy case: kernel has one or more unresolved references in gadget 918 {contentNoKernelRef, kInfoOneRef, `gadget does not consume any of the kernel assets needing synced update "ref"`}, 919 {contentNoKernelRef, kInfoTwoRefs, `gadget does not consume any of the kernel assets needing synced update "ref", "ref2"`}, 920 921 // unhappy case: gadget needs different asset than kernel provides 922 {contentOneKernelRef, kInfoOneRefDifferentName, `gadget does not consume any of the kernel assets needing synced update "ref-other"`}, 923 924 // happy case: exactly one matching kernel ref 925 {contentOneKernelRef, kInfoOneRef, ""}, 926 // happy case: one matching, one missing kernel ref, still considered fine 927 {contentTwoKernelRefs, kInfoTwoRefs, ""}, 928 } { 929 lv.Structure[0].Content = tc.volumeContent 930 err := gadget.GadgetVolumeConsumesOneKernelUpdateAsset(lv.Volume, tc.kinfo) 931 if tc.expectedErr == "" { 932 c.Check(err, IsNil, Commentf("should not fail %v", tc.volumeContent)) 933 } else { 934 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("should fail %v", tc.volumeContent)) 935 } 936 } 937 } 938 939 func (s *validateGadgetTestSuite) TestValidateContentKernelRefMissing(c *C) { 940 var gadgetYamlContent = ` 941 volumes: 942 first: 943 bootloader: grub 944 structure: 945 - name: first-foo 946 type: DA,21686148-6449-6E6F-744E-656564454649 947 size: 1M 948 content: 949 - image: first.img 950 second: 951 structure: 952 - name: second-foo 953 filesystem: ext4 954 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 955 size: 128M 956 content: 957 - source: $kernel:ref/foo 958 target: / 959 ` 960 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 961 makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil) 962 963 // note that there is no kernel.yaml 964 kernelUnpackDir := c.MkDir() 965 966 ginfo, err := gadget.ReadInfo(s.dir, nil) 967 c.Assert(err, IsNil) 968 err = gadget.ValidateContent(ginfo, s.dir, kernelUnpackDir) 969 c.Assert(err, ErrorMatches, `.*cannot find "ref" in kernel info.*`) 970 } 971 972 func (s *validateGadgetTestSuite) TestValidateContentKernelRefNotInGadget(c *C) { 973 var gadgetYamlContent = ` 974 volumes: 975 first: 976 bootloader: grub 977 structure: 978 - name: first-foo 979 type: DA,21686148-6449-6E6F-744E-656564454649 980 size: 1M 981 content: 982 - image: first.img 983 second: 984 structure: 985 - name: second-foo 986 filesystem: ext4 987 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 988 size: 128M 989 content: 990 - source: foo 991 target: / 992 ` 993 makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent)) 994 makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil) 995 makeSizedFile(c, filepath.Join(s.dir, "foo"), 1, nil) 996 997 kernelUnpackDir := c.MkDir() 998 kernelYamlContent := ` 999 assets: 1000 ref: 1001 update: true 1002 content: 1003 - dtbs/` 1004 makeSizedFile(c, filepath.Join(kernelUnpackDir, "meta/kernel.yaml"), 0, []byte(kernelYamlContent)) 1005 makeSizedFile(c, filepath.Join(kernelUnpackDir, "dtbs/foo.dtb"), 0, []byte("foo.dtb content")) 1006 1007 ginfo, err := gadget.ReadInfo(s.dir, nil) 1008 c.Assert(err, IsNil) 1009 err = gadget.ValidateContent(ginfo, s.dir, kernelUnpackDir) 1010 c.Assert(err, ErrorMatches, `no asset from the kernel.yaml needing synced update is consumed by the gadget at "/.*"`) 1011 }