github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/boot/makebootable_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 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 := []byte("#grub-recovery cfg") 180 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 181 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644) 182 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 183 defer restore() 184 185 c.Assert(err, IsNil) 186 grubCfg := []byte("#grub cfg") 187 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 188 c.Assert(err, IsNil) 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) TestMakeBootableImage20UnsetRecoverySystemLabelError(c *C) { 251 model := boottest.MakeMockUC20Model() 252 253 unpackedGadgetDir := c.MkDir() 254 grubRecoveryCfg := []byte("#grub-recovery cfg") 255 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644) 256 c.Assert(err, IsNil) 257 grubCfg := []byte("#grub cfg") 258 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 259 c.Assert(err, IsNil) 260 261 label := "20191209" 262 recoverySystemDir := filepath.Join("/systems", label) 263 bootWith := &boot.BootableSet{ 264 RecoverySystemDir: recoverySystemDir, 265 UnpackedGadgetDir: unpackedGadgetDir, 266 Recovery: true, 267 } 268 269 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 270 c.Assert(err, ErrorMatches, "internal error: recovery system label unset") 271 } 272 273 func (s *makeBootable20Suite) TestMakeBootableImage20MultipleRecoverySystemsError(c *C) { 274 model := boottest.MakeMockUC20Model() 275 276 bootWith := &boot.BootableSet{Recovery: true} 277 err := os.MkdirAll(filepath.Join(s.rootdir, "systems/20191204"), 0755) 278 c.Assert(err, IsNil) 279 err = os.MkdirAll(filepath.Join(s.rootdir, "systems/20191205"), 0755) 280 c.Assert(err, IsNil) 281 282 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 283 c.Assert(err, ErrorMatches, "cannot make multiple recovery systems bootable yet") 284 } 285 286 func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) { 287 model := boottest.MakeMockModel() 288 289 err := boot.MakeRunnableSystem(model, nil, nil) 290 c.Assert(err, ErrorMatches, "internal error: cannot make non-uc20 system runnable") 291 } 292 293 func (s *makeBootable20Suite) TestMakeSystemRunnable20(c *C) { 294 bootloader.Force(nil) 295 296 model := boottest.MakeMockUC20Model() 297 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 298 err := os.MkdirAll(seedSnapsDirs, 0755) 299 c.Assert(err, IsNil) 300 301 // grub on ubuntu-seed 302 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 303 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 304 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 305 c.Assert(err, IsNil) 306 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 307 c.Assert(err, IsNil) 308 309 // setup recovery boot assets 310 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 311 c.Assert(err, IsNil) 312 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 313 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 314 []byte("recovery shim content"), 0644) 315 c.Assert(err, IsNil) 316 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 317 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 318 []byte("recovery grub content"), 0644) 319 c.Assert(err, IsNil) 320 321 // grub on ubuntu-boot 322 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 323 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 324 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 325 c.Assert(err, IsNil) 326 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 327 c.Assert(err, IsNil) 328 329 unpackedGadgetDir := c.MkDir() 330 grubRecoveryCfg := []byte("#grub-recovery cfg") 331 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 332 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644) 333 c.Assert(err, IsNil) 334 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 335 defer restore() 336 grubCfg := []byte("#grub cfg") 337 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 338 c.Assert(err, IsNil) 339 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 340 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 341 defer restore() 342 343 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "bootx64.efi"), []byte("shim content"), 0644) 344 c.Assert(err, IsNil) 345 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grubx64.efi"), []byte("grub content"), 0644) 346 c.Assert(err, IsNil) 347 348 // make the snaps symlinks so that we can ensure that makebootable follows 349 // the symlinks and copies the files and not the symlinks 350 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 351 type: base 352 version: 5.0 353 `, snap.R(3)) 354 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 355 err = os.Symlink(baseFn, baseInSeed) 356 c.Assert(err, IsNil) 357 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 358 type: kernel 359 version: 5.0 360 `, snap.R(5), 361 [][]string{ 362 {"kernel.efi", "I'm a kernel.efi"}, 363 }, 364 ) 365 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 366 err = os.Symlink(kernelFn, kernelInSeed) 367 c.Assert(err, IsNil) 368 369 bootWith := &boot.BootableSet{ 370 RecoverySystemDir: "20191216", 371 BasePath: baseInSeed, 372 Base: baseInfo, 373 KernelPath: kernelInSeed, 374 Kernel: kernelInfo, 375 Recovery: false, 376 UnpackedGadgetDir: unpackedGadgetDir, 377 } 378 379 // set up observer state 380 useEncryption := true 381 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 382 c.Assert(obs, NotNil) 383 c.Assert(err, IsNil) 384 runBootStruct := &gadget.LaidOutStructure{ 385 VolumeStructure: &gadget.VolumeStructure{ 386 Role: gadget.SystemBoot, 387 }, 388 } 389 390 // only grubx64.efi gets installed to system-boot 391 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 392 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 393 c.Assert(err, IsNil) 394 395 // observe recovery assets 396 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 397 c.Assert(err, IsNil) 398 399 // set encryption key 400 myKey := secboot.EncryptionKey{} 401 myKey2 := secboot.EncryptionKey{} 402 for i := range myKey { 403 myKey[i] = byte(i) 404 myKey2[i] = byte(128 + i) 405 } 406 obs.ChosenEncryptionKeys(myKey, myKey2) 407 408 // set a mock recovery kernel 409 readSystemEssentialCalls := 0 410 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 411 readSystemEssentialCalls++ 412 kernelSnap := &seed.Snap{ 413 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 414 SideInfo: &snap.SideInfo{ 415 Revision: snap.Revision{N: 1}, 416 RealName: "pc-kernel", 417 }, 418 } 419 return model, []*seed.Snap{kernelSnap}, nil 420 }) 421 defer restore() 422 423 // set mock key sealing 424 sealKeysCalls := 0 425 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 426 sealKeysCalls++ 427 switch sealKeysCalls { 428 case 1: 429 c.Check(keys, HasLen, 1) 430 c.Check(keys[0].Key, DeepEquals, myKey) 431 case 2: 432 c.Check(keys, HasLen, 2) 433 c.Check(keys[0].Key, DeepEquals, myKey) 434 c.Check(keys[1].Key, DeepEquals, myKey2) 435 default: 436 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 437 } 438 c.Assert(params.ModelParams, HasLen, 1) 439 440 shim := bootloader.NewBootFile("", filepath.Join(s.rootdir, 441 "var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"), 442 bootloader.RoleRecovery) 443 grub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 444 "var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"), 445 bootloader.RoleRecovery) 446 runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 447 "var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"), 448 bootloader.RoleRunMode) 449 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 450 runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode) 451 452 switch sealKeysCalls { 453 case 1: 454 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 455 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 456 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))), 457 }) 458 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 459 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 460 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 461 }) 462 case 2: 463 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 464 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 465 }) 466 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 467 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 468 }) 469 default: 470 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 471 } 472 473 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 474 475 return nil 476 }) 477 defer restore() 478 479 err = boot.MakeRunnableSystem(model, bootWith, obs) 480 c.Assert(err, IsNil) 481 482 // also do the logical thing and make the next boot go to run mode 483 err = boot.EnsureNextBootToRunMode("20191216") 484 c.Assert(err, IsNil) 485 486 // ensure grub.cfg in boot was installed from internal assets 487 c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset)) 488 489 // ensure base/kernel got copied to /var/lib/snapd/snaps 490 core20Snap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap") 491 pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "pc-kernel_5.snap") 492 c.Check(core20Snap, testutil.FilePresent) 493 c.Check(pcKernelSnap, testutil.FilePresent) 494 c.Check(osutil.IsSymlink(core20Snap), Equals, false) 495 c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false) 496 497 // ensure the bootvars got updated the right way 498 mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv") 499 c.Check(mockSeedGrubenv, testutil.FilePresent) 500 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run") 501 mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv") 502 c.Check(mockBootGrubenv, testutil.FilePresent) 503 504 // ensure that kernel_status is empty, we specifically want this to be set 505 // to the empty string 506 // use (?m) to match multi-line file in the regex here, because the file is 507 // a grubenv with padding #### blocks 508 c.Check(mockBootGrubenv, testutil.FileMatches, `(?m)^kernel_status=$`) 509 510 // check that we have the extracted kernel in the right places, both in the 511 // old uc16/uc18 location and the new ubuntu-boot partition grub dir 512 extractedKernel := filepath.Join(mockBootGrubDir, "pc-kernel_5.snap", "kernel.efi") 513 c.Check(extractedKernel, testutil.FilePresent) 514 515 // the new uc20 location 516 extractedKernelSymlink := filepath.Join(mockBootGrubDir, "kernel.efi") 517 c.Check(extractedKernelSymlink, testutil.FilePresent) 518 519 // ensure modeenv looks correct 520 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 521 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run 522 recovery_system=20191216 523 current_recovery_systems=20191216 524 good_recovery_systems=20191216 525 base=core20_3.snap 526 current_kernels=pc-kernel_5.snap 527 model=my-brand/my-model-uc20 528 grade=dangerous 529 current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]} 530 current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]} 531 current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"] 532 `) 533 copiedGrubBin := filepath.Join( 534 dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), 535 "grub", 536 "grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d", 537 ) 538 copiedRecoveryGrubBin := filepath.Join( 539 dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), 540 "grub", 541 "grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5", 542 ) 543 copiedRecoveryShimBin := filepath.Join( 544 dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), 545 "grub", 546 "bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37", 547 ) 548 549 // only one file in the cache under new root 550 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), "grub", "*"), []string{ 551 copiedRecoveryShimBin, 552 copiedGrubBin, 553 copiedRecoveryGrubBin, 554 }) 555 // with the right content 556 c.Check(copiedGrubBin, testutil.FileEquals, "grub content") 557 c.Check(copiedRecoveryGrubBin, testutil.FileEquals, "recovery grub content") 558 c.Check(copiedRecoveryShimBin, testutil.FileEquals, "recovery shim content") 559 560 // make sure SealKey was called for the run object and the fallback object 561 c.Check(sealKeysCalls, Equals, 2) 562 563 // make sure the marker file for sealed key was created 564 c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent) 565 566 // make sure we wrote the boot chains data file 567 c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent) 568 } 569 570 func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) { 571 bootloader.Force(nil) 572 573 model := boottest.MakeMockUC20Model() 574 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 575 err := os.MkdirAll(seedSnapsDirs, 0755) 576 c.Assert(err, IsNil) 577 578 // grub on ubuntu-seed 579 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 580 err = os.MkdirAll(mockSeedGrubDir, 0755) 581 c.Assert(err, IsNil) 582 // no recovery grub.cfg so that test fails if it ever reaches that point 583 584 // grub on ubuntu-boot 585 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 586 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 587 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 588 c.Assert(err, IsNil) 589 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 590 c.Assert(err, IsNil) 591 592 unpackedGadgetDir := c.MkDir() 593 594 // make the snaps symlinks so that we can ensure that makebootable follows 595 // the symlinks and copies the files and not the symlinks 596 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 597 type: base 598 version: 5.0 599 `, snap.R(3)) 600 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 601 err = os.Symlink(baseFn, baseInSeed) 602 c.Assert(err, IsNil) 603 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 604 type: kernel 605 version: 5.0 606 `, snap.R(5), 607 [][]string{ 608 {"kernel.efi", "I'm a kernel.efi"}, 609 }, 610 ) 611 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 612 err = os.Symlink(kernelFn, kernelInSeed) 613 c.Assert(err, IsNil) 614 615 bootWith := &boot.BootableSet{ 616 RecoverySystemDir: "20191216", 617 BasePath: baseInSeed, 618 Base: baseInfo, 619 KernelPath: kernelInSeed, 620 Kernel: kernelInfo, 621 Recovery: false, 622 UnpackedGadgetDir: unpackedGadgetDir, 623 } 624 625 // no grub marker in gadget directory raises an error 626 err = boot.MakeRunnableSystem(model, bootWith, nil) 627 c.Assert(err, ErrorMatches, "internal error: cannot identify run system bootloader: cannot determine bootloader") 628 629 // set up grub.cfg in gadget 630 grubCfg := []byte("#grub cfg") 631 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 632 c.Assert(err, IsNil) 633 634 // no write access to destination directory 635 restore := assets.MockInternal("grub.cfg", nil) 636 defer restore() 637 err = boot.MakeRunnableSystem(model, bootWith, nil) 638 c.Assert(err, ErrorMatches, `cannot install managed bootloader assets: internal error: no boot asset for "grub.cfg"`) 639 } 640 641 func (s *makeBootable20Suite) TestMakeRunnableSystem20RunModeSealKeyErr(c *C) { 642 bootloader.Force(nil) 643 644 model := boottest.MakeMockUC20Model() 645 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 646 err := os.MkdirAll(seedSnapsDirs, 0755) 647 c.Assert(err, IsNil) 648 649 // grub on ubuntu-seed 650 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 651 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 652 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 653 c.Assert(err, IsNil) 654 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 655 c.Assert(err, IsNil) 656 657 // setup recovery boot assets 658 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 659 c.Assert(err, IsNil) 660 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 661 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 662 []byte("recovery shim content"), 0644) 663 c.Assert(err, IsNil) 664 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 665 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 666 []byte("recovery grub content"), 0644) 667 c.Assert(err, IsNil) 668 669 // grub on ubuntu-boot 670 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 671 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 672 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 673 c.Assert(err, IsNil) 674 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 675 c.Assert(err, IsNil) 676 677 unpackedGadgetDir := c.MkDir() 678 grubRecoveryCfg := []byte("#grub-recovery cfg") 679 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 680 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644) 681 c.Assert(err, IsNil) 682 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 683 defer restore() 684 grubCfg := []byte("#grub cfg") 685 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 686 c.Assert(err, IsNil) 687 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 688 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 689 defer restore() 690 691 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "bootx64.efi"), []byte("shim content"), 0644) 692 c.Assert(err, IsNil) 693 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grubx64.efi"), []byte("grub content"), 0644) 694 c.Assert(err, IsNil) 695 696 // make the snaps symlinks so that we can ensure that makebootable follows 697 // the symlinks and copies the files and not the symlinks 698 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 699 type: base 700 version: 5.0 701 `, snap.R(3)) 702 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 703 err = os.Symlink(baseFn, baseInSeed) 704 c.Assert(err, IsNil) 705 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 706 type: kernel 707 version: 5.0 708 `, snap.R(5), 709 [][]string{ 710 {"kernel.efi", "I'm a kernel.efi"}, 711 }, 712 ) 713 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 714 err = os.Symlink(kernelFn, kernelInSeed) 715 c.Assert(err, IsNil) 716 717 bootWith := &boot.BootableSet{ 718 RecoverySystemDir: "20191216", 719 BasePath: baseInSeed, 720 Base: baseInfo, 721 KernelPath: kernelInSeed, 722 Kernel: kernelInfo, 723 Recovery: false, 724 UnpackedGadgetDir: unpackedGadgetDir, 725 } 726 727 // set up observer state 728 useEncryption := true 729 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 730 c.Assert(obs, NotNil) 731 c.Assert(err, IsNil) 732 runBootStruct := &gadget.LaidOutStructure{ 733 VolumeStructure: &gadget.VolumeStructure{ 734 Role: gadget.SystemBoot, 735 }, 736 } 737 738 // only grubx64.efi gets installed to system-boot 739 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 740 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 741 c.Assert(err, IsNil) 742 743 // observe recovery assets 744 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 745 c.Assert(err, IsNil) 746 747 // set encryption key 748 myKey := secboot.EncryptionKey{} 749 myKey2 := secboot.EncryptionKey{} 750 for i := range myKey { 751 myKey[i] = byte(i) 752 myKey2[i] = byte(128 + i) 753 } 754 obs.ChosenEncryptionKeys(myKey, myKey2) 755 756 // set a mock recovery kernel 757 readSystemEssentialCalls := 0 758 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 759 readSystemEssentialCalls++ 760 kernelSnap := &seed.Snap{ 761 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 762 SideInfo: &snap.SideInfo{ 763 Revision: snap.Revision{N: 0}, 764 RealName: "pc-kernel", 765 }, 766 } 767 return model, []*seed.Snap{kernelSnap}, nil 768 }) 769 defer restore() 770 771 // set mock key sealing 772 sealKeysCalls := 0 773 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 774 sealKeysCalls++ 775 switch sealKeysCalls { 776 case 1: 777 c.Check(keys, HasLen, 1) 778 c.Check(keys[0].Key, DeepEquals, myKey) 779 case 2: 780 c.Check(keys, HasLen, 2) 781 c.Check(keys[0].Key, DeepEquals, myKey) 782 c.Check(keys[1].Key, DeepEquals, myKey2) 783 default: 784 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 785 } 786 c.Assert(params.ModelParams, HasLen, 1) 787 788 shim := bootloader.NewBootFile("", filepath.Join(s.rootdir, 789 "var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"), 790 bootloader.RoleRecovery) 791 grub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 792 "var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"), 793 bootloader.RoleRecovery) 794 runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 795 "var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"), 796 bootloader.RoleRunMode) 797 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 798 runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode) 799 800 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 801 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 802 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))), 803 }) 804 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 805 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 806 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 807 }) 808 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 809 810 return fmt.Errorf("seal error") 811 }) 812 defer restore() 813 814 err = boot.MakeRunnableSystem(model, bootWith, obs) 815 c.Assert(err, ErrorMatches, "cannot seal the encryption keys: seal error") 816 } 817 818 func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20TraditionalUbootenvFails(c *C) { 819 bootloader.Force(nil) 820 model := boottest.MakeMockUC20Model() 821 822 unpackedGadgetDir := c.MkDir() 823 ubootEnv := []byte("#uboot env") 824 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), ubootEnv, 0644) 825 c.Assert(err, IsNil) 826 827 // on uc20 the seed layout if different 828 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 829 err = os.MkdirAll(seedSnapsDirs, 0755) 830 c.Assert(err, IsNil) 831 832 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 833 type: base 834 version: 5.0 835 `, snap.R(3)) 836 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 837 err = os.Rename(baseFn, baseInSeed) 838 c.Assert(err, IsNil) 839 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 840 type: kernel 841 version: 5.0 842 `, snap.R(5), [][]string{ 843 {"kernel.img", "I'm a kernel"}, 844 {"initrd.img", "...and I'm an initrd"}, 845 {"dtbs/foo.dtb", "foo dtb"}, 846 {"dtbs/bar.dto", "bar dtbo"}, 847 }) 848 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 849 err = os.Rename(kernelFn, kernelInSeed) 850 c.Assert(err, IsNil) 851 852 label := "20191209" 853 recoverySystemDir := filepath.Join("/systems", label) 854 bootWith := &boot.BootableSet{ 855 Base: baseInfo, 856 BasePath: baseInSeed, 857 Kernel: kernelInfo, 858 KernelPath: kernelInSeed, 859 RecoverySystemDir: recoverySystemDir, 860 RecoverySystemLabel: label, 861 UnpackedGadgetDir: unpackedGadgetDir, 862 Recovery: true, 863 } 864 865 // TODO:UC20: enable this use case 866 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 867 c.Assert(err, ErrorMatches, "non-empty uboot.env not supported on UC20 yet") 868 } 869 870 func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20BootScr(c *C) { 871 model := boottest.MakeMockUC20Model() 872 873 unpackedGadgetDir := c.MkDir() 874 // the uboot.conf must be empty for this to work/do the right thing 875 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644) 876 c.Assert(err, IsNil) 877 878 // on uc20 the seed layout if different 879 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 880 err = os.MkdirAll(seedSnapsDirs, 0755) 881 c.Assert(err, IsNil) 882 883 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 884 type: base 885 version: 5.0 886 `, snap.R(3)) 887 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 888 err = os.Rename(baseFn, baseInSeed) 889 c.Assert(err, IsNil) 890 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 891 type: kernel 892 version: 5.0 893 `, snap.R(5), [][]string{ 894 {"kernel.img", "I'm a kernel"}, 895 {"initrd.img", "...and I'm an initrd"}, 896 {"dtbs/foo.dtb", "foo dtb"}, 897 {"dtbs/bar.dto", "bar dtbo"}, 898 }) 899 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 900 err = os.Rename(kernelFn, kernelInSeed) 901 c.Assert(err, IsNil) 902 903 label := "20191209" 904 recoverySystemDir := filepath.Join("/systems", label) 905 bootWith := &boot.BootableSet{ 906 Base: baseInfo, 907 BasePath: baseInSeed, 908 Kernel: kernelInfo, 909 KernelPath: kernelInSeed, 910 RecoverySystemDir: recoverySystemDir, 911 RecoverySystemLabel: label, 912 UnpackedGadgetDir: unpackedGadgetDir, 913 Recovery: true, 914 } 915 916 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 917 c.Assert(err, IsNil) 918 919 // since uboot.conf was absent, we won't have installed the uboot.env, as 920 // it is expected that the gadget assets would have installed boot.scr 921 // instead 922 c.Check(filepath.Join(s.rootdir, "uboot.env"), testutil.FileAbsent) 923 924 c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{ 925 "snapd_recovery_system": label, 926 "snapd_recovery_mode": "install", 927 }) 928 929 // ensure the correct recovery system configuration was set 930 c.Check( 931 s.bootloader.ExtractRecoveryKernelAssetsCalls, 932 DeepEquals, 933 []bootloadertest.ExtractedRecoveryKernelCall{{ 934 RecoverySystemDir: recoverySystemDir, 935 S: kernelInfo, 936 }}, 937 ) 938 } 939 940 func (s *makeBootable20UbootSuite) TestUbootMakeRunnableSystem20RunModeBootSel(c *C) { 941 bootloader.Force(nil) 942 943 model := boottest.MakeMockUC20Model() 944 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 945 err := os.MkdirAll(seedSnapsDirs, 0755) 946 c.Assert(err, IsNil) 947 948 // uboot on ubuntu-seed 949 mockSeedUbootBootSel := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel") 950 err = os.MkdirAll(filepath.Dir(mockSeedUbootBootSel), 0755) 951 c.Assert(err, IsNil) 952 env, err := ubootenv.Create(mockSeedUbootBootSel, 4096) 953 c.Assert(err, IsNil) 954 c.Assert(env.Save(), IsNil) 955 956 // uboot on ubuntu-boot (as if it was installed when creating the partition) 957 mockBootUbootBootSel := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel") 958 err = os.MkdirAll(filepath.Dir(mockBootUbootBootSel), 0755) 959 c.Assert(err, IsNil) 960 env, err = ubootenv.Create(mockBootUbootBootSel, 4096) 961 c.Assert(err, IsNil) 962 c.Assert(env.Save(), IsNil) 963 964 unpackedGadgetDir := c.MkDir() 965 c.Assert(ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644), IsNil) 966 967 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 968 type: base 969 version: 5.0 970 `, snap.R(3)) 971 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 972 err = os.Rename(baseFn, baseInSeed) 973 c.Assert(err, IsNil) 974 kernelSnapFiles := [][]string{ 975 {"kernel.img", "I'm a kernel"}, 976 {"initrd.img", "...and I'm an initrd"}, 977 {"dtbs/foo.dtb", "foo dtb"}, 978 {"dtbs/bar.dto", "bar dtbo"}, 979 } 980 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 981 type: kernel 982 version: 5.0 983 `, snap.R(5), kernelSnapFiles) 984 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 985 err = os.Rename(kernelFn, kernelInSeed) 986 c.Assert(err, IsNil) 987 988 bootWith := &boot.BootableSet{ 989 RecoverySystemDir: "20191216", 990 BasePath: baseInSeed, 991 Base: baseInfo, 992 KernelPath: kernelInSeed, 993 Kernel: kernelInfo, 994 Recovery: false, 995 UnpackedGadgetDir: unpackedGadgetDir, 996 } 997 err = boot.MakeRunnableSystem(model, bootWith, nil) 998 c.Assert(err, IsNil) 999 1000 // also do the logical next thing which is to ensure that the system 1001 // reboots into run mode 1002 err = boot.EnsureNextBootToRunMode("20191216") 1003 c.Assert(err, IsNil) 1004 1005 // ensure base/kernel got copied to /var/lib/snapd/snaps 1006 c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap"), testutil.FilePresent) 1007 c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "arm-kernel_5.snap"), testutil.FilePresent) 1008 1009 // ensure the bootvars on ubuntu-seed got updated the right way 1010 mockSeedUbootenv := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel") 1011 uenvSeed, err := ubootenv.Open(mockSeedUbootenv) 1012 c.Assert(err, IsNil) 1013 c.Assert(uenvSeed.Get("snapd_recovery_mode"), Equals, "run") 1014 1015 // now check ubuntu-boot boot.sel 1016 mockBootUbootenv := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel") 1017 uenvBoot, err := ubootenv.Open(mockBootUbootenv) 1018 c.Assert(err, IsNil) 1019 c.Assert(uenvBoot.Get("snap_try_kernel"), Equals, "") 1020 c.Assert(uenvBoot.Get("snap_kernel"), Equals, "arm-kernel_5.snap") 1021 c.Assert(uenvBoot.Get("kernel_status"), Equals, boot.DefaultStatus) 1022 1023 // check that we have the extracted kernel in the right places, in the 1024 // old uc16/uc18 location 1025 for _, file := range kernelSnapFiles { 1026 fName := file[0] 1027 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/arm-kernel_5.snap", fName), testutil.FilePresent) 1028 } 1029 1030 // ensure modeenv looks correct 1031 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 1032 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run 1033 recovery_system=20191216 1034 current_recovery_systems=20191216 1035 good_recovery_systems=20191216 1036 base=core20_3.snap 1037 current_kernels=arm-kernel_5.snap 1038 model=my-brand/my-model-uc20 1039 grade=dangerous 1040 `) 1041 }