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  }