github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/boot/makebootable_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 boot_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/boot" 32 "github.com/snapcore/snapd/boot/boottest" 33 "github.com/snapcore/snapd/bootloader" 34 "github.com/snapcore/snapd/bootloader/assets" 35 "github.com/snapcore/snapd/bootloader/bootloadertest" 36 "github.com/snapcore/snapd/bootloader/grubenv" 37 "github.com/snapcore/snapd/bootloader/ubootenv" 38 "github.com/snapcore/snapd/dirs" 39 "github.com/snapcore/snapd/gadget" 40 "github.com/snapcore/snapd/osutil" 41 "github.com/snapcore/snapd/secboot" 42 "github.com/snapcore/snapd/seed" 43 "github.com/snapcore/snapd/snap" 44 "github.com/snapcore/snapd/snap/snapfile" 45 "github.com/snapcore/snapd/snap/snaptest" 46 "github.com/snapcore/snapd/testutil" 47 "github.com/snapcore/snapd/timings" 48 ) 49 50 type makeBootableSuite struct { 51 baseBootenvSuite 52 53 bootloader *bootloadertest.MockBootloader 54 } 55 56 var _ = Suite(&makeBootableSuite{}) 57 58 func (s *makeBootableSuite) SetUpTest(c *C) { 59 s.baseBootenvSuite.SetUpTest(c) 60 61 s.bootloader = bootloadertest.Mock("mock", c.MkDir()) 62 s.forceBootloader(s.bootloader) 63 } 64 65 func makeSnap(c *C, name, yaml string, revno snap.Revision) (fn string, info *snap.Info) { 66 return makeSnapWithFiles(c, name, yaml, revno, nil) 67 } 68 69 func makeSnapWithFiles(c *C, name, yaml string, revno snap.Revision, files [][]string) (fn string, info *snap.Info) { 70 si := &snap.SideInfo{ 71 RealName: name, 72 Revision: revno, 73 } 74 fn = snaptest.MakeTestSnapWithFiles(c, yaml, files) 75 snapf, err := snapfile.Open(fn) 76 c.Assert(err, IsNil) 77 info, err = snap.ReadInfoFromSnapFile(snapf, si) 78 c.Assert(err, IsNil) 79 return fn, info 80 } 81 82 func (s *makeBootableSuite) TestMakeBootableImage(c *C) { 83 bootloader.Force(nil) 84 model := boottest.MakeMockModel() 85 86 grubCfg := []byte("#grub cfg") 87 unpackedGadgetDir := c.MkDir() 88 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 89 c.Assert(err, IsNil) 90 91 seedSnapsDirs := filepath.Join(s.rootdir, "/var/lib/snapd/seed", "snaps") 92 err = os.MkdirAll(seedSnapsDirs, 0755) 93 c.Assert(err, IsNil) 94 95 baseFn, baseInfo := makeSnap(c, "core18", `name: core18 96 type: base 97 version: 4.0 98 `, snap.R(3)) 99 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 100 err = os.Rename(baseFn, baseInSeed) 101 c.Assert(err, IsNil) 102 kernelFn, kernelInfo := makeSnap(c, "pc-kernel", `name: pc-kernel 103 type: kernel 104 version: 4.0 105 `, snap.R(5)) 106 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 107 err = os.Rename(kernelFn, kernelInSeed) 108 c.Assert(err, IsNil) 109 110 bootWith := &boot.BootableSet{ 111 Base: baseInfo, 112 BasePath: baseInSeed, 113 Kernel: kernelInfo, 114 KernelPath: kernelInSeed, 115 UnpackedGadgetDir: unpackedGadgetDir, 116 } 117 118 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 119 c.Assert(err, IsNil) 120 121 // check the bootloader config 122 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "boot/grub/grubenv")) 123 c.Assert(seedGenv.Load(), IsNil) 124 c.Check(seedGenv.Get("snap_kernel"), Equals, "pc-kernel_5.snap") 125 c.Check(seedGenv.Get("snap_core"), Equals, "core18_3.snap") 126 c.Check(seedGenv.Get("snap_menuentry"), Equals, "My Model") 127 128 // check symlinks from snap blob dir 129 kernelBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename()) 130 dst, err := os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename())) 131 c.Assert(err, IsNil) 132 c.Check(dst, Equals, "../seed/snaps/pc-kernel_5.snap") 133 c.Check(kernelBlob, testutil.FilePresent) 134 135 baseBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename()) 136 dst, err = os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename())) 137 c.Assert(err, IsNil) 138 c.Check(dst, Equals, "../seed/snaps/core18_3.snap") 139 c.Check(baseBlob, testutil.FilePresent) 140 141 // check that the bootloader (grub here) configuration was copied 142 c.Check(filepath.Join(s.rootdir, "boot", "grub/grub.cfg"), testutil.FileEquals, grubCfg) 143 } 144 145 type makeBootable20Suite struct { 146 baseBootenvSuite 147 148 bootloader *bootloadertest.MockRecoveryAwareBootloader 149 } 150 151 type makeBootable20UbootSuite struct { 152 baseBootenvSuite 153 154 bootloader *bootloadertest.MockExtractedRecoveryKernelImageBootloader 155 } 156 157 var _ = Suite(&makeBootable20Suite{}) 158 var _ = Suite(&makeBootable20UbootSuite{}) 159 160 func (s *makeBootable20Suite) SetUpTest(c *C) { 161 s.baseBootenvSuite.SetUpTest(c) 162 163 s.bootloader = bootloadertest.Mock("mock", c.MkDir()).RecoveryAware() 164 s.forceBootloader(s.bootloader) 165 } 166 167 func (s *makeBootable20UbootSuite) SetUpTest(c *C) { 168 s.baseBootenvSuite.SetUpTest(c) 169 170 s.bootloader = bootloadertest.Mock("mock", c.MkDir()).ExtractedRecoveryKernelImage() 171 s.forceBootloader(s.bootloader) 172 } 173 174 func (s *makeBootable20Suite) TestMakeBootableImage20(c *C) { 175 bootloader.Force(nil) 176 model := boottest.MakeMockUC20Model() 177 178 unpackedGadgetDir := c.MkDir() 179 grubRecoveryCfg := "#grub-recovery cfg" 180 grubRecoveryCfgAsset := "#grub-recovery cfg from assets" 181 grubCfg := "#grub cfg" 182 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 183 {"grub-recovery.conf", grubRecoveryCfg}, 184 {"grub.conf", grubCfg}, 185 {"meta/snap.yaml", gadgetSnapYaml}, 186 }) 187 restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset)) 188 defer restore() 189 190 // on uc20 the seed layout if different 191 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 192 err := os.MkdirAll(seedSnapsDirs, 0755) 193 c.Assert(err, IsNil) 194 195 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 196 type: base 197 version: 5.0 198 `, snap.R(3)) 199 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 200 err = os.Rename(baseFn, baseInSeed) 201 c.Assert(err, IsNil) 202 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 203 type: kernel 204 version: 5.0 205 `, snap.R(5), [][]string{ 206 {"kernel.efi", "I'm a kernel.efi"}, 207 }) 208 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 209 err = os.Rename(kernelFn, kernelInSeed) 210 c.Assert(err, IsNil) 211 212 label := "20191209" 213 recoverySystemDir := filepath.Join("/systems", label) 214 bootWith := &boot.BootableSet{ 215 Base: baseInfo, 216 BasePath: baseInSeed, 217 Kernel: kernelInfo, 218 KernelPath: kernelInSeed, 219 RecoverySystemDir: recoverySystemDir, 220 RecoverySystemLabel: label, 221 UnpackedGadgetDir: unpackedGadgetDir, 222 Recovery: true, 223 } 224 225 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 226 c.Assert(err, IsNil) 227 228 // ensure only a single file got copied (the grub.cfg) 229 files, err := filepath.Glob(filepath.Join(s.rootdir, "EFI/ubuntu/*")) 230 c.Assert(err, IsNil) 231 // grub.cfg and grubenv 232 c.Check(files, HasLen, 2) 233 // check that the recovery bootloader configuration was installed with 234 // the correct content 235 c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset) 236 237 // ensure no /boot was setup 238 c.Check(filepath.Join(s.rootdir, "boot"), testutil.FileAbsent) 239 240 // ensure the correct recovery system configuration was set 241 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv")) 242 c.Assert(seedGenv.Load(), IsNil) 243 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label) 244 245 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 246 c.Assert(systemGenv.Load(), IsNil) 247 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 248 } 249 250 func (s *makeBootable20Suite) TestMakeBootableImage20BootFlags(c *C) { 251 bootloader.Force(nil) 252 model := boottest.MakeMockUC20Model() 253 254 unpackedGadgetDir := c.MkDir() 255 grubRecoveryCfg := "#grub-recovery cfg" 256 grubRecoveryCfgAsset := "#grub-recovery cfg from assets" 257 grubCfg := "#grub cfg" 258 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 259 {"grub-recovery.conf", grubRecoveryCfg}, 260 {"grub.conf", grubCfg}, 261 {"meta/snap.yaml", gadgetSnapYaml}, 262 }) 263 restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset)) 264 defer restore() 265 266 // on uc20 the seed layout if different 267 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 268 err := os.MkdirAll(seedSnapsDirs, 0755) 269 c.Assert(err, IsNil) 270 271 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 272 type: base 273 version: 5.0 274 `, snap.R(3)) 275 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 276 err = os.Rename(baseFn, baseInSeed) 277 c.Assert(err, IsNil) 278 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 279 type: kernel 280 version: 5.0 281 `, snap.R(5), [][]string{ 282 {"kernel.efi", "I'm a kernel.efi"}, 283 }) 284 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 285 err = os.Rename(kernelFn, kernelInSeed) 286 c.Assert(err, IsNil) 287 288 label := "20191209" 289 recoverySystemDir := filepath.Join("/systems", label) 290 bootWith := &boot.BootableSet{ 291 Base: baseInfo, 292 BasePath: baseInSeed, 293 Kernel: kernelInfo, 294 KernelPath: kernelInSeed, 295 RecoverySystemDir: recoverySystemDir, 296 RecoverySystemLabel: label, 297 UnpackedGadgetDir: unpackedGadgetDir, 298 Recovery: true, 299 } 300 bootFlags := []string{"factory"} 301 302 err = boot.MakeBootableImage(model, s.rootdir, bootWith, bootFlags) 303 c.Assert(err, IsNil) 304 305 // ensure the correct recovery system configuration was set 306 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv")) 307 c.Assert(seedGenv.Load(), IsNil) 308 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label) 309 c.Check(seedGenv.Get("snapd_boot_flags"), Equals, "factory") 310 311 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 312 c.Assert(systemGenv.Load(), IsNil) 313 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 314 315 } 316 317 func (s *makeBootable20Suite) testMakeBootableImage20CustomKernelArgs(c *C, whichFile, content, errMsg string) { 318 bootloader.Force(nil) 319 model := boottest.MakeMockUC20Model() 320 321 unpackedGadgetDir := c.MkDir() 322 grubCfg := "#grub cfg" 323 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 324 {"grub.conf", grubCfg}, 325 {"meta/snap.yaml", gadgetSnapYaml}, 326 {whichFile, content}, 327 }) 328 329 // on uc20 the seed layout if different 330 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 331 err := os.MkdirAll(seedSnapsDirs, 0755) 332 c.Assert(err, IsNil) 333 334 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 335 type: base 336 version: 5.0 337 `, snap.R(3)) 338 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 339 err = os.Rename(baseFn, baseInSeed) 340 c.Assert(err, IsNil) 341 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 342 type: kernel 343 version: 5.0 344 `, snap.R(5), [][]string{ 345 {"kernel.efi", "I'm a kernel.efi"}, 346 }) 347 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 348 err = os.Rename(kernelFn, kernelInSeed) 349 c.Assert(err, IsNil) 350 351 label := "20191209" 352 recoverySystemDir := filepath.Join("/systems", label) 353 bootWith := &boot.BootableSet{ 354 Base: baseInfo, 355 BasePath: baseInSeed, 356 Kernel: kernelInfo, 357 KernelPath: kernelInSeed, 358 RecoverySystemDir: recoverySystemDir, 359 RecoverySystemLabel: label, 360 UnpackedGadgetDir: unpackedGadgetDir, 361 Recovery: true, 362 } 363 364 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 365 if errMsg != "" { 366 c.Assert(err, ErrorMatches, errMsg) 367 return 368 } 369 c.Assert(err, IsNil) 370 371 // ensure the correct recovery system configuration was set 372 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv")) 373 c.Assert(seedGenv.Load(), IsNil) 374 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label) 375 // and kernel command line 376 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 377 c.Assert(systemGenv.Load(), IsNil) 378 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 379 switch whichFile { 380 case "cmdline.extra": 381 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content) 382 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "") 383 case "cmdline.full": 384 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 385 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content) 386 } 387 } 388 389 func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelExtraArgs(c *C) { 390 s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "foo bar baz", "") 391 } 392 393 func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelFullArgs(c *C) { 394 s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.full", "foo bar baz", "") 395 } 396 397 func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelInvalidArgs(c *C) { 398 errMsg := `cannot obtain recovery system command line: cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument "snapd_foo=bar"` 399 s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "snapd_foo=bar", errMsg) 400 } 401 402 func (s *makeBootable20Suite) TestMakeBootableImage20UnsetRecoverySystemLabelError(c *C) { 403 model := boottest.MakeMockUC20Model() 404 405 unpackedGadgetDir := c.MkDir() 406 grubRecoveryCfg := []byte("#grub-recovery cfg") 407 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644) 408 c.Assert(err, IsNil) 409 grubCfg := []byte("#grub cfg") 410 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 411 c.Assert(err, IsNil) 412 413 label := "20191209" 414 recoverySystemDir := filepath.Join("/systems", label) 415 bootWith := &boot.BootableSet{ 416 RecoverySystemDir: recoverySystemDir, 417 UnpackedGadgetDir: unpackedGadgetDir, 418 Recovery: true, 419 } 420 421 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 422 c.Assert(err, ErrorMatches, "internal error: recovery system label unset") 423 } 424 425 func (s *makeBootable20Suite) TestMakeBootableImage20MultipleRecoverySystemsError(c *C) { 426 model := boottest.MakeMockUC20Model() 427 428 bootWith := &boot.BootableSet{Recovery: true} 429 err := os.MkdirAll(filepath.Join(s.rootdir, "systems/20191204"), 0755) 430 c.Assert(err, IsNil) 431 err = os.MkdirAll(filepath.Join(s.rootdir, "systems/20191205"), 0755) 432 c.Assert(err, IsNil) 433 434 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 435 c.Assert(err, ErrorMatches, "cannot make multiple recovery systems bootable yet") 436 } 437 438 func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) { 439 model := boottest.MakeMockModel() 440 441 err := boot.MakeRunnableSystem(model, nil, nil) 442 c.Assert(err, ErrorMatches, "internal error: cannot make non-uc20 system runnable") 443 } 444 445 func (s *makeBootable20Suite) TestMakeSystemRunnable20(c *C) { 446 bootloader.Force(nil) 447 448 model := boottest.MakeMockUC20Model() 449 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 450 err := os.MkdirAll(seedSnapsDirs, 0755) 451 c.Assert(err, IsNil) 452 453 // grub on ubuntu-seed 454 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 455 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 456 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 457 c.Assert(err, IsNil) 458 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 459 c.Assert(err, IsNil) 460 461 // setup recovery boot assets 462 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 463 c.Assert(err, IsNil) 464 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 465 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 466 []byte("recovery shim content"), 0644) 467 c.Assert(err, IsNil) 468 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 469 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 470 []byte("recovery grub content"), 0644) 471 c.Assert(err, IsNil) 472 473 // grub on ubuntu-boot 474 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 475 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 476 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 477 c.Assert(err, IsNil) 478 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 479 c.Assert(err, IsNil) 480 481 unpackedGadgetDir := c.MkDir() 482 grubRecoveryCfg := []byte("#grub-recovery cfg") 483 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 484 grubCfg := []byte("#grub cfg") 485 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 486 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 487 {"grub-recovery.conf", string(grubRecoveryCfg)}, 488 {"grub.conf", string(grubCfg)}, 489 {"bootx64.efi", "shim content"}, 490 {"grubx64.efi", "grub content"}, 491 {"meta/snap.yaml", gadgetSnapYaml}, 492 }) 493 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 494 defer restore() 495 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 496 defer restore() 497 498 // make the snaps symlinks so that we can ensure that makebootable follows 499 // the symlinks and copies the files and not the symlinks 500 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 501 type: base 502 version: 5.0 503 `, snap.R(3)) 504 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 505 err = os.Symlink(baseFn, baseInSeed) 506 c.Assert(err, IsNil) 507 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 508 type: kernel 509 version: 5.0 510 `, snap.R(5), 511 [][]string{ 512 {"kernel.efi", "I'm a kernel.efi"}, 513 }, 514 ) 515 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 516 err = os.Symlink(kernelFn, kernelInSeed) 517 c.Assert(err, IsNil) 518 519 bootWith := &boot.BootableSet{ 520 RecoverySystemDir: "20191216", 521 BasePath: baseInSeed, 522 Base: baseInfo, 523 KernelPath: kernelInSeed, 524 Kernel: kernelInfo, 525 Recovery: false, 526 UnpackedGadgetDir: unpackedGadgetDir, 527 } 528 529 // set up observer state 530 useEncryption := true 531 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 532 c.Assert(obs, NotNil) 533 c.Assert(err, IsNil) 534 runBootStruct := &gadget.LaidOutStructure{ 535 VolumeStructure: &gadget.VolumeStructure{ 536 Role: gadget.SystemBoot, 537 }, 538 } 539 540 // only grubx64.efi gets installed to system-boot 541 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 542 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 543 c.Assert(err, IsNil) 544 545 // observe recovery assets 546 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 547 c.Assert(err, IsNil) 548 549 // set encryption key 550 myKey := secboot.EncryptionKey{} 551 myKey2 := secboot.EncryptionKey{} 552 for i := range myKey { 553 myKey[i] = byte(i) 554 myKey2[i] = byte(128 + i) 555 } 556 obs.ChosenEncryptionKeys(myKey, myKey2) 557 558 // set a mock recovery kernel 559 readSystemEssentialCalls := 0 560 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 561 readSystemEssentialCalls++ 562 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 563 }) 564 defer restore() 565 566 // set mock key sealing 567 sealKeysCalls := 0 568 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 569 sealKeysCalls++ 570 switch sealKeysCalls { 571 case 1: 572 c.Check(keys, HasLen, 1) 573 c.Check(keys[0].Key, DeepEquals, myKey) 574 case 2: 575 c.Check(keys, HasLen, 2) 576 c.Check(keys[0].Key, DeepEquals, myKey) 577 c.Check(keys[1].Key, DeepEquals, myKey2) 578 default: 579 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 580 } 581 c.Assert(params.ModelParams, HasLen, 1) 582 583 shim := bootloader.NewBootFile("", filepath.Join(s.rootdir, 584 "var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"), 585 bootloader.RoleRecovery) 586 grub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 587 "var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"), 588 bootloader.RoleRecovery) 589 runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 590 "var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"), 591 bootloader.RoleRunMode) 592 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 593 runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode) 594 595 switch sealKeysCalls { 596 case 1: 597 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 598 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 599 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))), 600 }) 601 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 602 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 603 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 604 }) 605 case 2: 606 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 607 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 608 }) 609 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 610 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 611 }) 612 default: 613 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 614 } 615 616 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 617 618 return nil 619 }) 620 defer restore() 621 622 err = boot.MakeRunnableSystem(model, bootWith, obs) 623 c.Assert(err, IsNil) 624 625 // also do the logical thing and make the next boot go to run mode 626 err = boot.EnsureNextBootToRunMode("20191216") 627 c.Assert(err, IsNil) 628 629 // ensure grub.cfg in boot was installed from internal assets 630 c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset)) 631 632 // ensure base/kernel got copied to /var/lib/snapd/snaps 633 core20Snap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap") 634 pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "pc-kernel_5.snap") 635 c.Check(core20Snap, testutil.FilePresent) 636 c.Check(pcKernelSnap, testutil.FilePresent) 637 c.Check(osutil.IsSymlink(core20Snap), Equals, false) 638 c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false) 639 640 // ensure the bootvars got updated the right way 641 mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv") 642 c.Check(mockSeedGrubenv, testutil.FilePresent) 643 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run") 644 mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv") 645 c.Check(mockBootGrubenv, testutil.FilePresent) 646 647 // ensure that kernel_status is empty, we specifically want this to be set 648 // to the empty string 649 // use (?m) to match multi-line file in the regex here, because the file is 650 // a grubenv with padding #### blocks 651 c.Check(mockBootGrubenv, testutil.FileMatches, `(?m)^kernel_status=$`) 652 653 // check that we have the extracted kernel in the right places, both in the 654 // old uc16/uc18 location and the new ubuntu-boot partition grub dir 655 extractedKernel := filepath.Join(mockBootGrubDir, "pc-kernel_5.snap", "kernel.efi") 656 c.Check(extractedKernel, testutil.FilePresent) 657 658 // the new uc20 location 659 extractedKernelSymlink := filepath.Join(mockBootGrubDir, "kernel.efi") 660 c.Check(extractedKernelSymlink, testutil.FilePresent) 661 662 // ensure modeenv looks correct 663 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 664 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run 665 recovery_system=20191216 666 current_recovery_systems=20191216 667 good_recovery_systems=20191216 668 base=core20_3.snap 669 current_kernels=pc-kernel_5.snap 670 model=my-brand/my-model-uc20 671 grade=dangerous 672 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 673 current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]} 674 current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]} 675 current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"] 676 `) 677 copiedGrubBin := filepath.Join( 678 dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), 679 "grub", 680 "grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d", 681 ) 682 copiedRecoveryGrubBin := filepath.Join( 683 dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), 684 "grub", 685 "grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5", 686 ) 687 copiedRecoveryShimBin := filepath.Join( 688 dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), 689 "grub", 690 "bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37", 691 ) 692 693 // only one file in the cache under new root 694 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), "grub", "*"), []string{ 695 copiedRecoveryShimBin, 696 copiedGrubBin, 697 copiedRecoveryGrubBin, 698 }) 699 // with the right content 700 c.Check(copiedGrubBin, testutil.FileEquals, "grub content") 701 c.Check(copiedRecoveryGrubBin, testutil.FileEquals, "recovery grub content") 702 c.Check(copiedRecoveryShimBin, testutil.FileEquals, "recovery shim content") 703 704 // make sure SealKey was called for the run object and the fallback object 705 c.Check(sealKeysCalls, Equals, 2) 706 707 // make sure the marker file for sealed key was created 708 c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent) 709 710 // make sure we wrote the boot chains data file 711 c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent) 712 } 713 714 func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) { 715 bootloader.Force(nil) 716 717 model := boottest.MakeMockUC20Model() 718 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 719 err := os.MkdirAll(seedSnapsDirs, 0755) 720 c.Assert(err, IsNil) 721 722 // grub on ubuntu-seed 723 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 724 err = os.MkdirAll(mockSeedGrubDir, 0755) 725 c.Assert(err, IsNil) 726 // no recovery grub.cfg so that test fails if it ever reaches that point 727 728 // grub on ubuntu-boot 729 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 730 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 731 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 732 c.Assert(err, IsNil) 733 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 734 c.Assert(err, IsNil) 735 736 unpackedGadgetDir := c.MkDir() 737 738 // make the snaps symlinks so that we can ensure that makebootable follows 739 // the symlinks and copies the files and not the symlinks 740 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 741 type: base 742 version: 5.0 743 `, snap.R(3)) 744 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 745 err = os.Symlink(baseFn, baseInSeed) 746 c.Assert(err, IsNil) 747 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 748 type: kernel 749 version: 5.0 750 `, snap.R(5), 751 [][]string{ 752 {"kernel.efi", "I'm a kernel.efi"}, 753 }, 754 ) 755 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 756 err = os.Symlink(kernelFn, kernelInSeed) 757 c.Assert(err, IsNil) 758 759 bootWith := &boot.BootableSet{ 760 RecoverySystemDir: "20191216", 761 BasePath: baseInSeed, 762 Base: baseInfo, 763 KernelPath: kernelInSeed, 764 Kernel: kernelInfo, 765 Recovery: false, 766 UnpackedGadgetDir: unpackedGadgetDir, 767 } 768 769 // no grub marker in gadget directory raises an error 770 err = boot.MakeRunnableSystem(model, bootWith, nil) 771 c.Assert(err, ErrorMatches, "internal error: cannot identify run system bootloader: cannot determine bootloader") 772 773 // set up grub.cfg in gadget 774 grubCfg := []byte("#grub cfg") 775 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 776 c.Assert(err, IsNil) 777 778 // no write access to destination directory 779 restore := assets.MockInternal("grub.cfg", nil) 780 defer restore() 781 err = boot.MakeRunnableSystem(model, bootWith, nil) 782 c.Assert(err, ErrorMatches, `cannot install managed bootloader assets: internal error: no boot asset for "grub.cfg"`) 783 } 784 785 func (s *makeBootable20Suite) TestMakeRunnableSystem20RunModeSealKeyErr(c *C) { 786 bootloader.Force(nil) 787 788 model := boottest.MakeMockUC20Model() 789 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 790 err := os.MkdirAll(seedSnapsDirs, 0755) 791 c.Assert(err, IsNil) 792 793 // grub on ubuntu-seed 794 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 795 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 796 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 797 c.Assert(err, IsNil) 798 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 799 c.Assert(err, IsNil) 800 801 // setup recovery boot assets 802 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 803 c.Assert(err, IsNil) 804 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 805 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 806 []byte("recovery shim content"), 0644) 807 c.Assert(err, IsNil) 808 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 809 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 810 []byte("recovery grub content"), 0644) 811 c.Assert(err, IsNil) 812 813 // grub on ubuntu-boot 814 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 815 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 816 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 817 c.Assert(err, IsNil) 818 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 819 c.Assert(err, IsNil) 820 821 unpackedGadgetDir := c.MkDir() 822 grubRecoveryCfg := []byte("#grub-recovery cfg") 823 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 824 grubCfg := []byte("#grub cfg") 825 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 826 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 827 {"grub-recovery.conf", string(grubRecoveryCfg)}, 828 {"grub.conf", string(grubCfg)}, 829 {"bootx64.efi", "shim content"}, 830 {"grubx64.efi", "grub content"}, 831 {"meta/snap.yaml", gadgetSnapYaml}, 832 }) 833 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 834 defer restore() 835 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 836 defer restore() 837 838 // make the snaps symlinks so that we can ensure that makebootable follows 839 // the symlinks and copies the files and not the symlinks 840 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 841 type: base 842 version: 5.0 843 `, snap.R(3)) 844 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 845 err = os.Symlink(baseFn, baseInSeed) 846 c.Assert(err, IsNil) 847 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 848 type: kernel 849 version: 5.0 850 `, snap.R(5), 851 [][]string{ 852 {"kernel.efi", "I'm a kernel.efi"}, 853 }, 854 ) 855 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 856 err = os.Symlink(kernelFn, kernelInSeed) 857 c.Assert(err, IsNil) 858 859 bootWith := &boot.BootableSet{ 860 RecoverySystemDir: "20191216", 861 BasePath: baseInSeed, 862 Base: baseInfo, 863 KernelPath: kernelInSeed, 864 Kernel: kernelInfo, 865 Recovery: false, 866 UnpackedGadgetDir: unpackedGadgetDir, 867 } 868 869 // set up observer state 870 useEncryption := true 871 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 872 c.Assert(obs, NotNil) 873 c.Assert(err, IsNil) 874 runBootStruct := &gadget.LaidOutStructure{ 875 VolumeStructure: &gadget.VolumeStructure{ 876 Role: gadget.SystemBoot, 877 }, 878 } 879 880 // only grubx64.efi gets installed to system-boot 881 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 882 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 883 c.Assert(err, IsNil) 884 885 // observe recovery assets 886 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 887 c.Assert(err, IsNil) 888 889 // set encryption key 890 myKey := secboot.EncryptionKey{} 891 myKey2 := secboot.EncryptionKey{} 892 for i := range myKey { 893 myKey[i] = byte(i) 894 myKey2[i] = byte(128 + i) 895 } 896 obs.ChosenEncryptionKeys(myKey, myKey2) 897 898 // set a mock recovery kernel 899 readSystemEssentialCalls := 0 900 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 901 readSystemEssentialCalls++ 902 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 903 }) 904 defer restore() 905 906 // set mock key sealing 907 sealKeysCalls := 0 908 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 909 sealKeysCalls++ 910 switch sealKeysCalls { 911 case 1: 912 c.Check(keys, HasLen, 1) 913 c.Check(keys[0].Key, DeepEquals, myKey) 914 case 2: 915 c.Check(keys, HasLen, 2) 916 c.Check(keys[0].Key, DeepEquals, myKey) 917 c.Check(keys[1].Key, DeepEquals, myKey2) 918 default: 919 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 920 } 921 c.Assert(params.ModelParams, HasLen, 1) 922 923 shim := bootloader.NewBootFile("", filepath.Join(s.rootdir, 924 "var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"), 925 bootloader.RoleRecovery) 926 grub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 927 "var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"), 928 bootloader.RoleRecovery) 929 runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 930 "var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"), 931 bootloader.RoleRunMode) 932 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 933 runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode) 934 935 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 936 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 937 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))), 938 }) 939 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 940 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 941 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 942 }) 943 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 944 945 return fmt.Errorf("seal error") 946 }) 947 defer restore() 948 949 err = boot.MakeRunnableSystem(model, bootWith, obs) 950 c.Assert(err, ErrorMatches, "cannot seal the encryption keys: seal error") 951 } 952 953 func (s *makeBootable20Suite) testMakeSystemRunnable20WithCustomKernelArgs(c *C, whichFile, content, errMsg, cmdlineRun, cmdlineRecovery string) { 954 bootloader.Force(nil) 955 956 model := boottest.MakeMockUC20Model() 957 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 958 err := os.MkdirAll(seedSnapsDirs, 0755) 959 c.Assert(err, IsNil) 960 961 // grub on ubuntu-seed 962 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 963 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 964 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 965 c.Assert(err, IsNil) 966 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 967 c.Assert(err, IsNil) 968 969 // setup recovery boot assets 970 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 971 c.Assert(err, IsNil) 972 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 973 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 974 []byte("recovery shim content"), 0644) 975 c.Assert(err, IsNil) 976 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 977 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 978 []byte("recovery grub content"), 0644) 979 c.Assert(err, IsNil) 980 981 // grub on ubuntu-boot 982 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 983 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 984 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 985 c.Assert(err, IsNil) 986 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 987 c.Assert(err, IsNil) 988 989 unpackedGadgetDir := c.MkDir() 990 grubRecoveryCfg := []byte("#grub-recovery cfg") 991 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 992 grubCfg := []byte("#grub cfg") 993 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 994 gadgetFiles := [][]string{ 995 {"grub-recovery.conf", string(grubRecoveryCfg)}, 996 {"grub.conf", string(grubCfg)}, 997 {"bootx64.efi", "shim content"}, 998 {"grubx64.efi", "grub content"}, 999 {"meta/snap.yaml", gadgetSnapYaml}, 1000 {whichFile, content}, 1001 } 1002 snaptest.PopulateDir(unpackedGadgetDir, gadgetFiles) 1003 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 1004 defer restore() 1005 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 1006 defer restore() 1007 1008 // make the snaps symlinks so that we can ensure that makebootable follows 1009 // the symlinks and copies the files and not the symlinks 1010 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1011 type: base 1012 version: 5.0 1013 `, snap.R(3)) 1014 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1015 err = os.Symlink(baseFn, baseInSeed) 1016 c.Assert(err, IsNil) 1017 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1018 type: kernel 1019 version: 5.0 1020 `, snap.R(5), 1021 [][]string{ 1022 {"kernel.efi", "I'm a kernel.efi"}, 1023 }, 1024 ) 1025 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1026 err = os.Symlink(kernelFn, kernelInSeed) 1027 c.Assert(err, IsNil) 1028 1029 bootWith := &boot.BootableSet{ 1030 RecoverySystemDir: "20191216", 1031 BasePath: baseInSeed, 1032 Base: baseInfo, 1033 KernelPath: kernelInSeed, 1034 Kernel: kernelInfo, 1035 Recovery: false, 1036 UnpackedGadgetDir: unpackedGadgetDir, 1037 } 1038 1039 // set up observer state 1040 useEncryption := true 1041 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 1042 c.Assert(obs, NotNil) 1043 c.Assert(err, IsNil) 1044 runBootStruct := &gadget.LaidOutStructure{ 1045 VolumeStructure: &gadget.VolumeStructure{ 1046 Role: gadget.SystemBoot, 1047 }, 1048 } 1049 1050 // only grubx64.efi gets installed to system-boot 1051 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 1052 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 1053 c.Assert(err, IsNil) 1054 1055 // observe recovery assets 1056 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 1057 c.Assert(err, IsNil) 1058 1059 // set a mock recovery kernel 1060 readSystemEssentialCalls := 0 1061 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1062 readSystemEssentialCalls++ 1063 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, gadgetFiles)}, nil 1064 }) 1065 defer restore() 1066 1067 // set mock key sealing 1068 sealKeysCalls := 0 1069 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 1070 sealKeysCalls++ 1071 switch sealKeysCalls { 1072 case 1, 2: 1073 // expecting only 2 calls 1074 default: 1075 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 1076 } 1077 c.Assert(params.ModelParams, HasLen, 1) 1078 1079 switch sealKeysCalls { 1080 case 1: 1081 c.Assert(params.ModelParams[0].KernelCmdlines, HasLen, 2) 1082 c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlineRecovery) 1083 c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlineRun) 1084 case 2: 1085 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{cmdlineRecovery}) 1086 default: 1087 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 1088 } 1089 1090 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 1091 1092 return nil 1093 }) 1094 defer restore() 1095 1096 err = boot.MakeRunnableSystem(model, bootWith, obs) 1097 if errMsg != "" { 1098 c.Assert(err, ErrorMatches, errMsg) 1099 return 1100 } 1101 c.Assert(err, IsNil) 1102 1103 // also do the logical thing and make the next boot go to run mode 1104 err = boot.EnsureNextBootToRunMode("20191216") 1105 c.Assert(err, IsNil) 1106 1107 // ensure grub.cfg in boot was installed from internal assets 1108 c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset)) 1109 1110 // ensure the bootvars got updated the right way 1111 mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv") 1112 c.Check(mockSeedGrubenv, testutil.FilePresent) 1113 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run") 1114 mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv") 1115 c.Check(mockBootGrubenv, testutil.FilePresent) 1116 systemGenv := grubenv.NewEnv(mockBootGrubenv) 1117 c.Assert(systemGenv.Load(), IsNil) 1118 switch whichFile { 1119 case "cmdline.extra": 1120 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content) 1121 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "") 1122 case "cmdline.full": 1123 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 1124 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content) 1125 } 1126 1127 // ensure modeenv looks correct 1128 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 1129 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, fmt.Sprintf(`mode=run 1130 recovery_system=20191216 1131 current_recovery_systems=20191216 1132 good_recovery_systems=20191216 1133 base=core20_3.snap 1134 current_kernels=pc-kernel_5.snap 1135 model=my-brand/my-model-uc20 1136 grade=dangerous 1137 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 1138 current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]} 1139 current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]} 1140 current_kernel_command_lines=["%v"] 1141 `, cmdlineRun)) 1142 // make sure SealKey was called for the run object and the fallback object 1143 c.Check(sealKeysCalls, Equals, 2) 1144 1145 // make sure the marker file for sealed key was created 1146 c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent) 1147 1148 // make sure we wrote the boot chains data file 1149 c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent) 1150 } 1151 1152 func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelExtraArgs(c *C) { 1153 cmdlineRun := "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz" 1154 cmdlineRecovery := "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1 foo bar baz" 1155 s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar baz", "", cmdlineRun, cmdlineRecovery) 1156 } 1157 1158 func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelFullArgs(c *C) { 1159 cmdlineRun := "snapd_recovery_mode=run foo bar baz" 1160 cmdlineRecovery := "snapd_recovery_mode=recover snapd_recovery_system=20191216 foo bar baz" 1161 s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.full", "foo bar baz", "", cmdlineRun, cmdlineRecovery) 1162 } 1163 1164 func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelInvalidArgs(c *C) { 1165 errMsg := `cannot compose the candidate command line: cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument "snapd=unhappy"` 1166 s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar snapd=unhappy", errMsg, "", "") 1167 } 1168 1169 func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20TraditionalUbootenvFails(c *C) { 1170 bootloader.Force(nil) 1171 model := boottest.MakeMockUC20Model() 1172 1173 unpackedGadgetDir := c.MkDir() 1174 ubootEnv := []byte("#uboot env") 1175 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), ubootEnv, 0644) 1176 c.Assert(err, IsNil) 1177 1178 // on uc20 the seed layout if different 1179 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1180 err = os.MkdirAll(seedSnapsDirs, 0755) 1181 c.Assert(err, IsNil) 1182 1183 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1184 type: base 1185 version: 5.0 1186 `, snap.R(3)) 1187 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1188 err = os.Rename(baseFn, baseInSeed) 1189 c.Assert(err, IsNil) 1190 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 1191 type: kernel 1192 version: 5.0 1193 `, snap.R(5), [][]string{ 1194 {"kernel.img", "I'm a kernel"}, 1195 {"initrd.img", "...and I'm an initrd"}, 1196 {"dtbs/foo.dtb", "foo dtb"}, 1197 {"dtbs/bar.dto", "bar dtbo"}, 1198 }) 1199 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1200 err = os.Rename(kernelFn, kernelInSeed) 1201 c.Assert(err, IsNil) 1202 1203 label := "20191209" 1204 recoverySystemDir := filepath.Join("/systems", label) 1205 bootWith := &boot.BootableSet{ 1206 Base: baseInfo, 1207 BasePath: baseInSeed, 1208 Kernel: kernelInfo, 1209 KernelPath: kernelInSeed, 1210 RecoverySystemDir: recoverySystemDir, 1211 RecoverySystemLabel: label, 1212 UnpackedGadgetDir: unpackedGadgetDir, 1213 Recovery: true, 1214 } 1215 1216 // TODO:UC20: enable this use case 1217 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 1218 c.Assert(err, ErrorMatches, "non-empty uboot.env not supported on UC20 yet") 1219 } 1220 1221 func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20BootScr(c *C) { 1222 model := boottest.MakeMockUC20Model() 1223 1224 unpackedGadgetDir := c.MkDir() 1225 // the uboot.conf must be empty for this to work/do the right thing 1226 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644) 1227 c.Assert(err, IsNil) 1228 1229 // on uc20 the seed layout if different 1230 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1231 err = os.MkdirAll(seedSnapsDirs, 0755) 1232 c.Assert(err, IsNil) 1233 1234 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1235 type: base 1236 version: 5.0 1237 `, snap.R(3)) 1238 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1239 err = os.Rename(baseFn, baseInSeed) 1240 c.Assert(err, IsNil) 1241 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 1242 type: kernel 1243 version: 5.0 1244 `, snap.R(5), [][]string{ 1245 {"kernel.img", "I'm a kernel"}, 1246 {"initrd.img", "...and I'm an initrd"}, 1247 {"dtbs/foo.dtb", "foo dtb"}, 1248 {"dtbs/bar.dto", "bar dtbo"}, 1249 }) 1250 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1251 err = os.Rename(kernelFn, kernelInSeed) 1252 c.Assert(err, IsNil) 1253 1254 label := "20191209" 1255 recoverySystemDir := filepath.Join("/systems", label) 1256 bootWith := &boot.BootableSet{ 1257 Base: baseInfo, 1258 BasePath: baseInSeed, 1259 Kernel: kernelInfo, 1260 KernelPath: kernelInSeed, 1261 RecoverySystemDir: recoverySystemDir, 1262 RecoverySystemLabel: label, 1263 UnpackedGadgetDir: unpackedGadgetDir, 1264 Recovery: true, 1265 } 1266 1267 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 1268 c.Assert(err, IsNil) 1269 1270 // since uboot.conf was absent, we won't have installed the uboot.env, as 1271 // it is expected that the gadget assets would have installed boot.scr 1272 // instead 1273 c.Check(filepath.Join(s.rootdir, "uboot.env"), testutil.FileAbsent) 1274 1275 c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{ 1276 "snapd_recovery_system": label, 1277 "snapd_recovery_mode": "install", 1278 }) 1279 1280 // ensure the correct recovery system configuration was set 1281 c.Check( 1282 s.bootloader.ExtractRecoveryKernelAssetsCalls, 1283 DeepEquals, 1284 []bootloadertest.ExtractedRecoveryKernelCall{{ 1285 RecoverySystemDir: recoverySystemDir, 1286 S: kernelInfo, 1287 }}, 1288 ) 1289 } 1290 1291 func (s *makeBootable20UbootSuite) TestUbootMakeRunnableSystem20RunModeBootSel(c *C) { 1292 bootloader.Force(nil) 1293 1294 model := boottest.MakeMockUC20Model() 1295 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1296 err := os.MkdirAll(seedSnapsDirs, 0755) 1297 c.Assert(err, IsNil) 1298 1299 // uboot on ubuntu-seed 1300 mockSeedUbootBootSel := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel") 1301 err = os.MkdirAll(filepath.Dir(mockSeedUbootBootSel), 0755) 1302 c.Assert(err, IsNil) 1303 env, err := ubootenv.Create(mockSeedUbootBootSel, 4096) 1304 c.Assert(err, IsNil) 1305 c.Assert(env.Save(), IsNil) 1306 1307 // uboot on ubuntu-boot (as if it was installed when creating the partition) 1308 mockBootUbootBootSel := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel") 1309 err = os.MkdirAll(filepath.Dir(mockBootUbootBootSel), 0755) 1310 c.Assert(err, IsNil) 1311 env, err = ubootenv.Create(mockBootUbootBootSel, 4096) 1312 c.Assert(err, IsNil) 1313 c.Assert(env.Save(), IsNil) 1314 1315 unpackedGadgetDir := c.MkDir() 1316 c.Assert(ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644), IsNil) 1317 1318 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1319 type: base 1320 version: 5.0 1321 `, snap.R(3)) 1322 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1323 err = os.Rename(baseFn, baseInSeed) 1324 c.Assert(err, IsNil) 1325 kernelSnapFiles := [][]string{ 1326 {"kernel.img", "I'm a kernel"}, 1327 {"initrd.img", "...and I'm an initrd"}, 1328 {"dtbs/foo.dtb", "foo dtb"}, 1329 {"dtbs/bar.dto", "bar dtbo"}, 1330 } 1331 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 1332 type: kernel 1333 version: 5.0 1334 `, snap.R(5), kernelSnapFiles) 1335 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1336 err = os.Rename(kernelFn, kernelInSeed) 1337 c.Assert(err, IsNil) 1338 1339 bootWith := &boot.BootableSet{ 1340 RecoverySystemDir: "20191216", 1341 BasePath: baseInSeed, 1342 Base: baseInfo, 1343 KernelPath: kernelInSeed, 1344 Kernel: kernelInfo, 1345 Recovery: false, 1346 UnpackedGadgetDir: unpackedGadgetDir, 1347 } 1348 err = boot.MakeRunnableSystem(model, bootWith, nil) 1349 c.Assert(err, IsNil) 1350 1351 // also do the logical next thing which is to ensure that the system 1352 // reboots into run mode 1353 err = boot.EnsureNextBootToRunMode("20191216") 1354 c.Assert(err, IsNil) 1355 1356 // ensure base/kernel got copied to /var/lib/snapd/snaps 1357 c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap"), testutil.FilePresent) 1358 c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "arm-kernel_5.snap"), testutil.FilePresent) 1359 1360 // ensure the bootvars on ubuntu-seed got updated the right way 1361 mockSeedUbootenv := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel") 1362 uenvSeed, err := ubootenv.Open(mockSeedUbootenv) 1363 c.Assert(err, IsNil) 1364 c.Assert(uenvSeed.Get("snapd_recovery_mode"), Equals, "run") 1365 1366 // now check ubuntu-boot boot.sel 1367 mockBootUbootenv := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel") 1368 uenvBoot, err := ubootenv.Open(mockBootUbootenv) 1369 c.Assert(err, IsNil) 1370 c.Assert(uenvBoot.Get("snap_try_kernel"), Equals, "") 1371 c.Assert(uenvBoot.Get("snap_kernel"), Equals, "arm-kernel_5.snap") 1372 c.Assert(uenvBoot.Get("kernel_status"), Equals, boot.DefaultStatus) 1373 1374 // check that we have the extracted kernel in the right places, in the 1375 // old uc16/uc18 location 1376 for _, file := range kernelSnapFiles { 1377 fName := file[0] 1378 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/arm-kernel_5.snap", fName), testutil.FilePresent) 1379 } 1380 1381 // ensure modeenv looks correct 1382 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 1383 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run 1384 recovery_system=20191216 1385 current_recovery_systems=20191216 1386 good_recovery_systems=20191216 1387 base=core20_3.snap 1388 current_kernels=arm-kernel_5.snap 1389 model=my-brand/my-model-uc20 1390 grade=dangerous 1391 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 1392 `) 1393 } 1394 1395 func (s *makeBootable20Suite) TestMakeRecoverySystemBootableAtRuntime20(c *C) { 1396 bootloader.Force(nil) 1397 1398 // on uc20 the seed layout if different 1399 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1400 err := os.MkdirAll(seedSnapsDirs, 0755) 1401 c.Assert(err, IsNil) 1402 1403 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1404 type: kernel 1405 version: 5.0 1406 `, snap.R(5), [][]string{ 1407 {"kernel.efi", "I'm a kernel.efi"}, 1408 }) 1409 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1410 err = os.Rename(kernelFn, kernelInSeed) 1411 c.Assert(err, IsNil) 1412 1413 gadgets := map[string]string{} 1414 for _, rev := range []snap.Revision{snap.R(1), snap.R(5)} { 1415 gadgetFn, gadgetInfo := makeSnapWithFiles(c, "pc", gadgetSnapYaml, rev, [][]string{ 1416 {"grub.conf", ""}, 1417 {"meta/snap.yaml", gadgetSnapYaml}, 1418 {"cmdline.full", fmt.Sprintf("args from gadget rev %s", rev.String())}, 1419 }) 1420 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1421 err = os.Rename(gadgetFn, gadgetInSeed) 1422 c.Assert(err, IsNil) 1423 // keep track of the gadgets 1424 gadgets[rev.String()] = gadgetInSeed 1425 } 1426 1427 snaptest.PopulateDir(s.rootdir, [][]string{ 1428 {"EFI/ubuntu/grub.cfg", "this is grub"}, 1429 {"EFI/ubuntu/grubenv", "canary"}, 1430 }) 1431 1432 label := "20191209" 1433 recoverySystemDir := filepath.Join("/systems", label) 1434 err = boot.MakeRecoverySystemBootable(s.rootdir, recoverySystemDir, &boot.RecoverySystemBootableSet{ 1435 Kernel: kernelInfo, 1436 KernelPath: kernelInSeed, 1437 // use gadget revision 1 1438 GadgetSnapOrDir: gadgets["1"], 1439 // like it's called when creating a new recovery system 1440 PrepareImageTime: false, 1441 }) 1442 c.Assert(err, IsNil) 1443 // the recovery partition grubenv was not modified 1444 c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"), testutil.FileEquals, "canary") 1445 1446 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 1447 c.Assert(systemGenv.Load(), IsNil) 1448 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 1449 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 1450 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 1") 1451 1452 // create another system under a new label 1453 newLabel := "20210420" 1454 newRecoverySystemDir := filepath.Join("/systems", newLabel) 1455 // with a different gadget revision, but same kernel 1456 err = boot.MakeRecoverySystemBootable(s.rootdir, newRecoverySystemDir, &boot.RecoverySystemBootableSet{ 1457 Kernel: kernelInfo, 1458 KernelPath: kernelInSeed, 1459 GadgetSnapOrDir: gadgets["5"], 1460 // like it's called when creating a new recovery system 1461 PrepareImageTime: false, 1462 }) 1463 c.Assert(err, IsNil) 1464 1465 systemGenv = grubenv.NewEnv(filepath.Join(s.rootdir, newRecoverySystemDir, "grubenv")) 1466 c.Assert(systemGenv.Load(), IsNil) 1467 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 1468 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 1469 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 5") 1470 }