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