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