github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/devicestate/devicemgr.go (about)

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