github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/bootloader/grub_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 bootloader_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 29 "github.com/mvo5/goconfigparser" 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/bootloader" 33 "github.com/snapcore/snapd/bootloader/assets" 34 "github.com/snapcore/snapd/bootloader/grubenv" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/snap" 38 "github.com/snapcore/snapd/snap/snapfile" 39 "github.com/snapcore/snapd/snap/snaptest" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 type grubTestSuite struct { 44 baseBootenvTestSuite 45 46 bootdir string 47 } 48 49 var _ = Suite(&grubTestSuite{}) 50 51 func (s *grubTestSuite) SetUpTest(c *C) { 52 s.baseBootenvTestSuite.SetUpTest(c) 53 bootloader.MockGrubFiles(c, s.rootdir) 54 55 s.bootdir = filepath.Join(s.rootdir, "boot") 56 } 57 58 // grubEditenvCmd finds the right grub{,2}-editenv command 59 func grubEditenvCmd() string { 60 for _, exe := range []string{"grub2-editenv", "grub-editenv"} { 61 if osutil.ExecutableExists(exe) { 62 return exe 63 } 64 } 65 return "" 66 } 67 68 func grubEnvPath(rootdir string) string { 69 return filepath.Join(rootdir, "boot/grub/grubenv") 70 } 71 72 func (s *grubTestSuite) grubEditenvSet(c *C, key, value string) { 73 if grubEditenvCmd() == "" { 74 c.Skip("grub{,2}-editenv is not available") 75 } 76 77 output, err := exec.Command(grubEditenvCmd(), grubEnvPath(s.rootdir), "set", fmt.Sprintf("%s=%s", key, value)).CombinedOutput() 78 c.Check(err, IsNil) 79 c.Check(string(output), Equals, "") 80 } 81 82 func (s *grubTestSuite) grubEditenvGet(c *C, key string) string { 83 if grubEditenvCmd() == "" { 84 c.Skip("grub{,2}-editenv is not available") 85 } 86 87 output, err := exec.Command(grubEditenvCmd(), grubEnvPath(s.rootdir), "list").CombinedOutput() 88 c.Assert(err, IsNil) 89 cfg := goconfigparser.New() 90 cfg.AllowNoSectionHeader = true 91 err = cfg.ReadString(string(output)) 92 c.Assert(err, IsNil) 93 v, err := cfg.Get("", key) 94 c.Assert(err, IsNil) 95 return v 96 } 97 98 func (s *grubTestSuite) makeFakeGrubEnv(c *C) { 99 s.grubEditenvSet(c, "k", "v") 100 } 101 102 func (s *grubTestSuite) TestNewGrub(c *C) { 103 // no files means bl is not present, but we can still create the bl object 104 c.Assert(os.RemoveAll(s.rootdir), IsNil) 105 g := bootloader.NewGrub(s.rootdir, nil) 106 c.Assert(g, NotNil) 107 c.Assert(g.Name(), Equals, "grub") 108 109 present, err := g.Present() 110 c.Assert(err, IsNil) 111 c.Assert(present, Equals, false) 112 113 // now with files present, the bl is present 114 bootloader.MockGrubFiles(c, s.rootdir) 115 s.makeFakeGrubEnv(c) 116 present, err = g.Present() 117 c.Assert(err, IsNil) 118 c.Assert(present, Equals, true) 119 } 120 121 func (s *grubTestSuite) TestGetBootloaderWithGrub(c *C) { 122 s.makeFakeGrubEnv(c) 123 124 bootloader, err := bootloader.Find(s.rootdir, nil) 125 c.Assert(err, IsNil) 126 c.Assert(bootloader.Name(), Equals, "grub") 127 } 128 129 func (s *grubTestSuite) TestGetBootloaderWithGrubWithDefaultRoot(c *C) { 130 s.makeFakeGrubEnv(c) 131 132 dirs.SetRootDir(s.rootdir) 133 defer func() { dirs.SetRootDir("") }() 134 135 bootloader, err := bootloader.Find("", nil) 136 c.Assert(err, IsNil) 137 c.Assert(bootloader.Name(), Equals, "grub") 138 } 139 140 func (s *grubTestSuite) TestGetBootVer(c *C) { 141 s.makeFakeGrubEnv(c) 142 s.grubEditenvSet(c, "snap_mode", "regular") 143 144 g := bootloader.NewGrub(s.rootdir, nil) 145 v, err := g.GetBootVars("snap_mode") 146 c.Assert(err, IsNil) 147 c.Check(v, HasLen, 1) 148 c.Check(v["snap_mode"], Equals, "regular") 149 } 150 151 func (s *grubTestSuite) TestSetBootVer(c *C) { 152 s.makeFakeGrubEnv(c) 153 154 g := bootloader.NewGrub(s.rootdir, nil) 155 err := g.SetBootVars(map[string]string{ 156 "k1": "v1", 157 "k2": "v2", 158 }) 159 c.Assert(err, IsNil) 160 161 c.Check(s.grubEditenvGet(c, "k1"), Equals, "v1") 162 c.Check(s.grubEditenvGet(c, "k2"), Equals, "v2") 163 } 164 165 func (s *grubTestSuite) TestExtractKernelAssetsNoUnpacksKernelForGrub(c *C) { 166 s.makeFakeGrubEnv(c) 167 168 g := bootloader.NewGrub(s.rootdir, nil) 169 170 files := [][]string{ 171 {"kernel.img", "I'm a kernel"}, 172 {"initrd.img", "...and I'm an initrd"}, 173 {"meta/kernel.yaml", "version: 4.2"}, 174 } 175 si := &snap.SideInfo{ 176 RealName: "ubuntu-kernel", 177 Revision: snap.R(42), 178 } 179 fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 180 snapf, err := snapfile.Open(fn) 181 c.Assert(err, IsNil) 182 183 info, err := snap.ReadInfoFromSnapFile(snapf, si) 184 c.Assert(err, IsNil) 185 186 err = g.ExtractKernelAssets(info, snapf) 187 c.Assert(err, IsNil) 188 189 // kernel is *not* here 190 kernimg := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "kernel.img") 191 c.Assert(osutil.FileExists(kernimg), Equals, false) 192 } 193 194 func (s *grubTestSuite) TestExtractKernelForceWorks(c *C) { 195 s.makeFakeGrubEnv(c) 196 197 g := bootloader.NewGrub(s.rootdir, nil) 198 c.Assert(g, NotNil) 199 200 files := [][]string{ 201 {"kernel.img", "I'm a kernel"}, 202 {"initrd.img", "...and I'm an initrd"}, 203 {"meta/force-kernel-extraction", ""}, 204 {"meta/kernel.yaml", "version: 4.2"}, 205 } 206 si := &snap.SideInfo{ 207 RealName: "ubuntu-kernel", 208 Revision: snap.R(42), 209 } 210 fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 211 snapf, err := snapfile.Open(fn) 212 c.Assert(err, IsNil) 213 214 info, err := snap.ReadInfoFromSnapFile(snapf, si) 215 c.Assert(err, IsNil) 216 217 err = g.ExtractKernelAssets(info, snapf) 218 c.Assert(err, IsNil) 219 220 // kernel is extracted 221 kernimg := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "kernel.img") 222 c.Assert(osutil.FileExists(kernimg), Equals, true) 223 // initrd 224 initrdimg := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "initrd.img") 225 c.Assert(osutil.FileExists(initrdimg), Equals, true) 226 227 // ensure that removal of assets also works 228 err = g.RemoveKernelAssets(info) 229 c.Assert(err, IsNil) 230 exists, _, err := osutil.DirExists(filepath.Dir(kernimg)) 231 c.Assert(err, IsNil) 232 c.Check(exists, Equals, false) 233 } 234 235 func (s *grubTestSuite) grubDir() string { 236 return filepath.Join(s.bootdir, "grub") 237 } 238 239 func (s *grubTestSuite) grubEFINativeDir() string { 240 return filepath.Join(s.rootdir, "EFI/ubuntu") 241 } 242 243 func (s *grubTestSuite) makeFakeGrubEFINativeEnv(c *C, content []byte) { 244 err := os.MkdirAll(s.grubEFINativeDir(), 0755) 245 c.Assert(err, IsNil) 246 err = ioutil.WriteFile(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), content, 0644) 247 c.Assert(err, IsNil) 248 } 249 250 func (s *grubTestSuite) TestNewGrubWithOptionRecovery(c *C) { 251 s.makeFakeGrubEFINativeEnv(c, nil) 252 253 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 254 c.Assert(g, NotNil) 255 c.Assert(g.Name(), Equals, "grub") 256 } 257 258 func (s *grubTestSuite) TestNewGrubWithOptionRecoveryBootEnv(c *C) { 259 s.makeFakeGrubEFINativeEnv(c, nil) 260 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 261 262 // check that setting vars goes to the right place 263 c.Check(filepath.Join(s.grubEFINativeDir(), "grubenv"), testutil.FileAbsent) 264 err := g.SetBootVars(map[string]string{ 265 "k1": "v1", 266 "k2": "v2", 267 }) 268 c.Assert(err, IsNil) 269 c.Check(filepath.Join(s.grubEFINativeDir(), "grubenv"), testutil.FilePresent) 270 271 env, err := g.GetBootVars("k1", "k2") 272 c.Assert(err, IsNil) 273 c.Check(env, DeepEquals, map[string]string{ 274 "k1": "v1", 275 "k2": "v2", 276 }) 277 } 278 279 func (s *grubTestSuite) TestNewGrubWithOptionRecoveryNoEnv(c *C) { 280 // fake a *regular* grub env 281 s.makeFakeGrubEnv(c) 282 283 // we can't create a recovery grub with that 284 g, err := bootloader.Find(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 285 c.Assert(g, IsNil) 286 c.Assert(err, Equals, bootloader.ErrBootloader) 287 } 288 289 func (s *grubTestSuite) TestGrubSetRecoverySystemEnv(c *C) { 290 s.makeFakeGrubEFINativeEnv(c, nil) 291 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 292 293 // check that we can set a recovery system specific bootenv 294 bvars := map[string]string{ 295 "snapd_recovery_kernel": "/snaps/pc-kernel_1.snap", 296 "other_options": "are-supported", 297 } 298 299 err := g.SetRecoverySystemEnv("/systems/20191209", bvars) 300 c.Assert(err, IsNil) 301 recoverySystemGrubenv := filepath.Join(s.rootdir, "/systems/20191209/grubenv") 302 c.Assert(recoverySystemGrubenv, testutil.FilePresent) 303 304 genv := grubenv.NewEnv(recoverySystemGrubenv) 305 err = genv.Load() 306 c.Assert(err, IsNil) 307 c.Check(genv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_1.snap") 308 c.Check(genv.Get("other_options"), Equals, "are-supported") 309 } 310 311 func (s *grubTestSuite) TestGetRecoverySystemEnv(c *C) { 312 s.makeFakeGrubEFINativeEnv(c, nil) 313 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 314 315 err := os.MkdirAll(filepath.Join(s.rootdir, "/systems/20191209"), 0755) 316 c.Assert(err, IsNil) 317 recoverySystemGrubenv := filepath.Join(s.rootdir, "/systems/20191209/grubenv") 318 319 // does not fail when there is no recovery env 320 value, err := g.GetRecoverySystemEnv("/systems/20191209", "no_file") 321 c.Assert(err, IsNil) 322 c.Check(value, Equals, "") 323 324 genv := grubenv.NewEnv(recoverySystemGrubenv) 325 genv.Set("snapd_extra_cmdline_args", "foo bar baz") 326 genv.Set("random_option", `has "some spaces"`) 327 err = genv.Save() 328 c.Assert(err, IsNil) 329 330 value, err = g.GetRecoverySystemEnv("/systems/20191209", "snapd_extra_cmdline_args") 331 c.Assert(err, IsNil) 332 c.Check(value, Equals, "foo bar baz") 333 value, err = g.GetRecoverySystemEnv("/systems/20191209", "random_option") 334 c.Assert(err, IsNil) 335 c.Check(value, Equals, `has "some spaces"`) 336 value, err = g.GetRecoverySystemEnv("/systems/20191209", "not_set") 337 c.Assert(err, IsNil) 338 c.Check(value, Equals, ``) 339 } 340 341 func (s *grubTestSuite) makeKernelAssetSnap(c *C, snapFileName string) snap.PlaceInfo { 342 kernelSnap, err := snap.ParsePlaceInfoFromSnapFileName(snapFileName) 343 c.Assert(err, IsNil) 344 345 // make a kernel.efi snap as it would be by ExtractKernelAssets() 346 kernelSnapExtractedAssetsDir := filepath.Join(s.grubDir(), snapFileName) 347 err = os.MkdirAll(kernelSnapExtractedAssetsDir, 0755) 348 c.Assert(err, IsNil) 349 350 err = ioutil.WriteFile(filepath.Join(kernelSnapExtractedAssetsDir, "kernel.efi"), nil, 0644) 351 c.Assert(err, IsNil) 352 353 return kernelSnap 354 } 355 356 func (s *grubTestSuite) makeKernelAssetSnapAndSymlink(c *C, snapFileName, symlinkName string) snap.PlaceInfo { 357 kernelSnap := s.makeKernelAssetSnap(c, snapFileName) 358 359 // make a kernel.efi symlink to the kernel.efi above 360 err := os.Symlink( 361 filepath.Join(snapFileName, "kernel.efi"), 362 filepath.Join(s.grubDir(), symlinkName), 363 ) 364 c.Assert(err, IsNil) 365 366 return kernelSnap 367 } 368 369 func (s *grubTestSuite) TestGrubExtractedRunKernelImageKernel(c *C) { 370 s.makeFakeGrubEnv(c) 371 g := bootloader.NewGrub(s.rootdir, nil) 372 eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader) 373 c.Assert(ok, Equals, true) 374 375 kernel := s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_1.snap", "kernel.efi") 376 377 // ensure that the returned kernel is the same as the one we put there 378 sn, err := eg.Kernel() 379 c.Assert(err, IsNil) 380 c.Assert(sn, DeepEquals, kernel) 381 } 382 383 func (s *grubTestSuite) TestGrubExtractedRunKernelImageTryKernel(c *C) { 384 s.makeFakeGrubEnv(c) 385 g := bootloader.NewGrub(s.rootdir, nil) 386 eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader) 387 c.Assert(ok, Equals, true) 388 389 // ensure it doesn't return anything when the symlink doesn't exist 390 _, err := eg.TryKernel() 391 c.Assert(err, Equals, bootloader.ErrNoTryKernelRef) 392 393 // when a bad kernel snap name is in the extracted path, it will complain 394 // appropriately 395 kernelSnapExtractedAssetsDir := filepath.Join(s.grubDir(), "bad_snap_rev_name") 396 badKernelSnapPath := filepath.Join(kernelSnapExtractedAssetsDir, "kernel.efi") 397 tryKernelSymlink := filepath.Join(s.grubDir(), "try-kernel.efi") 398 err = os.MkdirAll(kernelSnapExtractedAssetsDir, 0755) 399 c.Assert(err, IsNil) 400 401 err = ioutil.WriteFile(badKernelSnapPath, nil, 0644) 402 c.Assert(err, IsNil) 403 404 err = os.Symlink("bad_snap_rev_name/kernel.efi", tryKernelSymlink) 405 c.Assert(err, IsNil) 406 407 _, err = eg.TryKernel() 408 c.Assert(err, ErrorMatches, "cannot parse kernel snap file name from symlink target \"bad_snap_rev_name\": .*") 409 410 // remove the bad symlink 411 err = os.Remove(tryKernelSymlink) 412 c.Assert(err, IsNil) 413 414 // make a real symlink 415 tryKernel := s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_2.snap", "try-kernel.efi") 416 417 // ensure that the returned kernel is the same as the one we put there 418 sn, err := eg.TryKernel() 419 c.Assert(err, IsNil) 420 c.Assert(sn, DeepEquals, tryKernel) 421 422 // if the destination of the symlink is removed, we get an error 423 err = os.Remove(filepath.Join(s.grubDir(), "pc-kernel_2.snap", "kernel.efi")) 424 c.Assert(err, IsNil) 425 _, err = eg.TryKernel() 426 c.Assert(err, ErrorMatches, "cannot read dangling symlink try-kernel.efi") 427 } 428 429 func (s *grubTestSuite) TestGrubExtractedRunKernelImageEnableKernel(c *C) { 430 s.makeFakeGrubEnv(c) 431 g := bootloader.NewGrub(s.rootdir, nil) 432 eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader) 433 c.Assert(ok, Equals, true) 434 435 // ensure we fail to create a dangling symlink to a kernel snap that was not 436 // actually extracted 437 nonExistSnap, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_12.snap") 438 c.Assert(err, IsNil) 439 err = eg.EnableKernel(nonExistSnap) 440 c.Assert(err, ErrorMatches, "cannot enable kernel.efi at pc-kernel_12.snap/kernel.efi: file does not exist") 441 442 kernel := s.makeKernelAssetSnap(c, "pc-kernel_1.snap") 443 444 // enable the Kernel we extracted 445 err = eg.EnableKernel(kernel) 446 c.Assert(err, IsNil) 447 448 // ensure that the symlink was put where we expect it 449 asset, err := os.Readlink(filepath.Join(s.grubDir(), "kernel.efi")) 450 c.Assert(err, IsNil) 451 c.Assert(asset, DeepEquals, filepath.Join("pc-kernel_1.snap", "kernel.efi")) 452 453 // create a new kernel snap and ensure that we can safely enable that one 454 // too 455 kernel2 := s.makeKernelAssetSnap(c, "pc-kernel_2.snap") 456 err = eg.EnableKernel(kernel2) 457 c.Assert(err, IsNil) 458 459 // ensure that the symlink was put where we expect it 460 asset, err = os.Readlink(filepath.Join(s.grubDir(), "kernel.efi")) 461 c.Assert(err, IsNil) 462 c.Assert(asset, DeepEquals, filepath.Join("pc-kernel_2.snap", "kernel.efi")) 463 } 464 465 func (s *grubTestSuite) TestGrubExtractedRunKernelImageEnableTryKernel(c *C) { 466 s.makeFakeGrubEnv(c) 467 g := bootloader.NewGrub(s.rootdir, nil) 468 eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader) 469 c.Assert(ok, Equals, true) 470 471 kernel := s.makeKernelAssetSnap(c, "pc-kernel_1.snap") 472 473 // enable the Kernel we extracted 474 err := eg.EnableTryKernel(kernel) 475 c.Assert(err, IsNil) 476 477 // ensure that the symlink was put where we expect it 478 asset, err := os.Readlink(filepath.Join(s.grubDir(), "try-kernel.efi")) 479 c.Assert(err, IsNil) 480 481 c.Assert(asset, DeepEquals, filepath.Join("pc-kernel_1.snap", "kernel.efi")) 482 } 483 484 func (s *grubTestSuite) TestGrubExtractedRunKernelImageDisableTryKernel(c *C) { 485 if os.Geteuid() == 0 { 486 c.Skip("the test cannot be run by the root user") 487 } 488 489 s.makeFakeGrubEnv(c) 490 g := bootloader.NewGrub(s.rootdir, nil) 491 eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader) 492 c.Assert(ok, Equals, true) 493 494 // trying to disable when the try-kernel.efi symlink is missing does not 495 // raise any errors 496 err := eg.DisableTryKernel() 497 c.Assert(err, IsNil) 498 499 // make the symlink and check that the symlink is missing afterwards 500 s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_1.snap", "try-kernel.efi") 501 // make sure symlink is there 502 c.Assert(filepath.Join(s.grubDir(), "try-kernel.efi"), testutil.FilePresent) 503 504 err = eg.DisableTryKernel() 505 c.Assert(err, IsNil) 506 507 // ensure that the symlink is no longer there 508 c.Assert(filepath.Join(s.grubDir(), "try-kernel.efi"), testutil.FileAbsent) 509 c.Assert(filepath.Join(s.grubDir(), "pc-kernel_1.snap/kernel.efi"), testutil.FilePresent) 510 511 // try again but make sure that the directory cannot be written to 512 s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_1.snap", "try-kernel.efi") 513 err = os.Chmod(s.grubDir(), 000) 514 c.Assert(err, IsNil) 515 defer os.Chmod(s.grubDir(), 0755) 516 517 err = eg.DisableTryKernel() 518 c.Assert(err, ErrorMatches, "remove .*/grub/try-kernel.efi: permission denied") 519 } 520 521 func (s *grubTestSuite) TestKernelExtractionRunImageKernel(c *C) { 522 s.makeFakeGrubEnv(c) 523 524 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode}) 525 c.Assert(g, NotNil) 526 527 files := [][]string{ 528 {"kernel.efi", "I'm a kernel"}, 529 {"another-kernel-file", "another kernel file"}, 530 {"meta/kernel.yaml", "version: 4.2"}, 531 } 532 si := &snap.SideInfo{ 533 RealName: "ubuntu-kernel", 534 Revision: snap.R(42), 535 } 536 fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 537 snapf, err := snapfile.Open(fn) 538 c.Assert(err, IsNil) 539 540 info, err := snap.ReadInfoFromSnapFile(snapf, si) 541 c.Assert(err, IsNil) 542 543 err = g.ExtractKernelAssets(info, snapf) 544 c.Assert(err, IsNil) 545 546 // kernel is extracted 547 kernefi := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "kernel.efi") 548 c.Assert(kernefi, testutil.FilePresent) 549 // other file is not extracted 550 other := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "another-kernel-file") 551 c.Assert(other, testutil.FileAbsent) 552 553 // ensure that removal of assets also works 554 err = g.RemoveKernelAssets(info) 555 c.Assert(err, IsNil) 556 exists, _, err := osutil.DirExists(filepath.Dir(kernefi)) 557 c.Assert(err, IsNil) 558 c.Check(exists, Equals, false) 559 } 560 561 func (s *grubTestSuite) TestKernelExtractionRunImageKernelNoSlashBoot(c *C) { 562 // this is ubuntu-boot but during install we use the native EFI/ubuntu 563 // layout, same as Recovery, without the /boot mount 564 s.makeFakeGrubEFINativeEnv(c, nil) 565 566 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}) 567 c.Assert(g, NotNil) 568 569 files := [][]string{ 570 {"kernel.efi", "I'm a kernel"}, 571 {"another-kernel-file", "another kernel file"}, 572 {"meta/kernel.yaml", "version: 4.2"}, 573 } 574 si := &snap.SideInfo{ 575 RealName: "ubuntu-kernel", 576 Revision: snap.R(42), 577 } 578 fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files) 579 snapf, err := snapfile.Open(fn) 580 c.Assert(err, IsNil) 581 582 info, err := snap.ReadInfoFromSnapFile(snapf, si) 583 c.Assert(err, IsNil) 584 585 err = g.ExtractKernelAssets(info, snapf) 586 c.Assert(err, IsNil) 587 588 // kernel is extracted 589 kernefi := filepath.Join(s.rootdir, "EFI/ubuntu", "ubuntu-kernel_42.snap", "kernel.efi") 590 c.Assert(kernefi, testutil.FilePresent) 591 // other file is not extracted 592 other := filepath.Join(s.rootdir, "EFI/ubuntu", "ubuntu-kernel_42.snap", "another-kernel-file") 593 c.Assert(other, testutil.FileAbsent) 594 595 // enable the Kernel we extracted 596 eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader) 597 c.Assert(ok, Equals, true) 598 err = eg.EnableKernel(info) 599 c.Assert(err, IsNil) 600 601 // ensure that the symlink was put where we expect it 602 asset, err := os.Readlink(filepath.Join(s.rootdir, "EFI/ubuntu", "kernel.efi")) 603 c.Assert(err, IsNil) 604 605 c.Assert(asset, DeepEquals, filepath.Join("ubuntu-kernel_42.snap", "kernel.efi")) 606 607 // ensure that removal of assets also works 608 err = g.RemoveKernelAssets(info) 609 c.Assert(err, IsNil) 610 exists, _, err := osutil.DirExists(filepath.Dir(kernefi)) 611 c.Assert(err, IsNil) 612 c.Check(exists, Equals, false) 613 } 614 615 func (s *grubTestSuite) TestListManagedAssets(c *C) { 616 s.makeFakeGrubEFINativeEnv(c, []byte(`this is 617 some random boot config`)) 618 619 opts := &bootloader.Options{NoSlashBoot: true} 620 g := bootloader.NewGrub(s.rootdir, opts) 621 c.Assert(g, NotNil) 622 623 tg, ok := g.(bootloader.TrustedAssetsBootloader) 624 c.Assert(ok, Equals, true) 625 626 c.Check(tg.ManagedAssets(), DeepEquals, []string{ 627 "EFI/ubuntu/grub.cfg", 628 }) 629 630 opts = &bootloader.Options{Role: bootloader.RoleRecovery} 631 tg = bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader) 632 c.Check(tg.ManagedAssets(), DeepEquals, []string{ 633 "EFI/ubuntu/grub.cfg", 634 }) 635 636 // as it called for the root fs rather than a mount point of a partition 637 // with boot assets 638 tg = bootloader.NewGrub(s.rootdir, nil).(bootloader.TrustedAssetsBootloader) 639 c.Check(tg.ManagedAssets(), DeepEquals, []string{ 640 "boot/grub/grub.cfg", 641 }) 642 } 643 644 func (s *grubTestSuite) TestRecoveryUpdateBootConfigNoEdition(c *C) { 645 // native EFI/ubuntu setup 646 s.makeFakeGrubEFINativeEnv(c, []byte("recovery boot script")) 647 648 opts := &bootloader.Options{Role: bootloader.RoleRecovery} 649 g := bootloader.NewGrub(s.rootdir, opts) 650 c.Assert(g, NotNil) 651 652 restore := assets.MockInternal("grub-recovery.cfg", []byte(`# Snapd-Boot-Config-Edition: 5 653 this is mocked grub-recovery.conf 654 `)) 655 defer restore() 656 657 tg, ok := g.(bootloader.TrustedAssetsBootloader) 658 c.Assert(ok, Equals, true) 659 // install the recovery boot script 660 updated, err := tg.UpdateBootConfig() 661 c.Assert(err, IsNil) 662 c.Assert(updated, Equals, false) 663 c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, `recovery boot script`) 664 } 665 666 func (s *grubTestSuite) TestRecoveryUpdateBootConfigUpdates(c *C) { 667 // native EFI/ubuntu setup 668 s.makeFakeGrubEFINativeEnv(c, []byte(`# Snapd-Boot-Config-Edition: 1 669 recovery boot script`)) 670 671 opts := &bootloader.Options{Role: bootloader.RoleRecovery} 672 g := bootloader.NewGrub(s.rootdir, opts) 673 c.Assert(g, NotNil) 674 675 restore := assets.MockInternal("grub-recovery.cfg", []byte(`# Snapd-Boot-Config-Edition: 3 676 this is mocked grub-recovery.conf 677 `)) 678 defer restore() 679 restore = assets.MockInternal("grub.cfg", []byte(`# Snapd-Boot-Config-Edition: 4 680 this is mocked grub.conf 681 `)) 682 defer restore() 683 tg, ok := g.(bootloader.TrustedAssetsBootloader) 684 c.Assert(ok, Equals, true) 685 // install the recovery boot script 686 updated, err := tg.UpdateBootConfig() 687 c.Assert(err, IsNil) 688 c.Assert(updated, Equals, true) 689 // the recovery boot asset was picked 690 c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, `# Snapd-Boot-Config-Edition: 3 691 this is mocked grub-recovery.conf 692 `) 693 } 694 695 func (s *grubTestSuite) testBootUpdateBootConfigUpdates(c *C, oldConfig, newConfig string, update bool) { 696 // native EFI/ubuntu setup 697 s.makeFakeGrubEFINativeEnv(c, []byte(oldConfig)) 698 699 opts := &bootloader.Options{NoSlashBoot: true} 700 g := bootloader.NewGrub(s.rootdir, opts) 701 c.Assert(g, NotNil) 702 703 restore := assets.MockInternal("grub.cfg", []byte(newConfig)) 704 defer restore() 705 706 tg, ok := g.(bootloader.TrustedAssetsBootloader) 707 c.Assert(ok, Equals, true) 708 updated, err := tg.UpdateBootConfig() 709 c.Assert(err, IsNil) 710 c.Assert(updated, Equals, update) 711 if update { 712 c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, newConfig) 713 } else { 714 c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig) 715 } 716 } 717 718 func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigNoUpdateWhenNotManaged(c *C) { 719 oldConfig := `not managed` 720 newConfig := `# Snapd-Boot-Config-Edition: 3 721 this update is not applied 722 ` 723 // the current boot config is not managed, no update applied 724 const updateApplied = false 725 s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied) 726 } 727 728 func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigUpdates(c *C) { 729 oldConfig := `# Snapd-Boot-Config-Edition: 2 730 boot script 731 ` 732 // edition is higher, update is applied 733 newConfig := `# Snapd-Boot-Config-Edition: 3 734 this is updated grub.cfg 735 ` 736 const updateApplied = true 737 s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied) 738 } 739 740 func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigNoUpdate(c *C) { 741 oldConfig := `# Snapd-Boot-Config-Edition: 2 742 boot script 743 ` 744 // edition is lower, no update is applied 745 newConfig := `# Snapd-Boot-Config-Edition: 1 746 this is updated grub.cfg 747 ` 748 const updateApplied = false 749 s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied) 750 } 751 752 func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigSameEdition(c *C) { 753 oldConfig := `# Snapd-Boot-Config-Edition: 1 754 boot script 755 ` 756 // edition is equal, no update is applied 757 newConfig := `# Snapd-Boot-Config-Edition: 1 758 this is updated grub.cfg 759 ` 760 const updateApplied = false 761 s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied) 762 } 763 764 func (s *grubTestSuite) TestBootUpdateBootConfigTrivialErr(c *C) { 765 if os.Geteuid() == 0 { 766 c.Skip("the test cannot be run by the root user") 767 } 768 769 oldConfig := `# Snapd-Boot-Config-Edition: 2 770 boot script 771 ` 772 // edition is higher, update is applied 773 newConfig := `# Snapd-Boot-Config-Edition: 3 774 this is updated grub.cfg 775 ` 776 // native EFI/ubuntu setup 777 s.makeFakeGrubEFINativeEnv(c, []byte(oldConfig)) 778 restore := assets.MockInternal("grub.cfg", []byte(newConfig)) 779 defer restore() 780 781 opts := &bootloader.Options{NoSlashBoot: true} 782 g := bootloader.NewGrub(s.rootdir, opts) 783 c.Assert(g, NotNil) 784 tg, ok := g.(bootloader.TrustedAssetsBootloader) 785 c.Assert(ok, Equals, true) 786 787 err := os.Chmod(s.grubEFINativeDir(), 0000) 788 c.Assert(err, IsNil) 789 defer os.Chmod(s.grubEFINativeDir(), 0755) 790 791 updated, err := tg.UpdateBootConfig() 792 c.Assert(err, ErrorMatches, "cannot load existing config asset: .*/EFI/ubuntu/grub.cfg: permission denied") 793 c.Assert(updated, Equals, false) 794 err = os.Chmod(s.grubEFINativeDir(), 0555) 795 c.Assert(err, IsNil) 796 797 c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig) 798 799 // writing out new config fails 800 err = os.Chmod(s.grubEFINativeDir(), 0111) 801 c.Assert(err, IsNil) 802 updated, err = tg.UpdateBootConfig() 803 c.Assert(err, ErrorMatches, `open .*/EFI/ubuntu/grub.cfg\..+: permission denied`) 804 c.Assert(updated, Equals, false) 805 c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig) 806 } 807 808 func (s *grubTestSuite) TestStaticCmdlineForGrubAsset(c *C) { 809 restore := assets.MockSnippetsForEdition("grub-asset:static-cmdline", []assets.ForEditions{ 810 {FirstEdition: 2, Snippet: []byte(`static cmdline "with spaces"`)}, 811 }) 812 defer restore() 813 cmdline := bootloader.StaticCommandLineForGrubAssetEdition("grub-asset", 1) 814 c.Check(cmdline, Equals, ``) 815 cmdline = bootloader.StaticCommandLineForGrubAssetEdition("grub-asset", 2) 816 c.Check(cmdline, Equals, `static cmdline "with spaces"`) 817 cmdline = bootloader.StaticCommandLineForGrubAssetEdition("grub-asset", 4) 818 c.Check(cmdline, Equals, `static cmdline "with spaces"`) 819 } 820 821 func (s *grubTestSuite) TestCommandLineNotManaged(c *C) { 822 grubCfg := "boot script\n" 823 824 // native EFI/ubuntu setup 825 s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg)) 826 827 restore := assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{ 828 {FirstEdition: 1, Snippet: []byte(`static=1`)}, 829 {FirstEdition: 2, Snippet: []byte(`static=2`)}, 830 }) 831 defer restore() 832 restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{ 833 {FirstEdition: 1, Snippet: []byte(`static=1 recovery`)}, 834 {FirstEdition: 2, Snippet: []byte(`static=2 recovery`)}, 835 }) 836 defer restore() 837 838 opts := &bootloader.Options{NoSlashBoot: true} 839 mg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader) 840 841 args, err := mg.CommandLine(bootloader.CommandLineComponents{ 842 ModeArg: "snapd_recovery_mode=run", 843 ExtraArgs: "extra", 844 }) 845 c.Assert(err, IsNil) 846 c.Check(args, Equals, "snapd_recovery_mode=run static=1 extra") 847 848 optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery} 849 mgr := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader) 850 851 args, err = mgr.CommandLine(bootloader.CommandLineComponents{ 852 ModeArg: "snapd_recovery_mode=recover", 853 SystemArg: "snapd_recovery_system=1234", 854 ExtraArgs: "extra", 855 }) 856 c.Assert(err, IsNil) 857 c.Check(args, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 static=1 recovery extra") 858 } 859 860 func (s *grubTestSuite) TestCommandLineMocked(c *C) { 861 grubCfg := `# Snapd-Boot-Config-Edition: 2 862 boot script 863 ` 864 staticCmdline := `arg1 foo=123 panic=-1 arg2="with spaces "` 865 staticCmdlineEdition3 := `edition=3 static args` 866 restore := assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{ 867 {FirstEdition: 1, Snippet: []byte(staticCmdline)}, 868 {FirstEdition: 3, Snippet: []byte(staticCmdlineEdition3)}, 869 }) 870 defer restore() 871 staticCmdlineRecovery := `recovery config panic=-1` 872 restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{ 873 {FirstEdition: 1, Snippet: []byte(staticCmdlineRecovery)}, 874 }) 875 defer restore() 876 877 // native EFI/ubuntu setup 878 s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg)) 879 880 optsNoSlashBoot := &bootloader.Options{NoSlashBoot: true} 881 g := bootloader.NewGrub(s.rootdir, optsNoSlashBoot) 882 c.Assert(g, NotNil) 883 tg, ok := g.(bootloader.TrustedAssetsBootloader) 884 c.Assert(ok, Equals, true) 885 886 extraArgs := `extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"` 887 args, err := tg.CommandLine(bootloader.CommandLineComponents{ 888 ModeArg: "snapd_recovery_mode=run", 889 ExtraArgs: extraArgs, 890 }) 891 c.Assert(err, IsNil) 892 c.Check(args, Equals, `snapd_recovery_mode=run arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`) 893 894 // empty mode/system do not produce confusing results 895 args, err = tg.CommandLine(bootloader.CommandLineComponents{ 896 ExtraArgs: extraArgs, 897 }) 898 c.Assert(err, IsNil) 899 c.Check(args, Equals, `arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`) 900 901 // now check the recovery bootloader 902 optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery} 903 mrg := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader) 904 args, err = mrg.CommandLine(bootloader.CommandLineComponents{ 905 ModeArg: "snapd_recovery_mode=recover", 906 SystemArg: "snapd_recovery_system=20200202", 907 ExtraArgs: extraArgs, 908 }) 909 c.Assert(err, IsNil) 910 // static command line from recovery asset 911 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery config panic=-1 extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`) 912 913 // try with a different edition 914 grubCfg3 := `# Snapd-Boot-Config-Edition: 3 915 boot script 916 ` 917 s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg3)) 918 tg = bootloader.NewGrub(s.rootdir, optsNoSlashBoot).(bootloader.TrustedAssetsBootloader) 919 c.Assert(g, NotNil) 920 extraArgs = `extra_arg=1` 921 args, err = tg.CommandLine(bootloader.CommandLineComponents{ 922 ModeArg: "snapd_recovery_mode=run", 923 ExtraArgs: extraArgs, 924 }) 925 c.Assert(err, IsNil) 926 c.Check(args, Equals, `snapd_recovery_mode=run edition=3 static args extra_arg=1`) 927 928 // full args set overrides static arguments 929 args, err = tg.CommandLine(bootloader.CommandLineComponents{ 930 ModeArg: "snapd_recovery_mode=run", 931 FullArgs: "full for run mode", 932 }) 933 c.Assert(err, IsNil) 934 c.Check(args, Equals, `snapd_recovery_mode=run full for run mode`) 935 args, err = mrg.CommandLine(bootloader.CommandLineComponents{ 936 ModeArg: "snapd_recovery_mode=recover", 937 SystemArg: "snapd_recovery_system=20200202", 938 FullArgs: "full for recover mode", 939 }) 940 c.Assert(err, IsNil) 941 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 full for recover mode`) 942 943 } 944 945 func (s *grubTestSuite) TestCandidateCommandLineMocked(c *C) { 946 grubCfg := `# Snapd-Boot-Config-Edition: 1 947 boot script 948 ` 949 // edition on disk 950 s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg)) 951 952 edition2 := []byte(`# Snapd-Boot-Config-Edition: 2`) 953 edition3 := []byte(`# Snapd-Boot-Config-Edition: 3`) 954 edition4 := []byte(`# Snapd-Boot-Config-Edition: 4`) 955 956 restore := assets.MockInternal("grub.cfg", edition2) 957 defer restore() 958 restore = assets.MockInternal("grub-recovery.cfg", edition2) 959 defer restore() 960 961 restore = assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{ 962 {FirstEdition: 1, Snippet: []byte(`edition=1`)}, 963 {FirstEdition: 3, Snippet: []byte(`edition=3`)}, 964 }) 965 defer restore() 966 restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{ 967 {FirstEdition: 1, Snippet: []byte(`recovery edition=1`)}, 968 {FirstEdition: 3, Snippet: []byte(`recovery edition=3`)}, 969 {FirstEdition: 4, Snippet: []byte(`recovery edition=4up`)}, 970 }) 971 defer restore() 972 973 optsNoSlashBoot := &bootloader.Options{NoSlashBoot: true} 974 mg := bootloader.NewGrub(s.rootdir, optsNoSlashBoot).(bootloader.TrustedAssetsBootloader) 975 optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery} 976 recoverymg := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader) 977 978 args, err := mg.CandidateCommandLine(bootloader.CommandLineComponents{ 979 ModeArg: "snapd_recovery_mode=run", 980 ExtraArgs: "extra=1", 981 }) 982 c.Assert(err, IsNil) 983 c.Check(args, Equals, `snapd_recovery_mode=run edition=1 extra=1`) 984 args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{ 985 ModeArg: "snapd_recovery_mode=recover", 986 SystemArg: "snapd_recovery_system=20200202", 987 ExtraArgs: "extra=1", 988 }) 989 c.Assert(err, IsNil) 990 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=1 extra=1`) 991 992 restore = assets.MockInternal("grub.cfg", edition3) 993 defer restore() 994 restore = assets.MockInternal("grub-recovery.cfg", edition3) 995 defer restore() 996 997 args, err = mg.CandidateCommandLine(bootloader.CommandLineComponents{ 998 ModeArg: "snapd_recovery_mode=run", 999 ExtraArgs: "extra=1", 1000 }) 1001 c.Assert(err, IsNil) 1002 c.Check(args, Equals, `snapd_recovery_mode=run edition=3 extra=1`) 1003 args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{ 1004 ModeArg: "snapd_recovery_mode=recover", 1005 SystemArg: "snapd_recovery_system=20200202", 1006 ExtraArgs: "extra=1", 1007 }) 1008 c.Assert(err, IsNil) 1009 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=3 extra=1`) 1010 1011 // bump edition only for recovery 1012 restore = assets.MockInternal("grub-recovery.cfg", edition4) 1013 defer restore() 1014 // boot bootloader unchanged 1015 args, err = mg.CandidateCommandLine(bootloader.CommandLineComponents{ 1016 ModeArg: "snapd_recovery_mode=run", 1017 ExtraArgs: "extra=1", 1018 }) 1019 c.Assert(err, IsNil) 1020 c.Check(args, Equals, `snapd_recovery_mode=run edition=3 extra=1`) 1021 // recovery uses a new edition 1022 args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{ 1023 ModeArg: "snapd_recovery_mode=recover", 1024 SystemArg: "snapd_recovery_system=20200202", 1025 ExtraArgs: "extra=1", 1026 }) 1027 c.Assert(err, IsNil) 1028 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=4up extra=1`) 1029 1030 // the static snippet is ignored when using full arg set 1031 args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{ 1032 ModeArg: "snapd_recovery_mode=recover", 1033 SystemArg: "snapd_recovery_system=20200202", 1034 FullArgs: "full args set", 1035 }) 1036 c.Assert(err, IsNil) 1037 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 full args set`) 1038 } 1039 1040 func (s *grubTestSuite) TestCommandLineReal(c *C) { 1041 grubCfg := `# Snapd-Boot-Config-Edition: 1 1042 boot script 1043 ` 1044 // native EFI/ubuntu setup 1045 s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg)) 1046 1047 opts := &bootloader.Options{NoSlashBoot: true} 1048 g := bootloader.NewGrub(s.rootdir, opts) 1049 c.Assert(g, NotNil) 1050 tg, ok := g.(bootloader.TrustedAssetsBootloader) 1051 c.Assert(ok, Equals, true) 1052 1053 extraArgs := "foo bar baz=1" 1054 args, err := tg.CommandLine(bootloader.CommandLineComponents{ 1055 ModeArg: "snapd_recovery_mode=run", 1056 ExtraArgs: extraArgs, 1057 }) 1058 c.Assert(err, IsNil) 1059 c.Check(args, Equals, `snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz=1`) 1060 // with full args the static part is not used 1061 args, err = tg.CommandLine(bootloader.CommandLineComponents{ 1062 ModeArg: "snapd_recovery_mode=run", 1063 FullArgs: "full for run mode", 1064 }) 1065 c.Assert(err, IsNil) 1066 c.Check(args, Equals, `snapd_recovery_mode=run full for run mode`) 1067 1068 // now check the recovery bootloader 1069 opts = &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery} 1070 mrg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader) 1071 args, err = mrg.CommandLine(bootloader.CommandLineComponents{ 1072 ModeArg: "snapd_recovery_mode=recover", 1073 SystemArg: "snapd_recovery_system=20200202", 1074 ExtraArgs: extraArgs, 1075 }) 1076 c.Assert(err, IsNil) 1077 // static command line from recovery asset 1078 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 console=ttyS0 console=tty1 panic=-1 foo bar baz=1`) 1079 // similarly, when passed full args, the static part is not used 1080 args, err = mrg.CommandLine(bootloader.CommandLineComponents{ 1081 ModeArg: "snapd_recovery_mode=recover", 1082 SystemArg: "snapd_recovery_system=20200202", 1083 FullArgs: "full for recover mode", 1084 }) 1085 c.Assert(err, IsNil) 1086 c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 full for recover mode`) 1087 } 1088 1089 func (s *grubTestSuite) TestCommandLineComponentsValidate(c *C) { 1090 grubCfg := `# Snapd-Boot-Config-Edition: 1 1091 boot script 1092 ` 1093 // native EFI/ubuntu setup 1094 s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg)) 1095 1096 opts := &bootloader.Options{NoSlashBoot: true} 1097 g := bootloader.NewGrub(s.rootdir, opts) 1098 c.Assert(g, NotNil) 1099 tg, ok := g.(bootloader.TrustedAssetsBootloader) 1100 c.Assert(ok, Equals, true) 1101 1102 args, err := tg.CommandLine(bootloader.CommandLineComponents{ 1103 ModeArg: "snapd_recovery_mode=run", 1104 ExtraArgs: "extra is set", 1105 FullArgs: "full is set", 1106 }) 1107 c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line") 1108 c.Check(args, Equals, "") 1109 // invalid for the candidate command line too 1110 args, err = tg.CandidateCommandLine(bootloader.CommandLineComponents{ 1111 ModeArg: "snapd_recovery_mode=run", 1112 ExtraArgs: "extra is set", 1113 FullArgs: "full is set", 1114 }) 1115 c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line") 1116 c.Check(args, Equals, "") 1117 1118 // now check the recovery bootloader 1119 opts = &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery} 1120 mrg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader) 1121 args, err = mrg.CommandLine(bootloader.CommandLineComponents{ 1122 ModeArg: "snapd_recovery_mode=recover", 1123 SystemArg: "snapd_recovery_system=20200202", 1124 ExtraArgs: "extra is set", 1125 FullArgs: "full is set", 1126 }) 1127 c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line") 1128 c.Check(args, Equals, "") 1129 // candidate recovery command line is checks validity of the components too 1130 args, err = mrg.CandidateCommandLine(bootloader.CommandLineComponents{ 1131 ModeArg: "snapd_recovery_mode=recover", 1132 SystemArg: "snapd_recovery_system=20200202", 1133 ExtraArgs: "extra is set", 1134 FullArgs: "full is set", 1135 }) 1136 c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line") 1137 c.Check(args, Equals, "") 1138 } 1139 1140 func (s *grubTestSuite) TestTrustedAssetsNativePartitionLayout(c *C) { 1141 // native EFI/ubuntu setup 1142 s.makeFakeGrubEFINativeEnv(c, []byte("grub.cfg")) 1143 opts := &bootloader.Options{NoSlashBoot: true} 1144 g := bootloader.NewGrub(s.rootdir, opts) 1145 c.Assert(g, NotNil) 1146 1147 tab, ok := g.(bootloader.TrustedAssetsBootloader) 1148 c.Assert(ok, Equals, true) 1149 1150 ta, err := tab.TrustedAssets() 1151 c.Assert(err, IsNil) 1152 c.Check(ta, DeepEquals, []string{ 1153 "EFI/boot/grubx64.efi", 1154 }) 1155 1156 // recovery bootloader 1157 recoveryOpts := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery} 1158 tarb := bootloader.NewGrub(s.rootdir, recoveryOpts).(bootloader.TrustedAssetsBootloader) 1159 c.Assert(tarb, NotNil) 1160 1161 ta, err = tarb.TrustedAssets() 1162 c.Assert(err, IsNil) 1163 c.Check(ta, DeepEquals, []string{ 1164 "EFI/boot/bootx64.efi", 1165 "EFI/boot/grubx64.efi", 1166 }) 1167 1168 } 1169 1170 func (s *grubTestSuite) TestTrustedAssetsRoot(c *C) { 1171 s.makeFakeGrubEnv(c) 1172 g := bootloader.NewGrub(s.rootdir, nil) 1173 tab, ok := g.(bootloader.TrustedAssetsBootloader) 1174 c.Assert(ok, Equals, true) 1175 1176 ta, err := tab.TrustedAssets() 1177 c.Assert(err, ErrorMatches, "internal error: trusted assets called without native host-partition layout") 1178 c.Check(ta, IsNil) 1179 } 1180 1181 func (s *grubTestSuite) TestRecoveryBootChains(c *C) { 1182 s.makeFakeGrubEFINativeEnv(c, nil) 1183 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 1184 tab, ok := g.(bootloader.TrustedAssetsBootloader) 1185 c.Assert(ok, Equals, true) 1186 1187 chain, err := tab.RecoveryBootChain("kernel.snap") 1188 c.Assert(err, IsNil) 1189 c.Assert(chain, DeepEquals, []bootloader.BootFile{ 1190 {Path: "EFI/boot/bootx64.efi", Role: bootloader.RoleRecovery}, 1191 {Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRecovery}, 1192 {Snap: "kernel.snap", Path: "kernel.efi", Role: bootloader.RoleRecovery}, 1193 }) 1194 } 1195 1196 func (s *grubTestSuite) TestRecoveryBootChainsNotRecoveryBootloader(c *C) { 1197 s.makeFakeGrubEnv(c) 1198 g := bootloader.NewGrub(s.rootdir, nil) 1199 tab, ok := g.(bootloader.TrustedAssetsBootloader) 1200 c.Assert(ok, Equals, true) 1201 1202 _, err := tab.RecoveryBootChain("kernel.snap") 1203 c.Assert(err, ErrorMatches, "not a recovery bootloader") 1204 } 1205 1206 func (s *grubTestSuite) TestBootChains(c *C) { 1207 s.makeFakeGrubEFINativeEnv(c, nil) 1208 g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery}) 1209 tab, ok := g.(bootloader.TrustedAssetsBootloader) 1210 c.Assert(ok, Equals, true) 1211 1212 g2 := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode}) 1213 1214 chain, err := tab.BootChain(g2, "kernel.snap") 1215 c.Assert(err, IsNil) 1216 c.Assert(chain, DeepEquals, []bootloader.BootFile{ 1217 {Path: "EFI/boot/bootx64.efi", Role: bootloader.RoleRecovery}, 1218 {Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRecovery}, 1219 {Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRunMode}, 1220 {Snap: "kernel.snap", Path: "kernel.efi", Role: bootloader.RoleRunMode}, 1221 }) 1222 } 1223 1224 func (s *grubTestSuite) TestBootChainsNotRecoveryBootloader(c *C) { 1225 s.makeFakeGrubEnv(c) 1226 g := bootloader.NewGrub(s.rootdir, nil) 1227 tab, ok := g.(bootloader.TrustedAssetsBootloader) 1228 c.Assert(ok, Equals, true) 1229 1230 g2 := bootloader.NewGrub(s.rootdir, &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRunMode}) 1231 1232 _, err := tab.BootChain(g2, "kernel.snap") 1233 c.Assert(err, ErrorMatches, "not a recovery bootloader") 1234 }