github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/boot/bootstate20.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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  	"path/filepath"
    25  
    26  	"github.com/snapcore/snapd/asserts"
    27  	"github.com/snapcore/snapd/bootloader"
    28  	"github.com/snapcore/snapd/dirs"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/osutil"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/strutil"
    33  )
    34  
    35  func newBootState20(typ snap.Type, dev Device) bootState {
    36  	switch typ {
    37  	case snap.TypeBase:
    38  		return &bootState20Base{}
    39  	case snap.TypeKernel:
    40  		return &bootState20Kernel{
    41  			dev: dev,
    42  		}
    43  	default:
    44  		panic(fmt.Sprintf("cannot make a bootState20 for snap type %q", typ))
    45  	}
    46  }
    47  
    48  func loadModeenv() (*Modeenv, error) {
    49  	modeenv, err := ReadModeenv("")
    50  	if err != nil {
    51  		return nil, fmt.Errorf("cannot get snap revision: unable to read modeenv: %v", err)
    52  	}
    53  	return modeenv, nil
    54  }
    55  
    56  //
    57  // bootloaderKernelState20 methods
    58  //
    59  
    60  type bootloaderKernelState20 interface {
    61  	// load will setup any state / actors needed to use other methods
    62  	load() error
    63  	// kernelStatus returns the current status of the kernel, i.e. the
    64  	// kernel_status bootenv
    65  	kernelStatus() string
    66  	// kernel returns the current non-try kernel
    67  	kernel() snap.PlaceInfo
    68  	// kernel returns the current try kernel if it exists on the bootloader
    69  	tryKernel() (snap.PlaceInfo, error)
    70  
    71  	// setNextKernel marks the kernel as the next, if it's not the currently
    72  	// booted kernel, then the specified kernel is setup as a try-kernel
    73  	setNextKernel(sn snap.PlaceInfo, status string) error
    74  	// markSuccessfulKernel marks the specified kernel as having booted
    75  	// successfully, whether that kernel is the current kernel or the try-kernel
    76  	markSuccessfulKernel(sn snap.PlaceInfo) error
    77  }
    78  
    79  //
    80  // bootStateUpdate for 20 methods
    81  //
    82  
    83  type bootCommitTask func() error
    84  
    85  // bootStateUpdate20 implements the bootStateUpdate interface for both kernel
    86  // and base snaps on UC20.
    87  type bootStateUpdate20 struct {
    88  	// tasks to run before the modeenv has been written
    89  	preModeenvTasks []bootCommitTask
    90  
    91  	// the modeenv that was read from disk
    92  	modeenv *Modeenv
    93  
    94  	// the modeenv that will be written out in commit
    95  	writeModeenv *Modeenv
    96  
    97  	// tasks to run after the modeenv has been written
    98  	postModeenvTasks []bootCommitTask
    99  
   100  	// model set if a reseal might be necessary
   101  	resealModel *asserts.Model
   102  }
   103  
   104  func (u20 *bootStateUpdate20) preModeenv(task bootCommitTask) {
   105  	u20.preModeenvTasks = append(u20.preModeenvTasks, task)
   106  }
   107  
   108  func (u20 *bootStateUpdate20) postModeenv(task bootCommitTask) {
   109  	u20.postModeenvTasks = append(u20.postModeenvTasks, task)
   110  }
   111  
   112  func (u20 *bootStateUpdate20) resealForModel(model *asserts.Model) {
   113  	u20.resealModel = model
   114  }
   115  
   116  func newBootStateUpdate20(m *Modeenv) (*bootStateUpdate20, error) {
   117  	u20 := &bootStateUpdate20{}
   118  	if m == nil {
   119  		var err error
   120  		m, err = loadModeenv()
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  	}
   125  	// copy the modeenv for the write object
   126  	u20.modeenv = m
   127  	var err error
   128  	u20.writeModeenv, err = m.Copy()
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	return u20, nil
   133  }
   134  
   135  // commit will write out boot state persistently to disk.
   136  func (u20 *bootStateUpdate20) commit() error {
   137  	// The actual actions taken here will depend on what things were called
   138  	// before commit(), either setNextBoot for a single type of kernel snap, or
   139  	// markSuccessful for kernel and/or base snaps.
   140  	// It is expected that the caller code is carefully analyzed to avoid
   141  	// critical points where a hard system reset during that critical point
   142  	// would brick a device or otherwise severely fail an update.
   143  	// There are three things that callers can do before calling commit(),
   144  	// 1. modify writeModeenv to specify new values for things that will be
   145  	//    written to disk in the modeenv.
   146  	// 2. Add tasks to run before writing the modeenv.
   147  	// 3. Add tasks to run after writing the modeenv.
   148  
   149  	// first handle any pre-modeenv writing tasks
   150  	for _, t := range u20.preModeenvTasks {
   151  		if err := t(); err != nil {
   152  			return err
   153  		}
   154  	}
   155  
   156  	modeenvRewritten := false
   157  	// next write the modeenv if it changed
   158  	if !u20.writeModeenv.deepEqual(u20.modeenv) {
   159  		if err := u20.writeModeenv.Write(); err != nil {
   160  			return err
   161  		}
   162  		modeenvRewritten = true
   163  	}
   164  
   165  	// next reseal using the modeenv values, we do this before any
   166  	// post-modeenv tasks so if we are rebooted at any point after
   167  	// the reseal even before the post tasks are completed, we
   168  	// still boot properly
   169  	if u20.resealModel != nil {
   170  		// if there is ambiguity whether the boot chains have
   171  		// changed because of unasserted kernels, then pass a
   172  		// flag as hint whether to reseal based on whether we
   173  		// wrote the modeenv
   174  		expectReseal := modeenvRewritten
   175  		if err := resealKeyToModeenv(dirs.GlobalRootDir, u20.resealModel, u20.writeModeenv, expectReseal); err != nil {
   176  			return err
   177  		}
   178  	}
   179  
   180  	// finally handle any post-modeenv writing tasks
   181  	for _, t := range u20.postModeenvTasks {
   182  		if err := t(); err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  //
   191  // kernel snap methods
   192  //
   193  
   194  // bootState20Kernel implements the bootState interface for kernel snaps on
   195  // UC20. It is used for both setNext() and markSuccessful(), with both of those
   196  // methods returning bootStateUpdate20 to be used with bootStateUpdate.
   197  type bootState20Kernel struct {
   198  	bks bootloaderKernelState20
   199  
   200  	// used to find the bootloader to manipulate the enabled kernel, etc.
   201  	blOpts *bootloader.Options
   202  	blDir  string
   203  
   204  	dev Device
   205  }
   206  
   207  func (ks20 *bootState20Kernel) loadBootenv() error {
   208  	// don't setup multiple times
   209  	if ks20.bks != nil {
   210  		return nil
   211  	}
   212  
   213  	// find the run-mode bootloader
   214  	var opts *bootloader.Options
   215  	if ks20.blOpts != nil {
   216  		opts = ks20.blOpts
   217  	} else {
   218  		opts = &bootloader.Options{
   219  			Role: bootloader.RoleRunMode,
   220  		}
   221  	}
   222  	bl, err := bootloader.Find(ks20.blDir, opts)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	ebl, ok := bl.(bootloader.ExtractedRunKernelImageBootloader)
   227  	if ok {
   228  		// use the new 20-style ExtractedRunKernelImage implementation
   229  		ks20.bks = &extractedRunKernelImageBootloaderKernelState{ebl: ebl}
   230  	} else {
   231  		// use fallback pure bootloader env implementation
   232  		ks20.bks = &envRefExtractedKernelBootloaderKernelState{bl: bl}
   233  	}
   234  
   235  	// setup the bootloaderKernelState20
   236  	if err := ks20.bks.load(); err != nil {
   237  		return err
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  func (ks20 *bootState20Kernel) revisions() (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) {
   244  	var tryBootSn snap.PlaceInfo
   245  	err = ks20.loadBootenv()
   246  	if err != nil {
   247  		return nil, nil, "", err
   248  	}
   249  
   250  	status := ks20.bks.kernelStatus()
   251  	kern := ks20.bks.kernel()
   252  
   253  	tryKernel, err := ks20.bks.tryKernel()
   254  	// if err is ErrNoTryKernelRef, then we will just return nil as the trySnap
   255  	if err != nil && err != bootloader.ErrNoTryKernelRef {
   256  		return kern, nil, "", newTrySnapErrorf("cannot identify try kernel snap: %v", err)
   257  	}
   258  
   259  	if err == nil {
   260  		tryBootSn = tryKernel
   261  	}
   262  
   263  	return kern, tryBootSn, status, nil
   264  }
   265  
   266  func (ks20 *bootState20Kernel) revisionsFromModeenv(*Modeenv) (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) {
   267  	// the kernel snap doesn't use modeenv at all for getting their revisions
   268  	return ks20.revisions()
   269  }
   270  
   271  func (ks20 *bootState20Kernel) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
   272  	// call the generic method with this object to do most of the legwork
   273  	u20, sn, err := selectSuccessfulBootSnap(ks20, update)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	// XXX: this if arises because some unit tests rely on not setting up kernel
   279  	// details and just operating on the base snap but this situation would
   280  	// never happen in reality
   281  	if sn != nil {
   282  		// On commit, mark the kernel successful before rewriting the modeenv
   283  		// because if we first rewrote the modeenv then got rebooted before
   284  		// marking the kernel successful, the bootloader would see that the boot
   285  		// failed to mark it successful and then fall back to the original
   286  		// kernel, but that kernel would no longer be in the modeenv, so we
   287  		// would die in the initramfs
   288  		u20.preModeenv(func() error { return ks20.bks.markSuccessfulKernel(sn) })
   289  
   290  		// On commit, set CurrentKernels as just this kernel because that is the
   291  		// successful kernel we booted
   292  		u20.writeModeenv.CurrentKernels = []string{sn.Filename()}
   293  
   294  		// keep track of the model for resealing
   295  		u20.resealForModel(ks20.dev.Model())
   296  	}
   297  
   298  	return u20, nil
   299  }
   300  
   301  func (ks20 *bootState20Kernel) setNext(next snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) {
   302  	u20, nextStatus, err := genericSetNext(ks20, next)
   303  	if err != nil {
   304  		return false, nil, err
   305  	}
   306  
   307  	// if we are setting a snap as a try snap, then we need to reboot
   308  	rebootRequired = false
   309  	if nextStatus == TryStatus {
   310  		rebootRequired = true
   311  	}
   312  
   313  	currentKernel := ks20.bks.kernel()
   314  	if next.Filename() != currentKernel.Filename() {
   315  		// on commit, add this kernel to the modeenv
   316  		u20.writeModeenv.CurrentKernels = append(
   317  			u20.writeModeenv.CurrentKernels,
   318  			next.Filename(),
   319  		)
   320  	}
   321  
   322  	// On commit, if we are about to try an update, and need to set the next
   323  	// kernel before rebooting, we need to do that after updating the modeenv,
   324  	// because if we did it before and got rebooted in between setting the next
   325  	// kernel and updating the modeenv, the initramfs would fail the boot
   326  	// because the modeenv doesn't "trust" or expect the new kernel that booted.
   327  	// As such, set the next kernel as a post modeenv task.
   328  	u20.postModeenv(func() error { return ks20.bks.setNextKernel(next, nextStatus) })
   329  
   330  	// keep track of the model for resealing
   331  	u20.resealForModel(ks20.dev.Model())
   332  
   333  	return rebootRequired, u20, nil
   334  }
   335  
   336  // selectAndCommitSnapInitramfsMount chooses which snap should be mounted
   337  // during the initramfs, and commits that choice if it needs state updated.
   338  // Choosing to boot/mount the base snap needs to be committed to the
   339  // modeenv, but no state needs to be committed when choosing to mount a
   340  // kernel snap.
   341  func (ks20 *bootState20Kernel) selectAndCommitSnapInitramfsMount(modeenv *Modeenv) (sn snap.PlaceInfo, err error) {
   342  	// first do the generic choice of which snap to use
   343  	first, second, err := genericInitramfsSelectSnap(ks20, modeenv, TryingStatus, "kernel")
   344  	if err != nil && err != errTrySnapFallback {
   345  		return nil, err
   346  	}
   347  
   348  	if err == errTrySnapFallback {
   349  		// this should not actually return, it should immediately reboot
   350  		return nil, initramfsReboot()
   351  	}
   352  
   353  	// now validate the chosen kernel snap against the modeenv CurrentKernel's
   354  	// setting
   355  	if strutil.ListContains(modeenv.CurrentKernels, first.Filename()) {
   356  		return first, nil
   357  	}
   358  
   359  	// if we didn't trust the first kernel in the modeenv, and second is set as
   360  	// a fallback, that means we booted a try kernel which is the first kernel,
   361  	// but we need to fallback to the second kernel, but we can't do that in the
   362  	// initramfs, we need to reboot so the bootloader boots the fallback kernel
   363  	// for us
   364  
   365  	if second != nil {
   366  		// this should not actually return, it should immediately reboot
   367  		return nil, initramfsReboot()
   368  	}
   369  
   370  	// no fallback expected, so first snap _is_ the only kernel and isn't
   371  	// trusted!
   372  	// since we have nothing to fallback to, we don't issue a reboot and will
   373  	// instead just fail the systemd unit in the initramfs for an operator to
   374  	// debug/fix
   375  	return nil, fmt.Errorf("fallback kernel snap %q is not trusted in the modeenv", first.Filename())
   376  }
   377  
   378  //
   379  // base snap methods
   380  //
   381  
   382  // bootState20Base implements the bootState interface for base snaps on UC20.
   383  // It is used for both setNext() and markSuccessful(), with both of those
   384  // methods returning bootStateUpdate20 to be used with bootStateUpdate.
   385  type bootState20Base struct{}
   386  
   387  // revisions returns the current boot snap and optional try boot snap for the
   388  // type specified in bsgeneric.
   389  func (bs20 *bootState20Base) revisions() (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) {
   390  	modeenv, err := loadModeenv()
   391  	if err != nil {
   392  		return nil, nil, "", err
   393  	}
   394  	return bs20.revisionsFromModeenv(modeenv)
   395  }
   396  
   397  func (bs20 *bootState20Base) revisionsFromModeenv(modeenv *Modeenv) (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) {
   398  	var bootSn, tryBootSn snap.PlaceInfo
   399  
   400  	if modeenv.Base == "" {
   401  		return nil, nil, "", fmt.Errorf("cannot get snap revision: modeenv base boot variable is empty")
   402  	}
   403  
   404  	bootSn, err = snap.ParsePlaceInfoFromSnapFileName(modeenv.Base)
   405  	if err != nil {
   406  		return nil, nil, "", fmt.Errorf("cannot get snap revision: modeenv base boot variable is invalid: %v", err)
   407  	}
   408  
   409  	if modeenv.BaseStatus != DefaultStatus && modeenv.TryBase != "" {
   410  		tryBootSn, err = snap.ParsePlaceInfoFromSnapFileName(modeenv.TryBase)
   411  		if err != nil {
   412  			return bootSn, nil, "", newTrySnapErrorf("cannot get snap revision: modeenv try base boot variable is invalid: %v", err)
   413  		}
   414  	}
   415  
   416  	return bootSn, tryBootSn, modeenv.BaseStatus, nil
   417  }
   418  
   419  func (bs20 *bootState20Base) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
   420  	// call the generic method with this object to do most of the legwork
   421  	u20, sn, err := selectSuccessfulBootSnap(bs20, update)
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  
   426  	// on commit, always clear the base_status and try_base when marking
   427  	// successful, this has the useful side-effect of cleaning up if we have
   428  	// base_status=trying but no try_base set, or if we had an issue with
   429  	// try_base being invalid
   430  	u20.writeModeenv.BaseStatus = DefaultStatus
   431  	u20.writeModeenv.TryBase = ""
   432  
   433  	// set the base
   434  	u20.writeModeenv.Base = sn.Filename()
   435  
   436  	return u20, nil
   437  }
   438  
   439  func (bs20 *bootState20Base) setNext(next snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) {
   440  	u20, nextStatus, err := genericSetNext(bs20, next)
   441  	if err != nil {
   442  		return false, nil, err
   443  	}
   444  
   445  	// if we are setting a snap as a try snap, then we need to reboot
   446  	rebootRequired = false
   447  	if nextStatus == TryStatus {
   448  		// only update the try base if we are actually in try status
   449  		u20.writeModeenv.TryBase = next.Filename()
   450  		rebootRequired = true
   451  	}
   452  
   453  	// always update the base status
   454  	u20.writeModeenv.BaseStatus = nextStatus
   455  
   456  	return rebootRequired, u20, nil
   457  }
   458  
   459  // selectAndCommitSnapInitramfsMount chooses which snap should be mounted
   460  // during the early boot sequence, i.e. the initramfs, and commits that
   461  // choice if it needs state updated.
   462  // Choosing to boot/mount the base snap needs to be committed to the
   463  // modeenv, but no state needs to be committed when choosing to mount a
   464  // kernel snap.
   465  func (bs20 *bootState20Base) selectAndCommitSnapInitramfsMount(modeenv *Modeenv) (sn snap.PlaceInfo, err error) {
   466  	// first do the generic choice of which snap to use
   467  	// the logic in that function is sufficient to pick the base snap entirely,
   468  	// so we don't ever need to look at the fallback snap, we just need to know
   469  	// whether the chosen snap is a try snap or not, if it is then we process
   470  	// the modeenv in the "try" -> "trying" case
   471  	first, second, err := genericInitramfsSelectSnap(bs20, modeenv, TryStatus, "base")
   472  	// errTrySnapFallback is handled manually by inspecting second below
   473  	if err != nil && err != errTrySnapFallback {
   474  		return nil, err
   475  	}
   476  
   477  	modeenvChanged := false
   478  
   479  	// apply the update logic to the choices modeenv
   480  	switch modeenv.BaseStatus {
   481  	case TryStatus:
   482  		// if we were in try status and we have a fallback, then we are in a
   483  		// normal try state and we change status to TryingStatus now
   484  		// all other cleanup of state is left to user space snapd
   485  		if second != nil {
   486  			modeenv.BaseStatus = TryingStatus
   487  			modeenvChanged = true
   488  		}
   489  	case TryingStatus:
   490  		// we tried to boot a try base snap and failed, so we need to reset
   491  		// BaseStatus
   492  		modeenv.BaseStatus = DefaultStatus
   493  		modeenvChanged = true
   494  	case DefaultStatus:
   495  		// nothing to do
   496  	default:
   497  		// log a message about invalid setting
   498  		logger.Noticef("invalid setting for \"base_status\" in modeenv : %q", modeenv.BaseStatus)
   499  	}
   500  
   501  	if modeenvChanged {
   502  		err = modeenv.Write()
   503  		if err != nil {
   504  			return nil, err
   505  		}
   506  	}
   507  
   508  	return first, nil
   509  }
   510  
   511  //
   512  // generic methods
   513  //
   514  
   515  type bootState20 interface {
   516  	bootState
   517  	// revisionsFromModeenv implements bootState.revisions but starting
   518  	// from an already loaded Modeenv.
   519  	revisionsFromModeenv(*Modeenv) (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error)
   520  }
   521  
   522  // genericSetNext implements the generic logic for setting up a snap to be tried
   523  // for boot and works for both kernel and base snaps (though not
   524  // simultaneously).
   525  func genericSetNext(b bootState20, next snap.PlaceInfo) (u20 *bootStateUpdate20, setStatus string, err error) {
   526  	u20, err = newBootStateUpdate20(nil)
   527  	if err != nil {
   528  		return nil, "", err
   529  	}
   530  
   531  	// get the current snap
   532  	current, _, _, err := b.revisionsFromModeenv(u20.modeenv)
   533  	if err != nil {
   534  		return nil, "", err
   535  	}
   536  
   537  	// check if the next snap is really the same as the current snap, in which
   538  	// case we either do nothing or just clear the status (and not reboot)
   539  	if current.SnapName() == next.SnapName() && next.SnapRevision() == current.SnapRevision() {
   540  		// if we are setting the next snap as the current snap, don't need to
   541  		// change any snaps, just reset the status to default
   542  		return u20, DefaultStatus, nil
   543  	}
   544  
   545  	// by default we will set the status as "try" to prepare for an update,
   546  	// which also by default will require a reboot
   547  	return u20, TryStatus, nil
   548  }
   549  
   550  func toBootStateUpdate20(update bootStateUpdate) (u20 *bootStateUpdate20, err error) {
   551  	// try to extract bootStateUpdate20 out of update
   552  	if update != nil {
   553  		var ok bool
   554  		if u20, ok = update.(*bootStateUpdate20); !ok {
   555  			return nil, fmt.Errorf("internal error, cannot thread %T with update for UC20", update)
   556  		}
   557  	}
   558  	if u20 == nil {
   559  		// make a new one, also loading modeenv
   560  		u20, err = newBootStateUpdate20(nil)
   561  		if err != nil {
   562  			return nil, err
   563  		}
   564  	}
   565  	return u20, nil
   566  }
   567  
   568  // selectSuccessfulBootSnap inspects the specified boot state to pick what
   569  // boot snap should be marked as successful and use as a valid rollback target.
   570  // If the first return value is non-nil, the second return value will be the
   571  // snap that was booted and should be marked as successful.
   572  func selectSuccessfulBootSnap(b bootState20, update bootStateUpdate) (
   573  	u20 *bootStateUpdate20,
   574  	bootedSnap snap.PlaceInfo,
   575  	err error,
   576  ) {
   577  	u20, err = toBootStateUpdate20(update)
   578  	if err != nil {
   579  		return nil, nil, err
   580  	}
   581  
   582  	// get the try snap and the current status
   583  	sn, trySnap, status, err := b.revisionsFromModeenv(u20.modeenv)
   584  	if err != nil {
   585  		return nil, nil, err
   586  	}
   587  
   588  	// kernel_status and base_status go from "" -> "try" (set by snapd), to
   589  	// "try" -> "trying" (set by the boot script)
   590  	// so if we are in "trying" mode, then we should choose the try snap
   591  	if status == TryingStatus && trySnap != nil {
   592  		return u20, trySnap, nil
   593  	}
   594  
   595  	// if we are not in trying then choose the normal snap
   596  	return u20, sn, nil
   597  }
   598  
   599  // genericInitramfsSelectSnap will run the logic to choose which snap should be
   600  // mounted during the initramfs using the given bootState and the expected try
   601  // status. The try status is needed because during the initramfs we will have
   602  // different statuses for kernel vs base snaps, where base snap is expected to
   603  // be in "try" mode, but kernel is expected to be in "trying" mode. It returns
   604  // the first and second choice for what snaps to mount. If there is a second
   605  // snap, then that snap is the fallback or non-trying snap and the first snap is
   606  // the try snap.
   607  func genericInitramfsSelectSnap(bs bootState20, modeenv *Modeenv, expectedTryStatus, typeString string) (
   608  	firstChoice, secondChoice snap.PlaceInfo,
   609  	err error,
   610  ) {
   611  	curSnap, trySnap, snapTryStatus, err := bs.revisionsFromModeenv(modeenv)
   612  
   613  	if err != nil && !isTrySnapError(err) {
   614  		// we have no fallback snap!
   615  		return nil, nil, fmt.Errorf("fallback %s snap unusable: %v", typeString, err)
   616  	}
   617  
   618  	// check that the current snap actually exists
   619  	file := curSnap.Filename()
   620  	snapPath := filepath.Join(dirs.SnapBlobDirUnder(InitramfsWritableDir), file)
   621  	if !osutil.FileExists(snapPath) {
   622  		// somehow the boot snap doesn't exist in ubuntu-data
   623  		// for a kernel, this could happen if we have some bug where ubuntu-boot
   624  		// isn't properly updated and never changes, but snapd thinks it was
   625  		// updated and eventually snapd garbage collects old revisions of
   626  		// the kernel snap as it is "refreshed"
   627  		// for a base, this could happen if the modeenv is manipulated
   628  		// out-of-band from snapd
   629  		return nil, nil, fmt.Errorf("%s snap %q does not exist on ubuntu-data", typeString, file)
   630  	}
   631  
   632  	if err != nil && isTrySnapError(err) {
   633  		// just log that we had issues with the try snap and continue with
   634  		// using the normal snap
   635  		logger.Noticef("unable to process try %s snap: %v", typeString, err)
   636  		return curSnap, nil, errTrySnapFallback
   637  	}
   638  	if snapTryStatus != expectedTryStatus {
   639  		// the status is unexpected, log if its value is invalid and continue
   640  		// with the normal snap
   641  		fallbackErr := errTrySnapFallback
   642  		switch snapTryStatus {
   643  		case DefaultStatus:
   644  			fallbackErr = nil
   645  		case TryStatus, TryingStatus:
   646  		default:
   647  			logger.Noticef("\"%s_status\" has an invalid setting: %q", typeString, snapTryStatus)
   648  		}
   649  		return curSnap, nil, fallbackErr
   650  	}
   651  	// then we are trying a snap update and there should be a try snap
   652  	if trySnap == nil {
   653  		// it is unexpected when there isn't one
   654  		logger.Noticef("try-%[1]s snap is empty, but \"%[1]s_status\" is \"trying\"", typeString)
   655  		return curSnap, nil, errTrySnapFallback
   656  	}
   657  	trySnapPath := filepath.Join(dirs.SnapBlobDirUnder(InitramfsWritableDir), trySnap.Filename())
   658  	if !osutil.FileExists(trySnapPath) {
   659  		// or when the snap file does not exist
   660  		logger.Noticef("try-%s snap %q does not exist", typeString, trySnap.Filename())
   661  		return curSnap, nil, errTrySnapFallback
   662  	}
   663  
   664  	// we have a try snap and everything appears in order
   665  	return trySnap, curSnap, nil
   666  }
   667  
   668  //
   669  // non snap boot resources
   670  //
   671  
   672  // bootState20BootAssets implements the successfulBootState interface for trusted
   673  // boot assets UC20.
   674  type bootState20BootAssets struct {
   675  	dev Device
   676  }
   677  
   678  func (ba20 *bootState20BootAssets) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
   679  	u20, err := toBootStateUpdate20(update)
   680  	if err != nil {
   681  		return nil, err
   682  	}
   683  
   684  	if len(u20.modeenv.CurrentTrustedBootAssets) == 0 && len(u20.modeenv.CurrentTrustedRecoveryBootAssets) == 0 {
   685  		// not using trusted boot assets, nothing more to do
   686  		return update, nil
   687  	}
   688  
   689  	newM, dropAssets, err := observeSuccessfulBootAssets(u20.writeModeenv)
   690  	if err != nil {
   691  		return nil, fmt.Errorf("cannot mark successful boot assets: %v", err)
   692  	}
   693  	// update modeenv
   694  	u20.writeModeenv = newM
   695  	// keep track of the model for resealing
   696  	u20.resealForModel(ba20.dev.Model())
   697  
   698  	if len(dropAssets) == 0 {
   699  		// nothing to drop, we're done
   700  		return u20, nil
   701  	}
   702  
   703  	u20.postModeenv(func() error {
   704  		cache := newTrustedAssetsCache(dirs.SnapBootAssetsDir)
   705  		// drop listed assets from cache
   706  		for _, ta := range dropAssets {
   707  			err := cache.Remove(ta.blName, ta.name, ta.hash)
   708  			if err != nil {
   709  				// XXX: should this be a log instead?
   710  				return fmt.Errorf("cannot remove unused boot asset %v:%v: %v", ta.name, ta.hash, err)
   711  			}
   712  		}
   713  		return nil
   714  	})
   715  	return u20, nil
   716  }
   717  
   718  func trustedAssetsBootState(dev Device) *bootState20BootAssets {
   719  	return &bootState20BootAssets{
   720  		dev: dev,
   721  	}
   722  }
   723  
   724  // bootState20CommandLine implements the successfulBootState interface for
   725  // kernel command line
   726  type bootState20CommandLine struct {
   727  	dev Device
   728  }
   729  
   730  func (bcl20 *bootState20CommandLine) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
   731  	u20, err := toBootStateUpdate20(update)
   732  	if err != nil {
   733  		return nil, err
   734  	}
   735  	if len(u20.modeenv.CurrentTrustedBootAssets) == 0 && len(u20.modeenv.CurrentTrustedRecoveryBootAssets) == 0 {
   736  		// XXX: does this change when we expose the ability to add
   737  		// things to the command line?
   738  
   739  		// not using trusted boot assets, bootloader config is not
   740  		// managed and command line can be manipulated externally
   741  		return update, nil
   742  	}
   743  
   744  	newM, err := observeSuccessfulCommandLine(bcl20.dev.Model(), u20.writeModeenv)
   745  	if err != nil {
   746  		return nil, fmt.Errorf("cannot mark successful boot command line: %v", err)
   747  	}
   748  	u20.writeModeenv = newM
   749  	return u20, nil
   750  }
   751  
   752  func trustedCommandLineBootState(dev Device) *bootState20CommandLine {
   753  	return &bootState20CommandLine{
   754  		dev: dev,
   755  	}
   756  }
   757  
   758  // bootState20RecoverySystem implements the successfulBootState interface for
   759  // tried recovery systems
   760  type bootState20RecoverySystem struct {
   761  	dev Device
   762  }
   763  
   764  func (brs20 *bootState20RecoverySystem) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
   765  	u20, err := toBootStateUpdate20(update)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  
   770  	newM, err := observeSuccessfulSystems(brs20.dev.Model(), u20.writeModeenv)
   771  	if err != nil {
   772  		return nil, fmt.Errorf("cannot mark successful recovery system: %v", err)
   773  	}
   774  	u20.writeModeenv = newM
   775  	return u20, nil
   776  }
   777  
   778  func recoverySystemsBootState(dev Device) *bootState20RecoverySystem {
   779  	return &bootState20RecoverySystem{dev: dev}
   780  }