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