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