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  }