gitee.com/mysnapcore/mysnapd@v0.1.0/boot/makebootable.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2022 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 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 27 "gitee.com/mysnapcore/mysnapd/asserts" 28 "gitee.com/mysnapcore/mysnapd/bootloader" 29 "gitee.com/mysnapcore/mysnapd/dirs" 30 "gitee.com/mysnapcore/mysnapd/osutil" 31 "gitee.com/mysnapcore/mysnapd/snap" 32 "gitee.com/mysnapcore/mysnapd/snap/snapfile" 33 ) 34 35 // BootableSet represents the boot snaps of a system to be made bootable. 36 type BootableSet struct { 37 Base *snap.Info 38 BasePath string 39 Kernel *snap.Info 40 KernelPath string 41 Gadget *snap.Info 42 GadgetPath string 43 44 RecoverySystemLabel string 45 // RecoverySystemDir is a path to a directory with recovery system 46 // assets. The path is relative to the recovery bootloader root 47 // directory. 48 RecoverySystemDir string 49 50 UnpackedGadgetDir string 51 52 // Recovery is set when making the recovery partition bootable. 53 Recovery bool 54 } 55 56 // MakeBootableImage sets up the given bootable set and target filesystem 57 // such that the image can be booted. 58 // 59 // rootdir points to an image filesystem (UC 16/18) or an image recovery 60 // filesystem (UC20 at prepare-image time). 61 // On UC20, bootWith.Recovery must be true, as this function makes the recovery 62 // system bootable. It does not make a run system bootable, for that 63 // functionality see MakeRunnableSystem, which is meant to be used at runtime 64 // from UC20 install mode. 65 // For a UC20 image a set of boot flags that will be set in the recovery 66 // boot environment can be specified. 67 func MakeBootableImage(model *asserts.Model, rootdir string, bootWith *BootableSet, bootFlags []string) error { 68 if model.Grade() == asserts.ModelGradeUnset { 69 if len(bootFlags) != 0 { 70 return fmt.Errorf("no boot flags support for UC16/18") 71 } 72 return makeBootable16(model, rootdir, bootWith) 73 } 74 75 if !bootWith.Recovery { 76 return fmt.Errorf("internal error: MakeBootableImage called at runtime, use MakeRunnableSystem instead") 77 } 78 return makeBootable20(rootdir, bootWith, bootFlags) 79 } 80 81 // MakeBootablePartition configures a partition mounted on rootdir 82 // using information from bootWith and bootFlags. Contrarily to 83 // MakeBootableImage this happens in a live system. 84 func MakeBootablePartition(partDir string, opts *bootloader.Options, bootWith *BootableSet, bootMode string, bootFlags []string) error { 85 if bootWith.RecoverySystemDir != "" { 86 return fmt.Errorf("internal error: RecoverySystemDir unexpectedly set for MakeBootablePartition") 87 } 88 return configureBootloader(partDir, opts, bootWith, bootMode, bootFlags) 89 } 90 91 // makeBootable16 setups the image filesystem for boot with UC16 92 // and UC18 models. This entails: 93 // - installing the bootloader configuration from the gadget 94 // - creating symlinks for boot snaps from seed to the runtime blob dir 95 // - setting boot env vars pointing to the revisions of the boot snaps to use 96 // - extracting kernel assets as needed by the bootloader 97 func makeBootable16(model *asserts.Model, rootdir string, bootWith *BootableSet) error { 98 opts := &bootloader.Options{ 99 PrepareImageTime: true, 100 } 101 102 // install the bootloader configuration from the gadget 103 if err := bootloader.InstallBootConfig(bootWith.UnpackedGadgetDir, rootdir, opts); err != nil { 104 return err 105 } 106 107 // setup symlinks for kernel and boot base from the blob directory 108 // to the seed snaps 109 110 snapBlobDir := dirs.SnapBlobDirUnder(rootdir) 111 if err := os.MkdirAll(snapBlobDir, 0755); err != nil { 112 return err 113 } 114 115 for _, fn := range []string{bootWith.BasePath, bootWith.KernelPath} { 116 dst := filepath.Join(snapBlobDir, filepath.Base(fn)) 117 // construct a relative symlink from the blob dir 118 // to the seed snap file 119 relSymlink, err := filepath.Rel(snapBlobDir, fn) 120 if err != nil { 121 return fmt.Errorf("cannot build symlink for boot snap: %v", err) 122 } 123 if err := os.Symlink(relSymlink, dst); err != nil { 124 return err 125 } 126 } 127 128 // Set bootvars for kernel/core snaps so the system boots and 129 // does the first-time initialization. There is also no 130 // mounted kernel/core/base snap, but just the blobs. 131 bl, err := bootloader.Find(rootdir, opts) 132 if err != nil { 133 return fmt.Errorf("cannot set kernel/core boot variables: %s", err) 134 } 135 136 m := map[string]string{ 137 "snap_mode": "", 138 "snap_try_core": "", 139 "snap_try_kernel": "", 140 } 141 if model.DisplayName() != "" { 142 m["snap_menuentry"] = model.DisplayName() 143 } 144 145 setBoot := func(name, fn string) { 146 m[name] = filepath.Base(fn) 147 } 148 // base 149 setBoot("snap_core", bootWith.BasePath) 150 151 // kernel 152 kernelf, err := snapfile.Open(bootWith.KernelPath) 153 if err != nil { 154 return err 155 } 156 if err := bl.ExtractKernelAssets(bootWith.Kernel, kernelf); err != nil { 157 return err 158 } 159 setBoot("snap_kernel", bootWith.KernelPath) 160 161 if err := bl.SetBootVars(m); err != nil { 162 return err 163 } 164 165 return nil 166 } 167 168 func configureBootloader(rootdir string, opts *bootloader.Options, bootWith *BootableSet, bootMode string, bootFlags []string) error { 169 blVars := make(map[string]string, 3) 170 if len(bootFlags) != 0 { 171 if err := setImageBootFlags(bootFlags, blVars); err != nil { 172 return err 173 } 174 } 175 176 // install the bootloader configuration from the gadget 177 if err := bootloader.InstallBootConfig(bootWith.UnpackedGadgetDir, rootdir, opts); err != nil { 178 return err 179 } 180 181 // now install the recovery system specific boot config 182 bl, err := bootloader.Find(rootdir, opts) 183 if err != nil { 184 return fmt.Errorf("internal error: cannot find bootloader: %v", err) 185 } 186 187 blVars["snapd_recovery_mode"] = bootMode 188 if bootWith.RecoverySystemLabel != "" { 189 // record which recovery system is to be used on the bootloader, note 190 // that this goes on the main bootloader environment, and not on the 191 // recovery system bootloader environment, for example for grub 192 // bootloader, this env var is set on the ubuntu-seed root grubenv, and 193 // not on the recovery system grubenv in the systems/20200314/ subdir on 194 // ubuntu-seed 195 blVars["snapd_recovery_system"] = bootWith.RecoverySystemLabel 196 } 197 198 if err := bl.SetBootVars(blVars); err != nil { 199 return fmt.Errorf("cannot set recovery environment: %v", err) 200 } 201 202 return nil 203 } 204 205 func makeBootable20(rootdir string, bootWith *BootableSet, bootFlags []string) error { 206 // we can only make a single recovery system bootable right now 207 recoverySystems, err := filepath.Glob(filepath.Join(rootdir, "systems/*")) 208 if err != nil { 209 return fmt.Errorf("cannot validate recovery systems: %v", err) 210 } 211 if len(recoverySystems) > 1 { 212 return fmt.Errorf("cannot make multiple recovery systems bootable yet") 213 } 214 215 if bootWith.RecoverySystemLabel == "" { 216 return fmt.Errorf("internal error: recovery system label unset") 217 } 218 219 opts := &bootloader.Options{ 220 PrepareImageTime: true, 221 // setup the recovery bootloader 222 Role: bootloader.RoleRecovery, 223 } 224 if err := configureBootloader(rootdir, opts, bootWith, ModeInstall, bootFlags); err != nil { 225 return fmt.Errorf("cannot install bootloader: %v", err) 226 } 227 228 return MakeRecoverySystemBootable(rootdir, bootWith.RecoverySystemDir, &RecoverySystemBootableSet{ 229 Kernel: bootWith.Kernel, 230 KernelPath: bootWith.KernelPath, 231 GadgetSnapOrDir: bootWith.UnpackedGadgetDir, 232 PrepareImageTime: true, 233 }) 234 } 235 236 // RecoverySystemBootableSet is a set of snaps relevant to booting a recovery 237 // system. 238 type RecoverySystemBootableSet struct { 239 Kernel *snap.Info 240 KernelPath string 241 GadgetSnapOrDir string 242 // PrepareImageTime is true when the structure is being used when 243 // preparing a bootable system image. 244 PrepareImageTime bool 245 } 246 247 // MakeRecoverySystemBootable prepares a recovery system under a path relative 248 // to recovery bootloader's rootdir for booting. 249 func MakeRecoverySystemBootable(rootdir string, relativeRecoverySystemDir string, bootWith *RecoverySystemBootableSet) error { 250 opts := &bootloader.Options{ 251 // XXX: this is only needed by LK, it is unclear whether LK does 252 // too much when extracting recovery kernel assets, in the end 253 // it is currently not possible to create a recovery system at 254 // runtime when using LK. 255 PrepareImageTime: bootWith.PrepareImageTime, 256 // setup the recovery bootloader 257 Role: bootloader.RoleRecovery, 258 } 259 260 bl, err := bootloader.Find(rootdir, opts) 261 if err != nil { 262 return fmt.Errorf("internal error: cannot find bootloader: %v", err) 263 } 264 265 // on e.g. ARM we need to extract the kernel assets on the recovery 266 // system as well, but the bootloader does not load any environment from 267 // the recovery system 268 erkbl, ok := bl.(bootloader.ExtractedRecoveryKernelImageBootloader) 269 if ok { 270 kernelf, err := snapfile.Open(bootWith.KernelPath) 271 if err != nil { 272 return err 273 } 274 275 err = erkbl.ExtractRecoveryKernelAssets( 276 relativeRecoverySystemDir, 277 bootWith.Kernel, 278 kernelf, 279 ) 280 if err != nil { 281 return fmt.Errorf("cannot extract recovery system kernel assets: %v", err) 282 } 283 284 return nil 285 } 286 287 rbl, ok := bl.(bootloader.RecoveryAwareBootloader) 288 if !ok { 289 return fmt.Errorf("cannot use %s bootloader: does not support recovery systems", bl.Name()) 290 } 291 kernelPath, err := filepath.Rel(rootdir, bootWith.KernelPath) 292 if err != nil { 293 return fmt.Errorf("cannot construct kernel boot path: %v", err) 294 } 295 recoveryBlVars := map[string]string{ 296 "snapd_recovery_kernel": filepath.Join("/", kernelPath), 297 } 298 if _, ok := bl.(bootloader.TrustedAssetsBootloader); ok { 299 recoveryCmdlineArgs, err := bootVarsForTrustedCommandLineFromGadget(bootWith.GadgetSnapOrDir) 300 if err != nil { 301 return fmt.Errorf("cannot obtain recovery system command line: %v", err) 302 } 303 for k, v := range recoveryCmdlineArgs { 304 recoveryBlVars[k] = v 305 } 306 } 307 308 if err := rbl.SetRecoverySystemEnv(relativeRecoverySystemDir, recoveryBlVars); err != nil { 309 return fmt.Errorf("cannot set recovery system environment: %v", err) 310 } 311 return nil 312 } 313 314 type makeRunnableOptions struct { 315 Standalone bool 316 AfterDataReset bool 317 } 318 319 func copyBootSnap(orig string, dstInfo *snap.Info, dstSnapBlobDir string) error { 320 // if the source path is a symlink, don't copy the symlink, copy the 321 // target file instead of copying the symlink, as the initramfs won't 322 // follow the symlink when it goes to mount the base and kernel snaps by 323 // design as the initramfs should only be using trusted things from 324 // ubuntu-data to boot in run mode 325 if osutil.IsSymlink(orig) { 326 link, err := os.Readlink(orig) 327 if err != nil { 328 return err 329 } 330 orig = link 331 } 332 // note that we need to use the "Filename()" here because unasserted 333 // snaps will have names like pc-kernel_5.19.4.snap but snapd expects 334 // "pc-kernel_x1.snap" 335 dst := filepath.Join(dstSnapBlobDir, dstInfo.Filename()) 336 if err := osutil.CopyFile(orig, dst, osutil.CopyFlagPreserveAll|osutil.CopyFlagSync); err != nil { 337 return err 338 } 339 return nil 340 } 341 342 func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver, makeOpts makeRunnableOptions) error { 343 if model.Grade() == asserts.ModelGradeUnset { 344 return fmt.Errorf("internal error: cannot make pre-UC20 system runnable") 345 } 346 if bootWith.RecoverySystemDir != "" { 347 return fmt.Errorf("internal error: RecoverySystemDir unexpectedly set for MakeRunnableSystem") 348 } 349 350 // TODO:UC20: 351 // - figure out what to do for uboot gadgets, currently we require them to 352 // install the boot.sel onto ubuntu-boot directly, but the file should be 353 // managed by snapd instead 354 355 // copy kernel/base/gadget into the ubuntu-data partition 356 snapBlobDir := dirs.SnapBlobDirUnder(InstallHostWritableDir(model)) 357 if err := os.MkdirAll(snapBlobDir, 0755); err != nil { 358 return err 359 } 360 for _, origDest := range []struct { 361 orig string 362 destInfo *snap.Info 363 }{ 364 {orig: bootWith.BasePath, destInfo: bootWith.Base}, 365 {orig: bootWith.KernelPath, destInfo: bootWith.Kernel}, 366 {orig: bootWith.GadgetPath, destInfo: bootWith.Gadget}} { 367 if err := copyBootSnap(origDest.orig, origDest.destInfo, snapBlobDir); err != nil { 368 return err 369 } 370 } 371 372 // replicate the boot assets cache in host's writable 373 if err := CopyBootAssetsCacheToRoot(InstallHostWritableDir(model)); err != nil { 374 return fmt.Errorf("cannot replicate boot assets cache: %v", err) 375 } 376 377 var currentTrustedBootAssets bootAssetsMap 378 var currentTrustedRecoveryBootAssets bootAssetsMap 379 if sealer != nil { 380 currentTrustedBootAssets = sealer.currentTrustedBootAssetsMap() 381 currentTrustedRecoveryBootAssets = sealer.currentTrustedRecoveryBootAssetsMap() 382 } 383 recoverySystemLabel := bootWith.RecoverySystemLabel 384 // write modeenv on the ubuntu-data partition 385 modeenv := &Modeenv{ 386 Mode: "run", 387 RecoverySystem: recoverySystemLabel, 388 // default to the system we were installed from 389 CurrentRecoverySystems: []string{recoverySystemLabel}, 390 // which is also considered to be good 391 GoodRecoverySystems: []string{recoverySystemLabel}, 392 CurrentTrustedBootAssets: currentTrustedBootAssets, 393 CurrentTrustedRecoveryBootAssets: currentTrustedRecoveryBootAssets, 394 // kernel command lines are set later once a boot config is 395 // installed 396 CurrentKernelCommandLines: nil, 397 // keep this comment to make gofmt 1.9 happy 398 Base: bootWith.Base.Filename(), 399 Gadget: bootWith.Gadget.Filename(), 400 CurrentKernels: []string{bootWith.Kernel.Filename()}, 401 BrandID: model.BrandID(), 402 Model: model.Model(), 403 // TODO: test this 404 Classic: model.Classic(), 405 Grade: string(model.Grade()), 406 ModelSignKeyID: model.SignKeyID(), 407 } 408 409 // get the ubuntu-boot bootloader and extract the kernel there 410 opts := &bootloader.Options{ 411 // Bootloader for run mode 412 Role: bootloader.RoleRunMode, 413 // At this point the run mode bootloader is under the native 414 // run partition layout, no /boot mount. 415 NoSlashBoot: true, 416 } 417 // the bootloader config may have been installed when the ubuntu-boot 418 // partition was created, but for a trusted assets the bootloader config 419 // will be installed further down; for now identify the run mode 420 // bootloader by looking at the gadget 421 bl, err := bootloader.ForGadget(bootWith.UnpackedGadgetDir, InitramfsUbuntuBootDir, opts) 422 if err != nil { 423 return fmt.Errorf("internal error: cannot identify run system bootloader: %v", err) 424 } 425 426 // extract the kernel first and mark kernel_status ready 427 kernelf, err := snapfile.Open(bootWith.KernelPath) 428 if err != nil { 429 return err 430 } 431 432 err = bl.ExtractKernelAssets(bootWith.Kernel, kernelf) 433 if err != nil { 434 return err 435 } 436 437 blVars := map[string]string{ 438 "kernel_status": "", 439 } 440 441 ebl, ok := bl.(bootloader.ExtractedRunKernelImageBootloader) 442 if ok { 443 // the bootloader supports additional extracted kernel handling 444 445 // enable the kernel on the bootloader and finally transition to 446 // run-mode last in case we get rebooted in between anywhere here 447 448 // it's okay to enable the kernel before writing the boot vars, because 449 // we haven't written snapd_recovery_mode=run, which is the critical 450 // thing that will inform the bootloader to try booting from ubuntu-boot 451 if err := ebl.EnableKernel(bootWith.Kernel); err != nil { 452 return err 453 } 454 } else { 455 // the bootloader does not support additional handling of 456 // extracted kernel images, we must name the kernel to be used 457 // explicitly in bootloader variables 458 blVars["snap_kernel"] = bootWith.Kernel.Filename() 459 } 460 461 // set the ubuntu-boot bootloader variables before triggering transition to 462 // try and boot from ubuntu-boot (that transition happens when we write 463 // snapd_recovery_mode below) 464 if err := bl.SetBootVars(blVars); err != nil { 465 return fmt.Errorf("cannot set run system environment: %v", err) 466 } 467 468 _, ok = bl.(bootloader.TrustedAssetsBootloader) 469 if ok { 470 // the bootloader can manage its boot config 471 472 // installing boot config must be performed after the boot 473 // partition has been populated with gadget data 474 if err := bl.InstallBootConfig(bootWith.UnpackedGadgetDir, opts); err != nil { 475 return fmt.Errorf("cannot install managed bootloader assets: %v", err) 476 } 477 // determine the expected command line 478 cmdline, err := ComposeCandidateCommandLine(model, bootWith.UnpackedGadgetDir) 479 if err != nil { 480 return fmt.Errorf("cannot compose the candidate command line: %v", err) 481 } 482 modeenv.CurrentKernelCommandLines = bootCommandLines{cmdline} 483 484 cmdlineVars, err := bootVarsForTrustedCommandLineFromGadget(bootWith.UnpackedGadgetDir) 485 if err != nil { 486 return fmt.Errorf("cannot prepare bootloader variables for kernel command line: %v", err) 487 } 488 if err := bl.SetBootVars(cmdlineVars); err != nil { 489 return fmt.Errorf("cannot set run system kernel command line arguments: %v", err) 490 } 491 } 492 493 // all fields that needed to be set in the modeenv must have been set by 494 // now, write modeenv to disk 495 if err := modeenv.WriteTo(InstallHostWritableDir(model)); err != nil { 496 return fmt.Errorf("cannot write modeenv: %v", err) 497 } 498 499 if sealer != nil { 500 hasHook, err := HasFDESetupHook(bootWith.Kernel) 501 if err != nil { 502 return fmt.Errorf("cannot check for fde-setup hook: %v", err) 503 } 504 505 flags := sealKeyToModeenvFlags{ 506 HasFDESetupHook: hasHook, 507 FactoryReset: makeOpts.AfterDataReset, 508 } 509 if makeOpts.Standalone { 510 flags.SnapsDir = snapBlobDir 511 } 512 // seal the encryption key to the parameters specified in modeenv 513 if err := sealKeyToModeenv(sealer.dataEncryptionKey, sealer.saveEncryptionKey, model, modeenv, flags); err != nil { 514 return err 515 } 516 } 517 518 // so far so good, we managed to install the system, so it can be used 519 // for recovery as well 520 if err := MarkRecoveryCapableSystem(recoverySystemLabel); err != nil { 521 return fmt.Errorf("cannot record %q as a recovery capable system: %v", recoverySystemLabel, err) 522 } 523 return nil 524 } 525 526 // MakeRunnableSystem is like MakeBootableImage in that it sets up a system to 527 // be able to boot, but is unique in that it is intended to be called from UC20 528 // install mode and makes the run system bootable (hence it is called 529 // "runnable"). 530 // Note that this function does not update the recovery bootloader env to 531 // actually transition to run mode here, that is left to the caller via 532 // something like boot.EnsureNextBootToRunMode(). This is to enable separately 533 // setting up a run system and actually transitioning to it, with hooks, etc. 534 // running in between. 535 func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { 536 return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{}) 537 } 538 539 // MakeRunnableStandaloneSystem operates like MakeRunnableSystem but does 540 // assume that the run system being set up is related to the current 541 // system. This is appropriate e.g when installing from a classic installer. 542 func MakeRunnableStandaloneSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { 543 // TODO consider merging this back into MakeRunnableSystem but need 544 // to consider the properties of the different input used for sealing 545 return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{ 546 Standalone: true, 547 }) 548 } 549 550 // MakeRunnableSystemAfterDataReset sets up the system to be able to boot, but it is 551 // intended to be called from UC20 factory reset mode right before switching 552 // back to the new run system. 553 func MakeRunnableSystemAfterDataReset(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { 554 return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{ 555 AfterDataReset: true, 556 }) 557 }