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