github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/devicestate/devicemgr.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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 devicestate
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"os"
    28  	"path/filepath"
    29  	"regexp"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/asserts/sysdb"
    35  	"github.com/snapcore/snapd/boot"
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/i18n"
    38  	"github.com/snapcore/snapd/logger"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/overlord/assertstate"
    41  	"github.com/snapcore/snapd/overlord/auth"
    42  	"github.com/snapcore/snapd/overlord/configstate/config"
    43  	"github.com/snapcore/snapd/overlord/devicestate/fde"
    44  	"github.com/snapcore/snapd/overlord/devicestate/internal"
    45  	"github.com/snapcore/snapd/overlord/hookstate"
    46  	"github.com/snapcore/snapd/overlord/snapstate"
    47  	"github.com/snapcore/snapd/overlord/state"
    48  	"github.com/snapcore/snapd/overlord/storecontext"
    49  	"github.com/snapcore/snapd/progress"
    50  	"github.com/snapcore/snapd/release"
    51  	"github.com/snapcore/snapd/snap"
    52  	"github.com/snapcore/snapd/snapdenv"
    53  	"github.com/snapcore/snapd/sysconfig"
    54  	"github.com/snapcore/snapd/systemd"
    55  	"github.com/snapcore/snapd/timings"
    56  )
    57  
    58  var (
    59  	cloudInitStatus   = sysconfig.CloudInitStatus
    60  	restrictCloudInit = sysconfig.RestrictCloudInit
    61  )
    62  
    63  // DeviceManager is responsible for managing the device identity and device
    64  // policies.
    65  type DeviceManager struct {
    66  	systemMode string
    67  	// saveAvailable keeps track whether /var/lib/snapd/save
    68  	// is available, i.e. exists and is mounted from ubuntu-save
    69  	// if the latter exists.
    70  	// TODO: evolve this to state to track things if we start mounting
    71  	// save as rw vs ro, or mount/umount it fully on demand
    72  	saveAvailable bool
    73  
    74  	state   *state.State
    75  	hookMgr *hookstate.HookManager
    76  
    77  	cachedKeypairMgr asserts.KeypairManager
    78  
    79  	// newStore can make new stores for remodeling
    80  	newStore func(storecontext.DeviceBackend) snapstate.StoreService
    81  
    82  	bootOkRan            bool
    83  	bootRevisionsUpdated bool
    84  
    85  	ensureSeedInConfigRan bool
    86  
    87  	ensureInstalledRan bool
    88  
    89  	cloudInitAlreadyRestricted           bool
    90  	cloudInitErrorAttemptStart           *time.Time
    91  	cloudInitEnabledInactiveAttemptStart *time.Time
    92  
    93  	lastBecomeOperationalAttempt time.Time
    94  	becomeOperationalBackoff     time.Duration
    95  	registered                   bool
    96  	reg                          chan struct{}
    97  
    98  	preseed bool
    99  }
   100  
   101  // Manager returns a new device manager.
   102  func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, newStore func(storecontext.DeviceBackend) snapstate.StoreService) (*DeviceManager, error) {
   103  	delayedCrossMgrInit()
   104  
   105  	m := &DeviceManager{
   106  		state:    s,
   107  		hookMgr:  hookManager,
   108  		newStore: newStore,
   109  		reg:      make(chan struct{}),
   110  		preseed:  snapdenv.Preseeding(),
   111  	}
   112  
   113  	modeEnv, err := maybeReadModeenv()
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	if modeEnv != nil {
   118  		m.systemMode = modeEnv.Mode
   119  	}
   120  
   121  	s.Lock()
   122  	s.Cache(deviceMgrKey{}, m)
   123  	s.Unlock()
   124  
   125  	if err := m.confirmRegistered(); err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	hookManager.Register(regexp.MustCompile("^prepare-device$"), newPrepareDeviceHandler)
   130  
   131  	runner.AddHandler("generate-device-key", m.doGenerateDeviceKey, nil)
   132  	runner.AddHandler("request-serial", m.doRequestSerial, nil)
   133  	runner.AddHandler("mark-preseeded", m.doMarkPreseeded, nil)
   134  	runner.AddHandler("mark-seeded", m.doMarkSeeded, nil)
   135  	runner.AddHandler("setup-run-system", m.doSetupRunSystem, nil)
   136  	runner.AddHandler("prepare-remodeling", m.doPrepareRemodeling, nil)
   137  	runner.AddCleanup("prepare-remodeling", m.cleanupRemodel)
   138  	// this *must* always run last and finalizes a remodel
   139  	runner.AddHandler("set-model", m.doSetModel, nil)
   140  	runner.AddCleanup("set-model", m.cleanupRemodel)
   141  	// There is no undo for successful gadget updates. The system is
   142  	// rebooted during update, if it boots up to the point where snapd runs
   143  	// we deem the new assets (be it bootloader or firmware) functional. The
   144  	// deployed boot assets must be backward compatible with reverted kernel
   145  	// or gadget snaps. There are no further changes to the boot assets,
   146  	// unless a new gadget update is deployed.
   147  	runner.AddHandler("update-gadget-assets", m.doUpdateGadgetAssets, nil)
   148  
   149  	runner.AddBlocked(gadgetUpdateBlocked)
   150  
   151  	// wire FDE kernel hook support into boot
   152  	boot.HasFDESetupHook = m.hasFDESetupHook
   153  	boot.RunFDESetupHook = m.runFDESetupHook
   154  	hookManager.Register(regexp.MustCompile("^fde-setup$"), newFdeSetupHandler)
   155  
   156  	return m, nil
   157  }
   158  
   159  func maybeReadModeenv() (*boot.Modeenv, error) {
   160  	modeEnv, err := boot.ReadModeenv("")
   161  	if err != nil && !os.IsNotExist(err) {
   162  		return nil, fmt.Errorf("cannot read modeenv: %v", err)
   163  	}
   164  	return modeEnv, nil
   165  }
   166  
   167  // StartUp implements StateStarterUp.Startup.
   168  func (m *DeviceManager) StartUp() error {
   169  	// system mode is explicitly set on UC20
   170  	// TODO:UC20: ubuntu-save needs to be mounted for recover too
   171  	if !release.OnClassic && m.systemMode == "run" {
   172  		if err := m.maybeSetupUbuntuSave(); err != nil {
   173  			return fmt.Errorf("cannot set up ubuntu-save: %v", err)
   174  		}
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func (m *DeviceManager) maybeSetupUbuntuSave() error {
   181  	// only called for UC20
   182  
   183  	saveMounted, err := osutil.IsMounted(dirs.SnapSaveDir)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	if saveMounted {
   188  		logger.Noticef("save already mounted under %v", dirs.SnapSaveDir)
   189  		m.saveAvailable = true
   190  		return nil
   191  	}
   192  
   193  	runMntSaveMounted, err := osutil.IsMounted(boot.InitramfsUbuntuSaveDir)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	if !runMntSaveMounted {
   198  		// we don't have ubuntu-save, save will be used directly
   199  		logger.Noticef("no ubuntu-save mount")
   200  		m.saveAvailable = true
   201  		return nil
   202  	}
   203  
   204  	logger.Noticef("bind-mounting ubuntu-save under %v", dirs.SnapSaveDir)
   205  
   206  	err = systemd.New(systemd.SystemMode, progress.Null).Mount(boot.InitramfsUbuntuSaveDir,
   207  		dirs.SnapSaveDir, "-o", "bind")
   208  	if err != nil {
   209  		logger.Noticef("bind-mounting ubuntu-save failed %v", err)
   210  		return fmt.Errorf("cannot bind mount %v under %v: %v", boot.InitramfsUbuntuSaveDir, dirs.SnapSaveDir, err)
   211  	}
   212  	m.saveAvailable = true
   213  	return nil
   214  }
   215  
   216  type deviceMgrKey struct{}
   217  
   218  func deviceMgr(st *state.State) *DeviceManager {
   219  	mgr := st.Cached(deviceMgrKey{})
   220  	if mgr == nil {
   221  		panic("internal error: device manager is not yet associated with state")
   222  	}
   223  	return mgr.(*DeviceManager)
   224  }
   225  
   226  func (m *DeviceManager) CanStandby() bool {
   227  	var seeded bool
   228  	if err := m.state.Get("seeded", &seeded); err != nil {
   229  		return false
   230  	}
   231  	return seeded
   232  }
   233  
   234  func (m *DeviceManager) confirmRegistered() error {
   235  	m.state.Lock()
   236  	defer m.state.Unlock()
   237  
   238  	device, err := m.device()
   239  	if err != nil {
   240  		return err
   241  	}
   242  
   243  	if device.Serial != "" {
   244  		m.markRegistered()
   245  	}
   246  	return nil
   247  }
   248  
   249  func (m *DeviceManager) markRegistered() {
   250  	if m.registered {
   251  		return
   252  	}
   253  	m.registered = true
   254  	close(m.reg)
   255  }
   256  
   257  func gadgetUpdateBlocked(cand *state.Task, running []*state.Task) bool {
   258  	if cand.Kind() == "update-gadget-assets" && len(running) != 0 {
   259  		// update-gadget-assets must be the only task running
   260  		return true
   261  	} else {
   262  		for _, other := range running {
   263  			if other.Kind() == "update-gadget-assets" {
   264  				// no other task can be started when
   265  				// update-gadget-assets is running
   266  				return true
   267  			}
   268  		}
   269  	}
   270  
   271  	return false
   272  }
   273  
   274  type prepareDeviceHandler struct{}
   275  
   276  func newPrepareDeviceHandler(context *hookstate.Context) hookstate.Handler {
   277  	return prepareDeviceHandler{}
   278  }
   279  
   280  func (h prepareDeviceHandler) Before() error {
   281  	return nil
   282  }
   283  
   284  func (h prepareDeviceHandler) Done() error {
   285  	return nil
   286  }
   287  
   288  func (h prepareDeviceHandler) Error(err error) error {
   289  	return nil
   290  }
   291  
   292  func (m *DeviceManager) changeInFlight(kind string) bool {
   293  	for _, chg := range m.state.Changes() {
   294  		if chg.Kind() == kind && !chg.Status().Ready() {
   295  			// change already in motion
   296  			return true
   297  		}
   298  	}
   299  	return false
   300  }
   301  
   302  // helpers to keep count of attempts to get a serial, useful to decide
   303  // to give up holding off trying to auto-refresh
   304  
   305  type ensureOperationalAttemptsKey struct{}
   306  
   307  func incEnsureOperationalAttempts(st *state.State) {
   308  	cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int)
   309  	st.Cache(ensureOperationalAttemptsKey{}, cur+1)
   310  }
   311  
   312  func ensureOperationalAttempts(st *state.State) int {
   313  	cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int)
   314  	return cur
   315  }
   316  
   317  // ensureOperationalShouldBackoff returns whether we should abstain from
   318  // further become-operational tentatives while its backoff interval is
   319  // not expired.
   320  func (m *DeviceManager) ensureOperationalShouldBackoff(now time.Time) bool {
   321  	if !m.lastBecomeOperationalAttempt.IsZero() && m.lastBecomeOperationalAttempt.Add(m.becomeOperationalBackoff).After(now) {
   322  		return true
   323  	}
   324  	if m.becomeOperationalBackoff == 0 {
   325  		m.becomeOperationalBackoff = 5 * time.Minute
   326  	} else {
   327  		newBackoff := m.becomeOperationalBackoff * 2
   328  		if newBackoff > (12 * time.Hour) {
   329  			newBackoff = 24 * time.Hour
   330  		}
   331  		m.becomeOperationalBackoff = newBackoff
   332  	}
   333  	m.lastBecomeOperationalAttempt = now
   334  	return false
   335  }
   336  
   337  func setClassicFallbackModel(st *state.State, device *auth.DeviceState) error {
   338  	err := assertstate.Add(st, sysdb.GenericClassicModel())
   339  	if err != nil && !asserts.IsUnaccceptedUpdate(err) {
   340  		return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err)
   341  	}
   342  	device.Brand = "generic"
   343  	device.Model = "generic-classic"
   344  	if err := internal.SetDevice(st, device); err != nil {
   345  		return err
   346  	}
   347  	return nil
   348  }
   349  
   350  func (m *DeviceManager) SystemMode() string {
   351  	if m.systemMode == "" {
   352  		return "run"
   353  	}
   354  	return m.systemMode
   355  }
   356  
   357  func (m *DeviceManager) ensureOperational() error {
   358  	m.state.Lock()
   359  	defer m.state.Unlock()
   360  
   361  	if m.SystemMode() != "run" {
   362  		// avoid doing registration in ephemeral mode
   363  		// note: this also stop auto-refreshes indirectly
   364  		return nil
   365  	}
   366  
   367  	device, err := m.device()
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	if device.Serial != "" {
   373  		// serial is set, we are all set
   374  		return nil
   375  	}
   376  
   377  	perfTimings := timings.New(map[string]string{"ensure": "become-operational"})
   378  
   379  	// conditions to trigger device registration
   380  	//
   381  	// * have a model assertion with a gadget (core and
   382  	//   device-like classic) in which case we need also to wait
   383  	//   for the gadget to have been installed though
   384  	// TODO: consider a way to support lazy registration on classic
   385  	// even with a gadget and some preseeded snaps
   386  	//
   387  	// * classic with a model assertion with a non-default store specified
   388  	// * lazy classic case (might have a model with no gadget nor store
   389  	//   or no model): we wait to have some snaps installed or be
   390  	//   in the process to install some
   391  
   392  	var seeded bool
   393  	err = m.state.Get("seeded", &seeded)
   394  	if err != nil && err != state.ErrNoState {
   395  		return err
   396  	}
   397  
   398  	if device.Brand == "" || device.Model == "" {
   399  		if !release.OnClassic || !seeded {
   400  			return nil
   401  		}
   402  		// we are on classic and seeded but there is no model:
   403  		// use a fallback model!
   404  		err := setClassicFallbackModel(m.state, device)
   405  		if err != nil {
   406  			return err
   407  		}
   408  	}
   409  
   410  	if m.changeInFlight("become-operational") {
   411  		return nil
   412  	}
   413  
   414  	var storeID, gadget string
   415  	model, err := m.Model()
   416  	if err != nil && err != state.ErrNoState {
   417  		return err
   418  	}
   419  	if err == nil {
   420  		gadget = model.Gadget()
   421  		storeID = model.Store()
   422  	} else {
   423  		return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion")
   424  	}
   425  
   426  	if gadget == "" && storeID == "" {
   427  		// classic: if we have no gadget and no non-default store
   428  		// wait to have snaps or snap installation
   429  
   430  		n, err := snapstate.NumSnaps(m.state)
   431  		if err != nil {
   432  			return err
   433  		}
   434  		if n == 0 && !snapstate.Installing(m.state) {
   435  			return nil
   436  		}
   437  	}
   438  
   439  	var hasPrepareDeviceHook bool
   440  	// if there's a gadget specified wait for it
   441  	if gadget != "" {
   442  		// if have a gadget wait until seeded to proceed
   443  		if !seeded {
   444  			// this will be run again, so eventually when the system is
   445  			// seeded the code below runs
   446  			return nil
   447  
   448  		}
   449  
   450  		gadgetInfo, err := snapstate.CurrentInfo(m.state, gadget)
   451  		if err != nil {
   452  			return err
   453  		}
   454  		hasPrepareDeviceHook = (gadgetInfo.Hooks["prepare-device"] != nil)
   455  	}
   456  
   457  	// have some backoff between full retries
   458  	if m.ensureOperationalShouldBackoff(time.Now()) {
   459  		return nil
   460  	}
   461  	// increment attempt count
   462  	incEnsureOperationalAttempts(m.state)
   463  
   464  	// XXX: some of these will need to be split and use hooks
   465  	// retries might need to embrace more than one "task" then,
   466  	// need to be careful
   467  
   468  	tasks := []*state.Task{}
   469  
   470  	var prepareDevice *state.Task
   471  	if hasPrepareDeviceHook {
   472  		summary := i18n.G("Run prepare-device hook")
   473  		hooksup := &hookstate.HookSetup{
   474  			Snap: gadget,
   475  			Hook: "prepare-device",
   476  		}
   477  		prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil)
   478  		tasks = append(tasks, prepareDevice)
   479  		// hooks are under a different manager, make sure we consider
   480  		// it immediately
   481  		m.state.EnsureBefore(0)
   482  	}
   483  
   484  	genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key"))
   485  	if prepareDevice != nil {
   486  		genKey.WaitFor(prepareDevice)
   487  	}
   488  	tasks = append(tasks, genKey)
   489  	requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial"))
   490  	requestSerial.WaitFor(genKey)
   491  	tasks = append(tasks, requestSerial)
   492  
   493  	chg := m.state.NewChange("become-operational", i18n.G("Initialize device"))
   494  	chg.AddAll(state.NewTaskSet(tasks...))
   495  
   496  	state.TagTimingsWithChange(perfTimings, chg)
   497  	perfTimings.Save(m.state)
   498  
   499  	return nil
   500  }
   501  
   502  var startTime time.Time
   503  
   504  func init() {
   505  	startTime = time.Now()
   506  }
   507  
   508  func (m *DeviceManager) setTimeOnce(name string, t time.Time) error {
   509  	var prev time.Time
   510  	err := m.state.Get(name, &prev)
   511  	if err != nil && err != state.ErrNoState {
   512  		return err
   513  	}
   514  	if !prev.IsZero() {
   515  		// already set
   516  		return nil
   517  	}
   518  	m.state.Set(name, t)
   519  	return nil
   520  }
   521  
   522  var populateStateFromSeed = populateStateFromSeedImpl
   523  
   524  // ensureSeeded makes sure that the snaps from seed.yaml get installed
   525  // with the matching assertions
   526  func (m *DeviceManager) ensureSeeded() error {
   527  	m.state.Lock()
   528  	defer m.state.Unlock()
   529  
   530  	var seeded bool
   531  	err := m.state.Get("seeded", &seeded)
   532  	if err != nil && err != state.ErrNoState {
   533  		return err
   534  	}
   535  	if seeded {
   536  		return nil
   537  	}
   538  
   539  	perfTimings := timings.New(map[string]string{"ensure": "seed"})
   540  
   541  	if m.changeInFlight("seed") {
   542  		return nil
   543  	}
   544  
   545  	var recordedStart string
   546  	var start time.Time
   547  	if m.preseed {
   548  		recordedStart = "preseed-start-time"
   549  		start = timeNow()
   550  	} else {
   551  		recordedStart = "seed-start-time"
   552  		start = startTime
   553  	}
   554  	if err := m.setTimeOnce(recordedStart, start); err != nil {
   555  		return err
   556  	}
   557  
   558  	var opts *populateStateFromSeedOptions
   559  	if m.preseed {
   560  		opts = &populateStateFromSeedOptions{Preseed: true}
   561  	} else {
   562  		modeEnv, err := maybeReadModeenv()
   563  		if err != nil {
   564  			return err
   565  		}
   566  		if modeEnv != nil {
   567  			opts = &populateStateFromSeedOptions{
   568  				Mode:  m.systemMode,
   569  				Label: modeEnv.RecoverySystem,
   570  			}
   571  		}
   572  	}
   573  
   574  	var tsAll []*state.TaskSet
   575  	timings.Run(perfTimings, "state-from-seed", "populate state from seed", func(tm timings.Measurer) {
   576  		tsAll, err = populateStateFromSeed(m.state, opts, tm)
   577  	})
   578  	if err != nil {
   579  		return err
   580  	}
   581  	if len(tsAll) == 0 {
   582  		return nil
   583  	}
   584  
   585  	chg := m.state.NewChange("seed", "Initialize system state")
   586  	for _, ts := range tsAll {
   587  		chg.AddAll(ts)
   588  	}
   589  	m.state.EnsureBefore(0)
   590  
   591  	state.TagTimingsWithChange(perfTimings, chg)
   592  	perfTimings.Save(m.state)
   593  	return nil
   594  }
   595  
   596  // ResetBootOk is only useful for integration testing
   597  func (m *DeviceManager) ResetBootOk() {
   598  	m.bootOkRan = false
   599  	m.bootRevisionsUpdated = false
   600  }
   601  
   602  func (m *DeviceManager) ensureBootOk() error {
   603  	m.state.Lock()
   604  	defer m.state.Unlock()
   605  
   606  	if release.OnClassic {
   607  		return nil
   608  	}
   609  
   610  	// boot-ok/update-boot-revision is only relevant in run-mode
   611  	if m.SystemMode() != "run" {
   612  		return nil
   613  	}
   614  
   615  	if !m.bootOkRan {
   616  		deviceCtx, err := DeviceCtx(m.state, nil, nil)
   617  		if err != nil && err != state.ErrNoState {
   618  			return err
   619  		}
   620  		if err == nil {
   621  			if err := boot.MarkBootSuccessful(deviceCtx); err != nil {
   622  				return err
   623  			}
   624  		}
   625  		m.bootOkRan = true
   626  	}
   627  
   628  	if !m.bootRevisionsUpdated {
   629  		if err := snapstate.UpdateBootRevisions(m.state); err != nil {
   630  			return err
   631  		}
   632  		m.bootRevisionsUpdated = true
   633  	}
   634  
   635  	return nil
   636  }
   637  
   638  func (m *DeviceManager) ensureCloudInitRestricted() error {
   639  	m.state.Lock()
   640  	defer m.state.Unlock()
   641  
   642  	if m.cloudInitAlreadyRestricted {
   643  		return nil
   644  	}
   645  
   646  	var seeded bool
   647  	err := m.state.Get("seeded", &seeded)
   648  	if err != nil && err != state.ErrNoState {
   649  		return err
   650  	}
   651  
   652  	// On Ubuntu Core devices that have been seeded, we want to restrict
   653  	// cloud-init so that its more dangerous (for an IoT device at least)
   654  	// features are not exploitable after a device has been seeded. This allows
   655  	// device administrators and other tools (such as multipass) to still
   656  	// configure an Ubuntu Core device on first boot, and also allows cloud
   657  	// vendors to run cloud-init with only a specific data-source on subsequent
   658  	// boots but disallows arbitrary cloud-init {user,meta,vendor}-data to be
   659  	// attached to a device via a USB drive and inject code onto the device.
   660  
   661  	if seeded && !release.OnClassic {
   662  		opts := &sysconfig.CloudInitRestrictOptions{}
   663  
   664  		// check the current state of cloud-init, if it is disabled or already
   665  		// restricted then we have nothing to do
   666  		cloudInitStatus, err := cloudInitStatus()
   667  		if err != nil {
   668  			return err
   669  		}
   670  		statusMsg := ""
   671  
   672  		switch cloudInitStatus {
   673  		case sysconfig.CloudInitDisabledPermanently, sysconfig.CloudInitRestrictedBySnapd:
   674  			// already been permanently disabled, nothing to do
   675  			m.cloudInitAlreadyRestricted = true
   676  			return nil
   677  		case sysconfig.CloudInitUntriggered:
   678  			// hasn't been used
   679  			statusMsg = "reported to be in disabled state"
   680  		case sysconfig.CloudInitDone:
   681  			// is done being used
   682  			statusMsg = "reported to be done"
   683  		case sysconfig.CloudInitErrored:
   684  			// cloud-init errored, so we give the device admin / developer a few
   685  			// minutes to reboot the machine to re-run cloud-init and try again,
   686  			// otherwise we will disable cloud-init permanently
   687  
   688  			// initialize the time we first saw cloud-init in error state
   689  			if m.cloudInitErrorAttemptStart == nil {
   690  				// save the time we started the attempt to restrict
   691  				now := timeNow()
   692  				m.cloudInitErrorAttemptStart = &now
   693  				logger.Noticef("System initialized, cloud-init reported to be in error state, will disable in 3 minutes")
   694  			}
   695  
   696  			// check if 3 minutes have elapsed since we first saw cloud-init in
   697  			// error state
   698  			timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitErrorAttemptStart)
   699  			if timeSinceFirstAttempt <= 3*time.Minute {
   700  				// we need to keep waiting for cloud-init, up to 3 minutes
   701  				nextCheck := 3*time.Minute - timeSinceFirstAttempt
   702  				m.state.EnsureBefore(nextCheck)
   703  				return nil
   704  			}
   705  			// otherwise, we timed out waiting for cloud-init to be fixed or
   706  			// rebooted and should restrict cloud-init
   707  			// we will restrict cloud-init below, but we need to force the
   708  			// disable, as by default RestrictCloudInit will error on state
   709  			// CloudInitErrored
   710  			opts.ForceDisable = true
   711  			statusMsg = "reported to be in error state after 3 minutes"
   712  		default:
   713  			// in unknown states we are conservative and let the device run for
   714  			// a while to see if it transitions to a known state, but eventually
   715  			// will disable anyways
   716  			fallthrough
   717  		case sysconfig.CloudInitEnabled:
   718  			// we will give cloud-init up to 5 minutes to try and run, if it
   719  			// still has not transitioned to some other known state, then we
   720  			// will give up waiting for it and disable it anyways
   721  
   722  			// initialize the first time we saw cloud-init in enabled state
   723  			if m.cloudInitEnabledInactiveAttemptStart == nil {
   724  				// save the time we started the attempt to restrict
   725  				now := timeNow()
   726  				m.cloudInitEnabledInactiveAttemptStart = &now
   727  			}
   728  
   729  			// keep re-scheduling again in 10 seconds until we hit 5 minutes
   730  			timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitEnabledInactiveAttemptStart)
   731  			if timeSinceFirstAttempt <= 5*time.Minute {
   732  				// TODO: should we log a message here about waiting for cloud-init
   733  				//       to be in a "known state"?
   734  				m.state.EnsureBefore(10 * time.Second)
   735  				return nil
   736  			}
   737  
   738  			// otherwise, we gave cloud-init 5 minutes to run, if it's still not
   739  			// done disable it anyways
   740  			// note we we need to force the disable, as by default
   741  			// RestrictCloudInit will error on state CloudInitEnabled
   742  			opts.ForceDisable = true
   743  			statusMsg = "failed to transition to done or error state after 5 minutes"
   744  		}
   745  
   746  		// we should always have a model if we are seeded and are not on classic
   747  		model, err := m.Model()
   748  		if err != nil {
   749  			return err
   750  		}
   751  
   752  		// For UC20, we want to always disable cloud-init after it has run on
   753  		// first boot unless we are in a "real cloud", i.e. not using NoCloud,
   754  		// or if we installed cloud-init configuration from the gadget
   755  		if model.Grade() != asserts.ModelGradeUnset {
   756  			// always disable NoCloud/local datasources after first boot on
   757  			// uc20, this is because even if the gadget has a cloud.conf
   758  			// configuring NoCloud, the config installed by cloud-init should
   759  			// not work differently for later boots, so it's sufficient that
   760  			// NoCloud runs on first-boot and never again
   761  			opts.DisableAfterLocalDatasourcesRun = true
   762  		}
   763  
   764  		// now restrict/disable cloud-init
   765  		res, err := restrictCloudInit(cloudInitStatus, opts)
   766  		if err != nil {
   767  			return err
   768  		}
   769  
   770  		// log a message about what we did
   771  		actionMsg := ""
   772  		switch res.Action {
   773  		case "disable":
   774  			actionMsg = "disabled permanently"
   775  		case "restrict":
   776  			// log different messages depending on what datasource was used
   777  			if res.DataSource == "NoCloud" {
   778  				actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label"
   779  			} else {
   780  				// all other datasources just log that we limited it to that datasource
   781  				actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource)
   782  			}
   783  		default:
   784  			return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action)
   785  		}
   786  		logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg)
   787  
   788  		m.cloudInitAlreadyRestricted = true
   789  	}
   790  
   791  	return nil
   792  }
   793  
   794  func (m *DeviceManager) ensureInstalled() error {
   795  	m.state.Lock()
   796  	defer m.state.Unlock()
   797  
   798  	if release.OnClassic {
   799  		return nil
   800  	}
   801  
   802  	if m.ensureInstalledRan {
   803  		return nil
   804  	}
   805  
   806  	if m.SystemMode() != "install" {
   807  		return nil
   808  	}
   809  
   810  	var seeded bool
   811  	err := m.state.Get("seeded", &seeded)
   812  	if err != nil && err != state.ErrNoState {
   813  		return err
   814  	}
   815  	if !seeded {
   816  		return nil
   817  	}
   818  
   819  	if m.changeInFlight("install-system") {
   820  		return nil
   821  	}
   822  
   823  	m.ensureInstalledRan = true
   824  
   825  	tasks := []*state.Task{}
   826  	setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode"))
   827  	tasks = append(tasks, setupRunSystem)
   828  
   829  	chg := m.state.NewChange("install-system", i18n.G("Install the system"))
   830  	chg.AddAll(state.NewTaskSet(tasks...))
   831  
   832  	return nil
   833  }
   834  
   835  var timeNow = time.Now
   836  
   837  // StartOfOperationTime returns the time when snapd started operating,
   838  // and sets it in the state when called for the first time.
   839  // The StartOfOperationTime time is seed-time if available,
   840  // or current time otherwise.
   841  func (m *DeviceManager) StartOfOperationTime() (time.Time, error) {
   842  	var opTime time.Time
   843  	if m.preseed {
   844  		return opTime, fmt.Errorf("internal error: unexpected call to StartOfOperationTime in preseed mode")
   845  	}
   846  	err := m.state.Get("start-of-operation-time", &opTime)
   847  	if err == nil {
   848  		return opTime, nil
   849  	}
   850  	if err != nil && err != state.ErrNoState {
   851  		return opTime, err
   852  	}
   853  
   854  	// start-of-operation-time not set yet, use seed-time if available
   855  	var seedTime time.Time
   856  	err = m.state.Get("seed-time", &seedTime)
   857  	if err != nil && err != state.ErrNoState {
   858  		return opTime, err
   859  	}
   860  	if err == nil {
   861  		opTime = seedTime
   862  	} else {
   863  		opTime = timeNow()
   864  	}
   865  	m.state.Set("start-of-operation-time", opTime)
   866  	return opTime, nil
   867  }
   868  
   869  func markSeededInConfig(st *state.State) error {
   870  	var seedDone bool
   871  	tr := config.NewTransaction(st)
   872  	if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) {
   873  		return err
   874  	}
   875  	if !seedDone {
   876  		if err := tr.Set("core", "seed.loaded", true); err != nil {
   877  			return err
   878  		}
   879  		tr.Commit()
   880  	}
   881  	return nil
   882  }
   883  
   884  func (m *DeviceManager) ensureSeedInConfig() error {
   885  	m.state.Lock()
   886  	defer m.state.Unlock()
   887  
   888  	if !m.ensureSeedInConfigRan {
   889  		// get global seeded option
   890  		var seeded bool
   891  		if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState {
   892  			return err
   893  		}
   894  		if !seeded {
   895  			// wait for ensure again, this is fine because
   896  			// doMarkSeeded will run "EnsureBefore(0)"
   897  			return nil
   898  		}
   899  
   900  		// Sync seeding with the configuration state. We need to
   901  		// do this here to ensure that old systems which did not
   902  		// set the configuration on seeding get the configuration
   903  		// update too.
   904  		if err := markSeededInConfig(m.state); err != nil {
   905  			return err
   906  		}
   907  		m.ensureSeedInConfigRan = true
   908  	}
   909  
   910  	return nil
   911  
   912  }
   913  
   914  type ensureError struct {
   915  	errs []error
   916  }
   917  
   918  func (e *ensureError) Error() string {
   919  	if len(e.errs) == 1 {
   920  		return fmt.Sprintf("devicemgr: %v", e.errs[0])
   921  	}
   922  	parts := []string{"devicemgr:"}
   923  	for _, e := range e.errs {
   924  		parts = append(parts, e.Error())
   925  	}
   926  	return strings.Join(parts, "\n - ")
   927  }
   928  
   929  // no \n allowed in warnings
   930  var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.`
   931  
   932  // Ensure implements StateManager.Ensure.
   933  func (m *DeviceManager) Ensure() error {
   934  	var errs []error
   935  
   936  	if err := m.ensureSeeded(); err != nil {
   937  		m.state.Lock()
   938  		m.state.Warnf(seedFailureFmt, err)
   939  		m.state.Unlock()
   940  		errs = append(errs, fmt.Errorf("cannot seed: %v", err))
   941  	}
   942  
   943  	if !m.preseed {
   944  		if err := m.ensureCloudInitRestricted(); err != nil {
   945  			errs = append(errs, err)
   946  		}
   947  
   948  		if err := m.ensureOperational(); err != nil {
   949  			errs = append(errs, err)
   950  		}
   951  
   952  		if err := m.ensureBootOk(); err != nil {
   953  			errs = append(errs, err)
   954  		}
   955  
   956  		if err := m.ensureSeedInConfig(); err != nil {
   957  			errs = append(errs, err)
   958  		}
   959  
   960  		if err := m.ensureInstalled(); err != nil {
   961  			errs = append(errs, err)
   962  		}
   963  	}
   964  
   965  	if len(errs) > 0 {
   966  		return &ensureError{errs}
   967  	}
   968  
   969  	return nil
   970  }
   971  
   972  var errNoSaveSupport = errors.New("no save directory before UC20")
   973  
   974  // withSaveDir invokes a function making sure save dir is available.
   975  // Under UC16/18 it returns errNoSaveSupport
   976  // For UC20 it also checks that ubuntu-save is available/mounted.
   977  func (m *DeviceManager) withSaveDir(f func() error) error {
   978  	// we use the model to check whether this is a UC20 device
   979  	model, err := m.Model()
   980  	if err == state.ErrNoState {
   981  		return fmt.Errorf("internal error: cannot access save dir before a model is set")
   982  	}
   983  	if err != nil {
   984  		return err
   985  	}
   986  	if model.Grade() == asserts.ModelGradeUnset {
   987  		return errNoSaveSupport
   988  	}
   989  	// at this point we need save available
   990  	if !m.saveAvailable {
   991  		return fmt.Errorf("internal error: save dir is unavailable")
   992  	}
   993  
   994  	return f()
   995  }
   996  
   997  // withSaveAssertDB invokes a function making the save device assertion
   998  // backup database available to it.
   999  // Under UC16/18 it returns errNoSaveSupport
  1000  // For UC20 it also checks that ubuntu-save is available/mounted.
  1001  func (m *DeviceManager) withSaveAssertDB(f func(*asserts.Database) error) error {
  1002  	return m.withSaveDir(func() error {
  1003  		// open an ancillary backup assertion database in save/device
  1004  		assertDB, err := sysdb.OpenAt(dirs.SnapDeviceSaveDir)
  1005  		if err != nil {
  1006  			return err
  1007  		}
  1008  		return f(assertDB)
  1009  	})
  1010  }
  1011  
  1012  // withKeypairMgr invokes a function making the device KeypairManager
  1013  // available to it.
  1014  // It uses the right location for the manager depending on UC16/18 vs 20,
  1015  // the latter uses ubuntu-save.
  1016  // For UC20 it also checks that ubuntu-save is available/mounted.
  1017  func (m *DeviceManager) withKeypairMgr(f func(asserts.KeypairManager) error) error {
  1018  	// we use the model to check whether this is a UC20 device
  1019  	// TODO: during a theoretical UC18->20 remodel the location of
  1020  	// keypair manager keys would move, we will need dedicated code
  1021  	// to deal with that, this code typically will return the old location
  1022  	// until a restart
  1023  	model, err := m.Model()
  1024  	if err == state.ErrNoState {
  1025  		return fmt.Errorf("internal error: cannot access device keypair manager before a model is set")
  1026  	}
  1027  	if err != nil {
  1028  		return err
  1029  	}
  1030  	underSave := false
  1031  	if model.Grade() != asserts.ModelGradeUnset {
  1032  		// on UC20 the keys are kept under the save dir
  1033  		underSave = true
  1034  	}
  1035  	where := dirs.SnapDeviceDir
  1036  	if underSave {
  1037  		// at this point we need save available
  1038  		if !m.saveAvailable {
  1039  			return fmt.Errorf("internal error: cannot access device keypair manager if ubuntu-save is unavailable")
  1040  		}
  1041  		where = dirs.SnapDeviceSaveDir
  1042  	}
  1043  	keypairMgr := m.cachedKeypairMgr
  1044  	if keypairMgr == nil {
  1045  		var err error
  1046  		keypairMgr, err = asserts.OpenFSKeypairManager(where)
  1047  		if err != nil {
  1048  			return err
  1049  		}
  1050  		m.cachedKeypairMgr = keypairMgr
  1051  	}
  1052  	return f(keypairMgr)
  1053  }
  1054  
  1055  // TODO:UC20: we need proper encapsulated support to read
  1056  // tpm-policy-auth-key from save if the latter can get unmounted on
  1057  // demand
  1058  
  1059  func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) {
  1060  	device, err := m.device()
  1061  	if err != nil {
  1062  		return nil, err
  1063  	}
  1064  
  1065  	if device.KeyID == "" {
  1066  		return nil, state.ErrNoState
  1067  	}
  1068  
  1069  	var privKey asserts.PrivateKey
  1070  	err = m.withKeypairMgr(func(keypairMgr asserts.KeypairManager) (err error) {
  1071  		privKey, err = keypairMgr.Get(device.KeyID)
  1072  		if err != nil {
  1073  			return fmt.Errorf("cannot read device key pair: %v", err)
  1074  		}
  1075  		return nil
  1076  	})
  1077  	if err != nil {
  1078  		return nil, err
  1079  	}
  1080  	return privKey, nil
  1081  }
  1082  
  1083  // Registered returns a channel that is closed when the device is known to have been registered.
  1084  func (m *DeviceManager) Registered() <-chan struct{} {
  1085  	return m.reg
  1086  }
  1087  
  1088  // device returns current device state.
  1089  func (m *DeviceManager) device() (*auth.DeviceState, error) {
  1090  	return internal.Device(m.state)
  1091  }
  1092  
  1093  // setDevice sets the device details in the state.
  1094  func (m *DeviceManager) setDevice(device *auth.DeviceState) error {
  1095  	return internal.SetDevice(m.state, device)
  1096  }
  1097  
  1098  // Model returns the device model assertion.
  1099  func (m *DeviceManager) Model() (*asserts.Model, error) {
  1100  	return findModel(m.state)
  1101  }
  1102  
  1103  // Serial returns the device serial assertion.
  1104  func (m *DeviceManager) Serial() (*asserts.Serial, error) {
  1105  	return findSerial(m.state, nil)
  1106  }
  1107  
  1108  type SystemAction struct {
  1109  	Title string
  1110  	Mode  string
  1111  }
  1112  
  1113  type System struct {
  1114  	// Current is true when the system running now was installed from that
  1115  	// seed
  1116  	Current bool
  1117  	// Label of the seed system
  1118  	Label string
  1119  	// Model assertion of the system
  1120  	Model *asserts.Model
  1121  	// Brand information
  1122  	Brand *asserts.Account
  1123  	// Actions available for this system
  1124  	Actions []SystemAction
  1125  }
  1126  
  1127  var defaultSystemActions = []SystemAction{
  1128  	{Title: "Install", Mode: "install"},
  1129  }
  1130  var currentSystemActions = []SystemAction{
  1131  	{Title: "Reinstall", Mode: "install"},
  1132  	{Title: "Recover", Mode: "recover"},
  1133  	{Title: "Run normally", Mode: "run"},
  1134  }
  1135  var recoverSystemActions = []SystemAction{
  1136  	{Title: "Reinstall", Mode: "install"},
  1137  	{Title: "Run normally", Mode: "run"},
  1138  }
  1139  
  1140  var ErrNoSystems = errors.New("no systems seeds")
  1141  
  1142  // Systems list the available recovery/seeding systems. Returns the list of
  1143  // systems, ErrNoSystems when no systems seeds were found or other error.
  1144  func (m *DeviceManager) Systems() ([]*System, error) {
  1145  	// it's tough luck when we cannot determine the current system seed
  1146  	systemMode := m.SystemMode()
  1147  	currentSys, _ := currentSystemForMode(m.state, systemMode)
  1148  
  1149  	systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*"))
  1150  	if err != nil && !os.IsNotExist(err) {
  1151  		return nil, fmt.Errorf("cannot list available systems: %v", err)
  1152  	}
  1153  	if len(systemLabels) == 0 {
  1154  		// maybe not a UC20 system
  1155  		return nil, ErrNoSystems
  1156  	}
  1157  
  1158  	var systems []*System
  1159  	for _, fpLabel := range systemLabels {
  1160  		label := filepath.Base(fpLabel)
  1161  		system, err := systemFromSeed(label, currentSys)
  1162  		if err != nil {
  1163  			// TODO:UC20 add a Broken field to the seed system like
  1164  			// we do for snap.Info
  1165  			logger.Noticef("cannot load system %q seed: %v", label, err)
  1166  			continue
  1167  		}
  1168  		systems = append(systems, system)
  1169  	}
  1170  	return systems, nil
  1171  }
  1172  
  1173  var ErrUnsupportedAction = errors.New("unsupported action")
  1174  
  1175  // Reboot triggers a reboot into the given systemLabel and mode.
  1176  //
  1177  // When called without a systemLabel and without a mode it will just
  1178  // trigger a regular reboot.
  1179  //
  1180  // When called without a systemLabel but with a mode it will use
  1181  // the current system to enter the given mode.
  1182  //
  1183  // Note that "recover" and "run" modes are only available for the
  1184  // current system.
  1185  func (m *DeviceManager) Reboot(systemLabel, mode string) error {
  1186  	rebootCurrent := func() {
  1187  		logger.Noticef("rebooting system")
  1188  		m.state.RequestRestart(state.RestartSystemNow)
  1189  	}
  1190  
  1191  	// most simple case: just reboot
  1192  	if systemLabel == "" && mode == "" {
  1193  		m.state.Lock()
  1194  		defer m.state.Unlock()
  1195  
  1196  		rebootCurrent()
  1197  		return nil
  1198  	}
  1199  
  1200  	// no systemLabel means "current" so get the current system label
  1201  	if systemLabel == "" {
  1202  		systemMode := m.SystemMode()
  1203  		currentSys, err := currentSystemForMode(m.state, systemMode)
  1204  		if err != nil {
  1205  			return fmt.Errorf("cannot get current system: %v", err)
  1206  		}
  1207  		systemLabel = currentSys.System
  1208  	}
  1209  
  1210  	switched := func(systemLabel string, sysAction *SystemAction) {
  1211  		logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode)
  1212  		m.state.RequestRestart(state.RestartSystemNow)
  1213  	}
  1214  	// even if we are already in the right mode we restart here by
  1215  	// passing rebootCurrent as this is what the user requested
  1216  	return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched)
  1217  }
  1218  
  1219  // RequestSystemAction requests the provided system to be run in a
  1220  // given mode as specified by action.
  1221  // A system reboot will be requested when the request can be
  1222  // successfully carried out.
  1223  func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error {
  1224  	if systemLabel == "" {
  1225  		return fmt.Errorf("internal error: system label is unset")
  1226  	}
  1227  
  1228  	nop := func() {}
  1229  	switched := func(systemLabel string, sysAction *SystemAction) {
  1230  		logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title)
  1231  		m.state.RequestRestart(state.RestartSystemNow)
  1232  	}
  1233  	// we do nothing (nop) if the mode and system are the same
  1234  	return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched)
  1235  }
  1236  
  1237  // switchToSystemAndMode switches to given systemLabel and mode.
  1238  // If the systemLabel and mode are the same as current, it calls
  1239  // sameSystemAndMode. If successful otherwise it calls switched. Both
  1240  // are called with the state lock held.
  1241  func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error {
  1242  	if err := checkSystemRequestConflict(m.state, systemLabel); err != nil {
  1243  		return err
  1244  	}
  1245  
  1246  	systemMode := m.SystemMode()
  1247  	// ignore the error to be robust in scenarios that
  1248  	// dont' stricly require currentSys to be carried through.
  1249  	// make sure that currentSys == nil does not break
  1250  	// the code below!
  1251  	// TODO: should we log the error?
  1252  	currentSys, _ := currentSystemForMode(m.state, systemMode)
  1253  
  1254  	systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel)
  1255  	if _, err := os.Stat(systemSeedDir); err != nil {
  1256  		// XXX: should we wrap this instead return a naked stat error?
  1257  		return err
  1258  	}
  1259  	system, err := systemFromSeed(systemLabel, currentSys)
  1260  	if err != nil {
  1261  		return fmt.Errorf("cannot load seed system: %v", err)
  1262  	}
  1263  
  1264  	var sysAction *SystemAction
  1265  	for _, act := range system.Actions {
  1266  		if mode == act.Mode {
  1267  			sysAction = &act
  1268  			break
  1269  		}
  1270  	}
  1271  	if sysAction == nil {
  1272  		// XXX: provide more context here like what mode was requested?
  1273  		return ErrUnsupportedAction
  1274  	}
  1275  
  1276  	// XXX: requested mode is valid; only current system has 'run' and
  1277  	// recover 'actions'
  1278  
  1279  	switch systemMode {
  1280  	case "recover", "run":
  1281  		// if going from recover to recover or from run to run and the systems
  1282  		// are the same do nothing
  1283  		if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System {
  1284  			m.state.Lock()
  1285  			defer m.state.Unlock()
  1286  			sameSystemAndMode()
  1287  			return nil
  1288  		}
  1289  	case "install":
  1290  		// requesting system actions in install mode does not make sense atm
  1291  		//
  1292  		// TODO:UC20: maybe factory hooks will be able to something like
  1293  		// this?
  1294  		return ErrUnsupportedAction
  1295  	default:
  1296  		// probably test device manager mocking problem, or also potentially
  1297  		// missing modeenv
  1298  		return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode)
  1299  	}
  1300  
  1301  	m.state.Lock()
  1302  	defer m.state.Unlock()
  1303  
  1304  	deviceCtx, err := DeviceCtx(m.state, nil, nil)
  1305  	if err != nil {
  1306  		return err
  1307  	}
  1308  	if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil {
  1309  		return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err)
  1310  	}
  1311  
  1312  	switched(systemLabel, sysAction)
  1313  	return nil
  1314  }
  1315  
  1316  // implement storecontext.Backend
  1317  
  1318  type storeContextBackend struct {
  1319  	*DeviceManager
  1320  }
  1321  
  1322  func (scb storeContextBackend) Device() (*auth.DeviceState, error) {
  1323  	return scb.DeviceManager.device()
  1324  }
  1325  
  1326  func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error {
  1327  	return scb.DeviceManager.setDevice(device)
  1328  }
  1329  
  1330  func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) {
  1331  	st := scb.DeviceManager.state
  1332  	return proxyStore(st, config.NewTransaction(st))
  1333  }
  1334  
  1335  // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce.
  1336  func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) {
  1337  	if serial == nil {
  1338  		// shouldn't happen, but be safe
  1339  		return nil, fmt.Errorf("internal error: cannot sign a session request without a serial")
  1340  	}
  1341  
  1342  	privKey, err := scb.DeviceManager.keyPair()
  1343  	if err == state.ErrNoState {
  1344  		return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key")
  1345  	}
  1346  	if err != nil {
  1347  		return nil, err
  1348  	}
  1349  
  1350  	a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{
  1351  		"brand-id":  serial.BrandID(),
  1352  		"model":     serial.Model(),
  1353  		"serial":    serial.Serial(),
  1354  		"nonce":     nonce,
  1355  		"timestamp": time.Now().UTC().Format(time.RFC3339),
  1356  	}, nil, privKey)
  1357  	if err != nil {
  1358  		return nil, err
  1359  	}
  1360  
  1361  	return a.(*asserts.DeviceSessionRequest), nil
  1362  }
  1363  
  1364  func (m *DeviceManager) StoreContextBackend() storecontext.Backend {
  1365  	return storeContextBackend{m}
  1366  }
  1367  
  1368  func (m *DeviceManager) hasFDESetupHook() (bool, error) {
  1369  	// state must be locked
  1370  	st := m.state
  1371  
  1372  	deviceCtx, err := DeviceCtx(st, nil, nil)
  1373  	if err != nil {
  1374  		return false, fmt.Errorf("cannot get device context: %v", err)
  1375  	}
  1376  
  1377  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
  1378  	if err != nil {
  1379  		return false, fmt.Errorf("cannot get kernel info: %v", err)
  1380  	}
  1381  	return hasFDESetupHookInKernel(kernelInfo), nil
  1382  }
  1383  
  1384  func (m *DeviceManager) runFDESetupHook(op string, params *boot.FDESetupHookParams) ([]byte, error) {
  1385  	// TODO:UC20: when this runs on refresh we need to be very careful
  1386  	// that we never run this when the kernel is not fully configured
  1387  	// i.e. when there are no security profiles for the hook
  1388  
  1389  	// state must be locked
  1390  	st := m.state
  1391  
  1392  	deviceCtx, err := DeviceCtx(st, nil, nil)
  1393  	if err != nil {
  1394  		return nil, fmt.Errorf("cannot get device context to run fde-setup hook: %v", err)
  1395  	}
  1396  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
  1397  	if err != nil {
  1398  		return nil, fmt.Errorf("cannot get kernel info to run fde-setup hook: %v", err)
  1399  	}
  1400  	hooksup := &hookstate.HookSetup{
  1401  		Snap:     kernelInfo.InstanceName(),
  1402  		Revision: kernelInfo.Revision,
  1403  		Hook:     "fde-setup",
  1404  		// XXX: should this be configurable somehow?
  1405  		Timeout: 5 * time.Minute,
  1406  	}
  1407  	req := &fde.SetupRequest{
  1408  		Op:      op,
  1409  		Key:     &params.Key,
  1410  		KeyName: params.KeyName,
  1411  		// TODO: include boot chains
  1412  	}
  1413  	for _, model := range params.Models {
  1414  		req.Models = append(req.Models, map[string]string{
  1415  			"series":     model.Series(),
  1416  			"brand-id":   model.BrandID(),
  1417  			"model":      model.Model(),
  1418  			"grade":      string(model.Grade()),
  1419  			"signkey-id": model.SignKeyID(),
  1420  		})
  1421  	}
  1422  	contextData := map[string]interface{}{
  1423  		"fde-setup-request": req,
  1424  	}
  1425  	st.Unlock()
  1426  	defer st.Lock()
  1427  	context, err := m.hookMgr.EphemeralRunHook(context.Background(), hooksup, contextData)
  1428  	if err != nil {
  1429  		return nil, fmt.Errorf("cannot run hook for %q: %v", op, err)
  1430  	}
  1431  	// the hook is expected to call "snapctl fde-setup-result" which
  1432  	// wil set the "fde-setup-result" value on the task
  1433  	var hookResult []byte
  1434  	context.Lock()
  1435  	err = context.Get("fde-setup-result", &hookResult)
  1436  	context.Unlock()
  1437  	if err != nil {
  1438  		return nil, fmt.Errorf("cannot get result from fde-setup hook %q: %v", op, err)
  1439  	}
  1440  
  1441  	return hookResult, nil
  1442  }
  1443  
  1444  func (m *DeviceManager) checkFDEFeatures(st *state.State) error {
  1445  	// Run fde-setup hook with "op":"features". If the hook
  1446  	// returns any {"features":[...]} reply we consider the
  1447  	// hardware supported. If the hook errors or if it returns
  1448  	// {"error":"hardware-unsupported"} we don't.
  1449  	output, err := m.runFDESetupHook("features", &boot.FDESetupHookParams{})
  1450  	if err != nil {
  1451  		return err
  1452  	}
  1453  	var res struct {
  1454  		Features []string `json:"features"`
  1455  		Error    string   `json:"error"`
  1456  	}
  1457  	if err := json.Unmarshal(output, &res); err != nil {
  1458  		return fmt.Errorf("cannot parse hook output %q: %v", output, err)
  1459  	}
  1460  	if res.Features == nil && res.Error == "" {
  1461  		return fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`)
  1462  	}
  1463  	if res.Error != "" {
  1464  		return fmt.Errorf("cannot use hook: it returned error: %v", res.Error)
  1465  	}
  1466  	return nil
  1467  }
  1468  
  1469  func hasFDESetupHookInKernel(kernelInfo *snap.Info) bool {
  1470  	_, ok := kernelInfo.Hooks["fde-setup"]
  1471  	return ok
  1472  }
  1473  
  1474  type fdeSetupHandler struct {
  1475  	context *hookstate.Context
  1476  }
  1477  
  1478  func newFdeSetupHandler(ctx *hookstate.Context) hookstate.Handler {
  1479  	return fdeSetupHandler{context: ctx}
  1480  }
  1481  
  1482  func (h fdeSetupHandler) Before() error {
  1483  	return nil
  1484  }
  1485  
  1486  func (h fdeSetupHandler) Done() error {
  1487  	return nil
  1488  }
  1489  
  1490  func (h fdeSetupHandler) Error(err error) error {
  1491  	return nil
  1492  }