gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/bootloader/lk.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 bootloader 21 22 import ( 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 29 "golang.org/x/xerrors" 30 31 "github.com/snapcore/snapd/bootloader/lkenv" 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/logger" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/osutil/disks" 36 "github.com/snapcore/snapd/snap" 37 ) 38 39 const ( 40 backupStorage = true 41 primaryStorage = false 42 ) 43 44 type lk struct { 45 rootdir string 46 prepareImageTime bool 47 48 // role is what bootloader role we are, which also maps to which version of 49 // the underlying lkenv struct we use for bootenv 50 // * RoleSole == uc16 -> v1 51 // * RoleRecovery == uc20 + recovery -> v2 recovery 52 // * RoleRunMode == uc20 + run -> v2 run 53 role Role 54 55 // blDisk is what disk the bootloader informed us to use to look for the 56 // bootloader structure partitions 57 blDisk disks.Disk 58 } 59 60 // newLk create a new lk bootloader object 61 func newLk(rootdir string, opts *Options) Bootloader { 62 l := &lk{rootdir: rootdir} 63 64 l.processOpts(opts) 65 66 return l 67 } 68 69 func (l *lk) processOpts(opts *Options) { 70 if opts != nil { 71 // XXX: in the long run we want this to go away, we probably add 72 // something like "boot.PrepareImage()" and add an (optional) 73 // method "PrepareImage" to the bootloader interface that is 74 // used to setup a bootloader from prepare-image if things 75 // are very different from runtime vs image-building mode. 76 // 77 // determine mode we are in, runtime or image build 78 79 l.prepareImageTime = opts.PrepareImageTime 80 81 l.role = opts.Role 82 } 83 } 84 85 func (l *lk) Name() string { 86 return "lk" 87 } 88 89 func (l *lk) dir() string { 90 if l.prepareImageTime { 91 // at prepare-image time, then use rootdir and look for /boot/lk/ - 92 // this is only used in prepare-image time where the binary files exist 93 // extracted from the gadget 94 return filepath.Join(l.rootdir, "/boot/lk/") 95 } 96 97 // for runtime, we should only be using dir() for V1 and the dir is just 98 // the udev by-partlabel directory 99 switch l.role { 100 case RoleSole: 101 // TODO: this should be adjusted to try and use the kernel cmdline 102 // parameter for the disk that the bootloader says to find 103 // the lk partitions on if provided like the UC20 case does, but 104 // that involves changing many more tests, so let's do that in a 105 // followup PR 106 return filepath.Join(l.rootdir, "/dev/disk/by-partlabel/") 107 case RoleRecovery, RoleRunMode: 108 // TODO: maybe panic'ing here is a bit harsh... 109 panic("internal error: shouldn't be using lk.dir() for uc20 runtime modes!") 110 default: 111 panic("unexpected bootloader role for lk dir") 112 } 113 } 114 115 func (l *lk) InstallBootConfig(gadgetDir string, opts *Options) error { 116 // make sure that the opts are put into the object 117 l.processOpts(opts) 118 gadgetFile := filepath.Join(gadgetDir, l.Name()+".conf") 119 // since we are just installing static files from the gadget, there is no 120 // backup to copy, the backup will be created automatically (if allowed) by 121 // lkenv when we go to Save() the environment file. 122 systemFile, err := l.envBackstore(primaryStorage) 123 if err != nil { 124 return err 125 } 126 return genericInstallBootConfig(gadgetFile, systemFile) 127 } 128 129 func (l *lk) Present() (bool, error) { 130 // if we are in prepare-image mode or in V1, just check the env file 131 if l.prepareImageTime || l.role == RoleSole { 132 primary, err := l.envBackstore(primaryStorage) 133 if err != nil { 134 return false, err 135 } 136 137 if osutil.FileExists(primary) { 138 return true, nil 139 } 140 141 // at prepare-image time, we won't have a backup file from the gadget, 142 // so just give up here 143 if l.prepareImageTime { 144 return false, nil 145 } 146 147 // but at runtime we should check the backup in case the primary 148 // partition got corrupted 149 backup, err := l.envBackstore(backupStorage) 150 if err != nil { 151 return false, err 152 } 153 return osutil.FileExists(backup), nil 154 } 155 156 // otherwise for V2, non-sole bootloader roles we need to check on the 157 // partition name existing, note that devPathForPartName will only return 158 // partiallyFound as true if it reasonably concludes that this is a lk 159 // device, so in that case forward err, otherwise return err as nil 160 partitionLabel := l.partLabelForRole() 161 _, partiallyFound, err := l.devPathForPartName(partitionLabel) 162 if partiallyFound { 163 return true, err 164 } 165 return false, nil 166 } 167 168 func (l *lk) partLabelForRole() string { 169 // TODO: should the partition labels be fetched from gadget.yaml instead? we 170 // have roles that we could use in the gadget.yaml structures to find 171 // them 172 label := "" 173 switch l.role { 174 case RoleSole, RoleRunMode: 175 label = "snapbootsel" 176 case RoleRecovery: 177 label = "snaprecoverysel" 178 default: 179 panic(fmt.Sprintf("unknown bootloader role for littlekernel: %s", l.role)) 180 } 181 return label 182 } 183 184 // envBackstore returns a filepath for the lkenv bootloader environment file. 185 // For prepare-image time operations, it will be a normal config file; for 186 // runtime operations it will be a device file from a udev-created symlink in 187 // /dev/disk. If backup is true then the filename is suffixed with "bak" or at 188 // runtime the partition label is suffixed with "bak". 189 func (l *lk) envBackstore(backup bool) (string, error) { 190 partitionLabelOrConfFile := l.partLabelForRole() 191 if backup { 192 partitionLabelOrConfFile += "bak" 193 } 194 if l.prepareImageTime { 195 // at prepare-image time, we just use the env file, but append .bin 196 // since it is a file from the gadget we will evenutally install into 197 // a partition when flashing the image 198 return filepath.Join(l.dir(), partitionLabelOrConfFile+".bin"), nil 199 } 200 201 if l.role == RoleSole { 202 // for V1, we just use the partition label directly, dir() here will be 203 // the udev by-partlabel symlink dir. 204 // see TODO: in l.dir(), this should eventually also be using 205 // devPathForPartName() too 206 return filepath.Join(l.dir(), partitionLabelOrConfFile), nil 207 } 208 209 // for RoleRun or RoleRecovery, we need to find the partition securely 210 partitionFile, _, err := l.devPathForPartName(partitionLabelOrConfFile) 211 if err != nil { 212 return "", err 213 } 214 return partitionFile, nil 215 } 216 217 // devPathForPartName returns the environment file in /dev for the partition 218 // name, which will always be a partition on the disk given by 219 // the kernel command line parameter "snapd_lk_boot_disk" set by the bootloader. 220 // It returns a boolean as the second parameter which is primarily used by 221 // Present() to indicate if the searching process got "far enough" to reasonably 222 // conclude that the device is using a lk bootloader, but we had errors finding 223 // it. This feature is mainly for better error reporting in logs. 224 func (l *lk) devPathForPartName(partName string) (string, bool, error) { 225 // lazily initialize l.blDisk if it hasn't yet been initialized 226 if l.blDisk == nil { 227 // For security, we want to restrict our search for the partition 228 // that the binary structure exists on to only the disk that the 229 // bootloader tells us to search on - it uses a kernel cmdline 230 // parameter "snapd_lk_boot_disk" to indicated which disk we should look 231 // for partitions on. In typical boot scenario this will be something like 232 // "snapd_lk_boot_disk=mmcblk0". 233 m, err := osutil.KernelCommandLineKeyValues("snapd_lk_boot_disk") 234 if err != nil { 235 // return false, since we don't have enough info to conclude there 236 // is likely a lk bootloader here or not 237 return "", false, err 238 } 239 blDiskName, ok := m["snapd_lk_boot_disk"] 240 if blDiskName == "" { 241 // we switch on ok here, since if "snapd_lk_boot_disk" was found at 242 // all on the kernel command line, we can reasonably assume that 243 // only the lk bootloader would have put it there, but maybe 244 // it is buggy and put an empty value there. 245 if ok { 246 return "", true, fmt.Errorf("kernel command line parameter \"snapd_lk_boot_disk\" is empty") 247 } 248 // if we didn't find the kernel command line parameter at all, then 249 // we want to return false because we don't have enough info 250 return "", false, fmt.Errorf("kernel command line parameter \"snapd_lk_boot_disk\" is missing") 251 } 252 253 disk, err := disks.DiskFromDeviceName(blDiskName) 254 if err != nil { 255 return "", true, fmt.Errorf("cannot find disk from bootloader supplied disk name %q: %v", blDiskName, err) 256 } 257 258 l.blDisk = disk 259 } 260 261 partitionUUID, err := l.blDisk.FindMatchingPartitionUUIDWithPartLabel(partName) 262 if err != nil { 263 return "", true, err 264 } 265 266 // for the runtime lk bootloader we should never prefix the path with the 267 // bootloader rootdir and instead always use dirs.GlobalRootDir, since the 268 // file we are providing is at an absolute location for all bootloaders, 269 // regardless of role, in /dev, so using dirs.GlobalRootDir ensures that we 270 // are still able to mock things in test functions, but that we never end up 271 // trying to use a path like /run/mnt/ubuntu-boot/dev/disk/by-partuuid/... 272 // for example 273 return filepath.Join(dirs.GlobalRootDir, "/dev/disk/by-partuuid", partitionUUID), true, nil 274 } 275 276 func (l *lk) newenv() (*lkenv.Env, error) { 277 // check which role we are, it affects which struct is used for the env 278 var version lkenv.Version 279 switch l.role { 280 case RoleSole: 281 version = lkenv.V1 282 case RoleRecovery: 283 version = lkenv.V2Recovery 284 case RoleRunMode: 285 version = lkenv.V2Run 286 } 287 f, err := l.envBackstore(primaryStorage) 288 if err != nil { 289 return nil, err 290 } 291 292 backup, err := l.envBackstore(backupStorage) 293 if err != nil { 294 return nil, err 295 } 296 297 return lkenv.NewEnv(f, backup, version), nil 298 } 299 300 func (l *lk) GetBootVars(names ...string) (map[string]string, error) { 301 out := make(map[string]string) 302 303 env, err := l.newenv() 304 if err != nil { 305 return nil, err 306 } 307 if err := env.Load(); err != nil { 308 return nil, err 309 } 310 311 for _, name := range names { 312 out[name] = env.Get(name) 313 } 314 315 return out, nil 316 } 317 318 func (l *lk) SetBootVars(values map[string]string) error { 319 env, err := l.newenv() 320 if err != nil { 321 return err 322 } 323 // if we couldn't find the env, that's okay, as this may be the first thing 324 // to write boot vars to the env 325 if err := env.Load(); err != nil { 326 // if the error was something other than file not found, it is fatal 327 if !xerrors.Is(err, os.ErrNotExist) { 328 return err 329 } 330 // otherwise at prepare-image time it is okay to not have the file 331 // existing, but we should always have it at runtime as it is a 332 // partition, so it is highly unexpected for it to be missing and we 333 // cannot proceed 334 // also note that env.Load() will automatically try the backup, so if 335 // Load() failed to get the backup at runtime there's nothing left to 336 // try here 337 if !l.prepareImageTime { 338 return err 339 } 340 } 341 342 // update environment only if something changes 343 dirty := false 344 for k, v := range values { 345 // already set to the right value, nothing to do 346 if env.Get(k) == v { 347 continue 348 } 349 env.Set(k, v) 350 dirty = true 351 } 352 353 if dirty { 354 return env.Save() 355 } 356 357 return nil 358 } 359 360 func (l *lk) ExtractRecoveryKernelAssets(recoverySystemDir string, sn snap.PlaceInfo, snapf snap.Container) error { 361 if !l.prepareImageTime { 362 // error case, we cannot be extracting a recovery kernel and also be 363 // called with !opts.PrepareImageTime (yet) 364 365 // TODO:UC20: this codepath is exercised when creating new 366 // recovery systems from runtime 367 return fmt.Errorf("internal error: extracting recovery kernel assets is not supported for a runtime lk bootloader") 368 } 369 370 env, err := l.newenv() 371 if err != nil { 372 return err 373 } 374 if err := env.Load(); err != nil { 375 // don't handle os.ErrNotExist specially here, it doesn't really make 376 // sense to extract kernel assets if we can't load the existing env, 377 // since then the caller would just see an error about not being able 378 // to find the kernel blob name (as they will all be empty in the env), 379 // when in reality the reason one can't find an available boot image 380 // partition is because we couldn't read the env file and so returning 381 // that error is better 382 return err 383 } 384 385 recoverySystem := filepath.Base(recoverySystemDir) 386 387 bootPartition, err := env.FindFreeRecoverySystemBootPartition(recoverySystem) 388 if err != nil { 389 return err 390 } 391 392 // we are preparing a recovery system, just extract boot image to bootloader 393 // directory 394 logger.Debugf("extracting recovery kernel %s to %s with lk bootloader", sn.SnapName(), recoverySystem) 395 if err := snapf.Unpack(env.GetBootImageName(), l.dir()); err != nil { 396 return fmt.Errorf("cannot open unpacked %s: %v", env.GetBootImageName(), err) 397 } 398 399 if err := env.SetBootPartitionRecoverySystem(bootPartition, recoverySystem); err != nil { 400 return err 401 } 402 403 return env.Save() 404 } 405 406 // ExtractKernelAssets extract kernel assets per bootloader specifics 407 // lk bootloader requires boot partition to hold valid boot image 408 // there are two boot partition available, one holding current bootimage 409 // kernel assets are extracted to other (free) boot partition 410 // in case this function is called as part of image creation, 411 // boot image is extracted to the file 412 func (l *lk) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error { 413 blobName := s.Filename() 414 415 logger.Debugf("extracting kernel assets for %s with lk bootloader", s.SnapName()) 416 417 env, err := l.newenv() 418 if err != nil { 419 return err 420 } 421 if err := env.Load(); err != nil { 422 // don't handle os.ErrNotExist specially here, it doesn't really make 423 // sense to extract kernel assets if we can't load the existing env, 424 // since then the caller would just see an error about not being able 425 // to find the kernel blob name (as they will all be empty in the env), 426 // when in reality the reason one can't find an available boot image 427 // partition is because we couldn't read the env file and so returning 428 // that error is better 429 return err 430 } 431 432 bootPartition, err := env.FindFreeKernelBootPartition(blobName) 433 if err != nil { 434 return err 435 } 436 437 if l.prepareImageTime { 438 // we are preparing image, just extract boot image to bootloader directory 439 if err := snapf.Unpack(env.GetBootImageName(), l.dir()); err != nil { 440 return fmt.Errorf("cannot open unpacked %s: %v", env.GetBootImageName(), err) 441 } 442 } else { 443 // this is live system, extracted bootimg needs to be flashed to 444 // free bootimg partition and env has to be updated with 445 // new kernel snap to bootimg partition mapping 446 tmpdir, err := ioutil.TempDir("", "bootimg") 447 if err != nil { 448 return fmt.Errorf("cannot create temp directory: %v", err) 449 } 450 defer os.RemoveAll(tmpdir) 451 452 bootImg := env.GetBootImageName() 453 if err := snapf.Unpack(bootImg, tmpdir); err != nil { 454 return fmt.Errorf("cannot unpack %s: %v", bootImg, err) 455 } 456 // write boot.img to free boot partition 457 bootimgName := filepath.Join(tmpdir, bootImg) 458 bif, err := os.Open(bootimgName) 459 if err != nil { 460 return fmt.Errorf("cannot open unpacked %s: %v", bootImg, err) 461 } 462 defer bif.Close() 463 var bpart string 464 // TODO: for RoleSole bootloaders this will eventually be the same 465 // codepath as for non-RoleSole bootloader 466 if l.role == RoleSole { 467 bpart = filepath.Join(l.dir(), bootPartition) 468 } else { 469 bpart, _, err = l.devPathForPartName(bootPartition) 470 if err != nil { 471 return err 472 } 473 } 474 475 bpf, err := os.OpenFile(bpart, os.O_WRONLY, 0660) 476 if err != nil { 477 return fmt.Errorf("cannot open boot partition [%s]: %v", bpart, err) 478 } 479 defer bpf.Close() 480 481 if _, err := io.Copy(bpf, bif); err != nil { 482 return err 483 } 484 } 485 486 if err := env.SetBootPartitionKernel(bootPartition, blobName); err != nil { 487 return err 488 } 489 490 return env.Save() 491 } 492 493 func (l *lk) RemoveKernelAssets(s snap.PlaceInfo) error { 494 blobName := s.Filename() 495 logger.Debugf("removing kernel assets for %s with lk bootloader", s.SnapName()) 496 497 env, err := l.newenv() 498 if err != nil { 499 return err 500 } 501 if err := env.Load(); err != nil { 502 // don't handle os.ErrNotExist specially here, it doesn't really make 503 // sense to delete kernel assets if we can't load the existing env, 504 // since then the caller would just see an error about not being able 505 // to find the kernel blob name, when in reality the reason one can't 506 // find that kernel blob name is because we couldn't read the env file 507 return err 508 } 509 err = env.RemoveKernelFromBootPartition(blobName) 510 if err == nil { 511 // found and removed the revision from the bootimg matrix, need to 512 // update the env to persist the change 513 return env.Save() 514 } 515 return nil 516 }