github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/snapmgr.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2017 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 snapstate
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"math/rand"
    28  	"os"
    29  	"strings"
    30  	"time"
    31  
    32  	"gopkg.in/tomb.v2"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/errtracker"
    36  	"github.com/snapcore/snapd/i18n"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/release"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/store"
    44  	"github.com/snapcore/snapd/strutil"
    45  )
    46  
    47  var (
    48  	snapdTransitionDelayWithRandomess = 3*time.Hour + time.Duration(rand.Int63n(int64(4*time.Hour)))
    49  )
    50  
    51  // overridden in the tests
    52  var errtrackerReport = errtracker.Report
    53  
    54  // SnapManager is responsible for the installation and removal of snaps.
    55  type SnapManager struct {
    56  	state   *state.State
    57  	backend managerBackend
    58  
    59  	autoRefresh    *autoRefresh
    60  	refreshHints   *refreshHints
    61  	catalogRefresh *catalogRefresh
    62  
    63  	lastUbuntuCoreTransitionAttempt time.Time
    64  }
    65  
    66  // SnapSetup holds the necessary snap details to perform most snap manager tasks.
    67  type SnapSetup struct {
    68  	// FIXME: rename to RequestedChannel to convey the meaning better
    69  	Channel string    `json:"channel,omitempty"`
    70  	UserID  int       `json:"user-id,omitempty"`
    71  	Base    string    `json:"base,omitempty"`
    72  	Type    snap.Type `json:"type,omitempty"`
    73  	// PlugsOnly indicates whether the relevant revisions for the
    74  	// operation have only plugs (#plugs >= 0), and absolutely no
    75  	// slots (#slots == 0).
    76  	PlugsOnly bool `json:"plugs-only,omitempty"`
    77  
    78  	CohortKey string `json:"cohort-key,omitempty"`
    79  
    80  	// FIXME: implement rename of this as suggested in
    81  	//  https://github.com/snapcore/snapd/pull/4103#discussion_r169569717
    82  	//
    83  	// Prereq is a list of snap-names that need to get installed
    84  	// together with this snap. Typically used when installing
    85  	// content-snaps with default-providers.
    86  	Prereq []string `json:"prereq,omitempty"`
    87  
    88  	Flags
    89  
    90  	SnapPath string `json:"snap-path,omitempty"`
    91  
    92  	DownloadInfo *snap.DownloadInfo `json:"download-info,omitempty"`
    93  	SideInfo     *snap.SideInfo     `json:"side-info,omitempty"`
    94  	auxStoreInfo
    95  
    96  	// InstanceKey is set by the user during installation and differs for
    97  	// each instance of given snap
    98  	InstanceKey string `json:"instance-key,omitempty"`
    99  }
   100  
   101  func (snapsup *SnapSetup) InstanceName() string {
   102  	return snap.InstanceName(snapsup.SnapName(), snapsup.InstanceKey)
   103  }
   104  
   105  func (snapsup *SnapSetup) SnapName() string {
   106  	if snapsup.SideInfo.RealName == "" {
   107  		panic("SnapSetup.SideInfo.RealName not set")
   108  	}
   109  	return snapsup.SideInfo.RealName
   110  }
   111  
   112  func (snapsup *SnapSetup) Revision() snap.Revision {
   113  	return snapsup.SideInfo.Revision
   114  }
   115  
   116  func (snapsup *SnapSetup) placeInfo() snap.PlaceInfo {
   117  	return snap.MinimalPlaceInfo(snapsup.InstanceName(), snapsup.Revision())
   118  }
   119  
   120  func (snapsup *SnapSetup) MountDir() string {
   121  	return snap.MountDir(snapsup.InstanceName(), snapsup.Revision())
   122  }
   123  
   124  func (snapsup *SnapSetup) MountFile() string {
   125  	return snap.MountFile(snapsup.InstanceName(), snapsup.Revision())
   126  }
   127  
   128  // SnapState holds the state for a snap installed in the system.
   129  type SnapState struct {
   130  	SnapType string           `json:"type"` // Use Type and SetType
   131  	Sequence []*snap.SideInfo `json:"sequence"`
   132  	Active   bool             `json:"active,omitempty"`
   133  	// Current indicates the current active revision if Active is
   134  	// true or the last active revision if Active is false
   135  	// (usually while a snap is being operated on or disabled)
   136  	Current snap.Revision `json:"current"`
   137  	Channel string        `json:"channel,omitempty"`
   138  	Flags
   139  	// aliases, see aliasesv2.go
   140  	Aliases             map[string]*AliasTarget `json:"aliases,omitempty"`
   141  	AutoAliasesDisabled bool                    `json:"auto-aliases-disabled,omitempty"`
   142  	AliasesPending      bool                    `json:"aliases-pending,omitempty"`
   143  
   144  	// UserID of the user requesting the install
   145  	UserID int `json:"user-id,omitempty"`
   146  
   147  	// InstanceKey is set by the user during installation and differs for
   148  	// each instance of given snap
   149  	InstanceKey string `json:"instance-key,omitempty"`
   150  	CohortKey   string `json:"cohort-key,omitempty"`
   151  
   152  	// RefreshInhibitedime records the time when the refresh was first
   153  	// attempted but inhibited because the snap was busy. This value is
   154  	// reset on each successful refresh.
   155  	RefreshInhibitedTime *time.Time `json:"refresh-inhibited-time,omitempty"`
   156  }
   157  
   158  // Type returns the type of the snap or an error.
   159  // Should never error if Current is not nil.
   160  func (snapst *SnapState) Type() (snap.Type, error) {
   161  	if snapst.SnapType == "" {
   162  		return snap.Type(""), fmt.Errorf("snap type unset")
   163  	}
   164  	return snap.Type(snapst.SnapType), nil
   165  }
   166  
   167  // SetType records the type of the snap.
   168  func (snapst *SnapState) SetType(typ snap.Type) {
   169  	snapst.SnapType = string(typ)
   170  }
   171  
   172  // IsInstalled returns whether the snap is installed, i.e. snapst represents an installed snap with Current revision set.
   173  func (snapst *SnapState) IsInstalled() bool {
   174  	if snapst.Current.Unset() {
   175  		if len(snapst.Sequence) > 0 {
   176  			panic(fmt.Sprintf("snapst.Current and snapst.Sequence out of sync: %#v %#v", snapst.Current, snapst.Sequence))
   177  		}
   178  
   179  		return false
   180  	}
   181  	return true
   182  }
   183  
   184  // LocalRevision returns the "latest" local revision. Local revisions
   185  // start at -1 and are counted down.
   186  func (snapst *SnapState) LocalRevision() snap.Revision {
   187  	var local snap.Revision
   188  	for _, si := range snapst.Sequence {
   189  		if si.Revision.Local() && si.Revision.N < local.N {
   190  			local = si.Revision
   191  		}
   192  	}
   193  	return local
   194  }
   195  
   196  // CurrentSideInfo returns the side info for the revision indicated by snapst.Current in the snap revision sequence if there is one.
   197  func (snapst *SnapState) CurrentSideInfo() *snap.SideInfo {
   198  	if !snapst.IsInstalled() {
   199  		return nil
   200  	}
   201  	if idx := snapst.LastIndex(snapst.Current); idx >= 0 {
   202  		return snapst.Sequence[idx]
   203  	}
   204  	panic("cannot find snapst.Current in the snapst.Sequence")
   205  }
   206  
   207  func (snapst *SnapState) previousSideInfo() *snap.SideInfo {
   208  	n := len(snapst.Sequence)
   209  	if n < 2 {
   210  		return nil
   211  	}
   212  	// find "current" and return the one before that
   213  	currentIndex := snapst.LastIndex(snapst.Current)
   214  	if currentIndex <= 0 {
   215  		return nil
   216  	}
   217  	return snapst.Sequence[currentIndex-1]
   218  }
   219  
   220  // LastIndex returns the last index of the given revision in the
   221  // snapst.Sequence
   222  func (snapst *SnapState) LastIndex(revision snap.Revision) int {
   223  	for i := len(snapst.Sequence) - 1; i >= 0; i-- {
   224  		if snapst.Sequence[i].Revision == revision {
   225  			return i
   226  		}
   227  	}
   228  	return -1
   229  }
   230  
   231  // Block returns revisions that should be blocked on refreshes,
   232  // computed from Sequence[currentRevisionIndex+1:].
   233  func (snapst *SnapState) Block() []snap.Revision {
   234  	// return revisions from Sequence[currentIndex:]
   235  	currentIndex := snapst.LastIndex(snapst.Current)
   236  	if currentIndex < 0 || currentIndex+1 == len(snapst.Sequence) {
   237  		return nil
   238  	}
   239  	out := make([]snap.Revision, len(snapst.Sequence)-currentIndex-1)
   240  	for i, si := range snapst.Sequence[currentIndex+1:] {
   241  		out[i] = si.Revision
   242  	}
   243  	return out
   244  }
   245  
   246  var ErrNoCurrent = errors.New("snap has no current revision")
   247  
   248  // Retrieval functions
   249  
   250  const (
   251  	errorOnBroken = 1 << iota
   252  	withAuxStoreInfo
   253  )
   254  
   255  var snapReadInfo = snap.ReadInfo
   256  
   257  // AutomaticSnapshot allows to hook snapshot manager's AutomaticSnapshot.
   258  var AutomaticSnapshot func(st *state.State, instanceName string) (ts *state.TaskSet, err error)
   259  var AutomaticSnapshotExpiration func(st *state.State) (time.Duration, error)
   260  
   261  func readInfo(name string, si *snap.SideInfo, flags int) (*snap.Info, error) {
   262  	info, err := snapReadInfo(name, si)
   263  	if err != nil && flags&errorOnBroken != 0 {
   264  		return nil, err
   265  	}
   266  	if err != nil {
   267  		logger.Noticef("cannot read snap info of snap %q at revision %s: %s", name, si.Revision, err)
   268  	}
   269  	if bse, ok := err.(snap.BrokenSnapError); ok {
   270  		_, instanceKey := snap.SplitInstanceName(name)
   271  		info = &snap.Info{
   272  			SuggestedName: name,
   273  			Broken:        bse.Broken(),
   274  			InstanceKey:   instanceKey,
   275  		}
   276  		info.Apps = snap.GuessAppsForBroken(info)
   277  		if si != nil {
   278  			info.SideInfo = *si
   279  		}
   280  		err = nil
   281  	}
   282  	if err == nil && flags&withAuxStoreInfo != 0 {
   283  		if err := retrieveAuxStoreInfo(info); err != nil {
   284  			logger.Debugf("cannot read auxiliary store info for snap %q: %v", name, err)
   285  		}
   286  	}
   287  	return info, err
   288  }
   289  
   290  var revisionDate = revisionDateImpl
   291  
   292  // revisionDate returns a good approximation of when a revision reached the system.
   293  func revisionDateImpl(info *snap.Info) time.Time {
   294  	fi, err := os.Lstat(info.MountFile())
   295  	if err != nil {
   296  		return time.Time{}
   297  	}
   298  	return fi.ModTime()
   299  }
   300  
   301  // CurrentInfo returns the information about the current active revision or the last active revision (if the snap is inactive). It returns the ErrNoCurrent error if snapst.Current is unset.
   302  func (snapst *SnapState) CurrentInfo() (*snap.Info, error) {
   303  	cur := snapst.CurrentSideInfo()
   304  	if cur == nil {
   305  		return nil, ErrNoCurrent
   306  	}
   307  
   308  	name := snap.InstanceName(cur.RealName, snapst.InstanceKey)
   309  	return readInfo(name, cur, withAuxStoreInfo)
   310  }
   311  
   312  func revisionInSequence(snapst *SnapState, needle snap.Revision) bool {
   313  	for _, si := range snapst.Sequence {
   314  		if si.Revision == needle {
   315  			return true
   316  		}
   317  	}
   318  	return false
   319  }
   320  
   321  type cachedStoreKey struct{}
   322  
   323  // ReplaceStore replaces the store used by the manager.
   324  func ReplaceStore(state *state.State, store StoreService) {
   325  	state.Cache(cachedStoreKey{}, store)
   326  }
   327  
   328  func cachedStore(st *state.State) StoreService {
   329  	ubuntuStore := st.Cached(cachedStoreKey{})
   330  	if ubuntuStore == nil {
   331  		return nil
   332  	}
   333  	return ubuntuStore.(StoreService)
   334  }
   335  
   336  // the store implementation has the interface consumed here
   337  var _ StoreService = (*store.Store)(nil)
   338  
   339  // Store returns the store service provided by the optional device context or
   340  // the one used by the snapstate package if the former has no
   341  // override.
   342  func Store(st *state.State, deviceCtx DeviceContext) StoreService {
   343  	if deviceCtx != nil {
   344  		sto := deviceCtx.Store()
   345  		if sto != nil {
   346  			return sto
   347  		}
   348  	}
   349  	if cachedStore := cachedStore(st); cachedStore != nil {
   350  		return cachedStore
   351  	}
   352  	panic("internal error: needing the store before managers have initialized it")
   353  }
   354  
   355  // Manager returns a new snap manager.
   356  func Manager(st *state.State, runner *state.TaskRunner) (*SnapManager, error) {
   357  	m := &SnapManager{
   358  		state:          st,
   359  		backend:        backend.Backend{},
   360  		autoRefresh:    newAutoRefresh(st),
   361  		refreshHints:   newRefreshHints(st),
   362  		catalogRefresh: newCatalogRefresh(st),
   363  	}
   364  
   365  	if err := os.MkdirAll(dirs.SnapCookieDir, 0700); err != nil {
   366  		return nil, fmt.Errorf("cannot create directory %q: %v", dirs.SnapCookieDir, err)
   367  	}
   368  
   369  	if err := genRefreshRequestSalt(st); err != nil {
   370  		return nil, fmt.Errorf("cannot generate request salt: %v", err)
   371  	}
   372  
   373  	// this handler does nothing
   374  	runner.AddHandler("nop", func(t *state.Task, _ *tomb.Tomb) error {
   375  		return nil
   376  	}, nil)
   377  
   378  	// install/update related
   379  
   380  	// TODO: no undo handler here, we may use the GC for this and just
   381  	// remove anything that is not referenced anymore
   382  	runner.AddHandler("prerequisites", m.doPrerequisites, nil)
   383  	runner.AddHandler("prepare-snap", m.doPrepareSnap, m.undoPrepareSnap)
   384  	runner.AddHandler("download-snap", m.doDownloadSnap, m.undoPrepareSnap)
   385  	runner.AddHandler("mount-snap", m.doMountSnap, m.undoMountSnap)
   386  	runner.AddHandler("unlink-current-snap", m.doUnlinkCurrentSnap, m.undoUnlinkCurrentSnap)
   387  	runner.AddHandler("copy-snap-data", m.doCopySnapData, m.undoCopySnapData)
   388  	runner.AddCleanup("copy-snap-data", m.cleanupCopySnapData)
   389  	runner.AddHandler("link-snap", m.doLinkSnap, m.undoLinkSnap)
   390  	runner.AddHandler("start-snap-services", m.startSnapServices, m.stopSnapServices)
   391  	runner.AddHandler("switch-snap-channel", m.doSwitchSnapChannel, nil)
   392  	runner.AddHandler("toggle-snap-flags", m.doToggleSnapFlags, nil)
   393  	runner.AddHandler("check-rerefresh", m.doCheckReRefresh, nil)
   394  
   395  	// FIXME: drop the task entirely after a while
   396  	// (having this wart here avoids yet-another-patch)
   397  	runner.AddHandler("cleanup", func(*state.Task, *tomb.Tomb) error { return nil }, nil)
   398  
   399  	// remove related
   400  	runner.AddHandler("stop-snap-services", m.stopSnapServices, m.startSnapServices)
   401  	runner.AddHandler("unlink-snap", m.doUnlinkSnap, nil)
   402  	runner.AddHandler("clear-snap", m.doClearSnapData, nil)
   403  	runner.AddHandler("discard-snap", m.doDiscardSnap, nil)
   404  
   405  	// alias related
   406  	// FIXME: drop the task entirely after a while
   407  	runner.AddHandler("clear-aliases", func(*state.Task, *tomb.Tomb) error { return nil }, nil)
   408  	runner.AddHandler("set-auto-aliases", m.doSetAutoAliases, m.undoRefreshAliases)
   409  	runner.AddHandler("setup-aliases", m.doSetupAliases, m.doRemoveAliases)
   410  	runner.AddHandler("refresh-aliases", m.doRefreshAliases, m.undoRefreshAliases)
   411  	runner.AddHandler("prune-auto-aliases", m.doPruneAutoAliases, m.undoRefreshAliases)
   412  	runner.AddHandler("remove-aliases", m.doRemoveAliases, m.doSetupAliases)
   413  	runner.AddHandler("alias", m.doAlias, m.undoRefreshAliases)
   414  	runner.AddHandler("unalias", m.doUnalias, m.undoRefreshAliases)
   415  	runner.AddHandler("disable-aliases", m.doDisableAliases, m.undoRefreshAliases)
   416  	runner.AddHandler("prefer-aliases", m.doPreferAliases, m.undoRefreshAliases)
   417  
   418  	// misc
   419  	runner.AddHandler("switch-snap", m.doSwitchSnap, nil)
   420  
   421  	// control serialisation
   422  	runner.AddBlocked(m.blockedTask)
   423  
   424  	return m, nil
   425  }
   426  
   427  // StartUp implements StateStarterUp.Startup.
   428  func (m *SnapManager) StartUp() error {
   429  	writeSnapReadme()
   430  
   431  	m.state.Lock()
   432  	defer m.state.Unlock()
   433  	if err := m.SyncCookies(m.state); err != nil {
   434  		return fmt.Errorf("failed to generate cookies: %q", err)
   435  	}
   436  	return nil
   437  }
   438  
   439  func (m *SnapManager) CanStandby() bool {
   440  	if n, err := NumSnaps(m.state); err == nil && n == 0 {
   441  		return true
   442  	}
   443  	return false
   444  }
   445  
   446  func genRefreshRequestSalt(st *state.State) error {
   447  	var refreshPrivacyKey string
   448  
   449  	st.Lock()
   450  	defer st.Unlock()
   451  
   452  	if err := st.Get("refresh-privacy-key", &refreshPrivacyKey); err != nil && err != state.ErrNoState {
   453  		return err
   454  	}
   455  	if refreshPrivacyKey != "" {
   456  		// nothing to do
   457  		return nil
   458  	}
   459  
   460  	refreshPrivacyKey = strutil.MakeRandomString(16)
   461  	st.Set("refresh-privacy-key", refreshPrivacyKey)
   462  
   463  	return nil
   464  }
   465  
   466  func (m *SnapManager) blockedTask(cand *state.Task, running []*state.Task) bool {
   467  	// Serialize "prerequisites", the state lock is not enough as
   468  	// Install() inside doPrerequisites() will unlock to talk to
   469  	// the store.
   470  	if cand.Kind() == "prerequisites" {
   471  		for _, t := range running {
   472  			if t.Kind() == "prerequisites" {
   473  				return true
   474  			}
   475  		}
   476  	}
   477  
   478  	return false
   479  }
   480  
   481  // NextRefresh returns the time the next update of the system's snaps
   482  // will be attempted.
   483  // The caller should be holding the state lock.
   484  func (m *SnapManager) NextRefresh() time.Time {
   485  	return m.autoRefresh.NextRefresh()
   486  }
   487  
   488  // EffectiveRefreshHold returns the time until to which refreshes are
   489  // held if refresh.hold configuration is set and accounting for the
   490  // max postponement since the last refresh.
   491  // The caller should be holding the state lock.
   492  func (m *SnapManager) EffectiveRefreshHold() (time.Time, error) {
   493  	return m.autoRefresh.EffectiveRefreshHold()
   494  }
   495  
   496  // LastRefresh returns the time the last snap update.
   497  // The caller should be holding the state lock.
   498  func (m *SnapManager) LastRefresh() (time.Time, error) {
   499  	return m.autoRefresh.LastRefresh()
   500  }
   501  
   502  // RefreshSchedule returns the current refresh schedule as a string suitable for
   503  // display to a user and a flag indicating whether the schedule is a legacy one.
   504  // The caller should be holding the state lock.
   505  func (m *SnapManager) RefreshSchedule() (string, bool, error) {
   506  	return m.autoRefresh.RefreshSchedule()
   507  }
   508  
   509  // ensureForceDevmodeDropsDevmodeFromState undoes the forced devmode
   510  // in snapstate for forced devmode distros.
   511  func (m *SnapManager) ensureForceDevmodeDropsDevmodeFromState() error {
   512  	if !release.ReleaseInfo.ForceDevMode() {
   513  		return nil
   514  	}
   515  
   516  	m.state.Lock()
   517  	defer m.state.Unlock()
   518  
   519  	// int because we might want to come back and do a second pass at cleanup
   520  	var fixed int
   521  	if err := m.state.Get("fix-forced-devmode", &fixed); err != nil && err != state.ErrNoState {
   522  		return err
   523  	}
   524  
   525  	if fixed > 0 {
   526  		return nil
   527  	}
   528  
   529  	for _, name := range []string{"core", "ubuntu-core"} {
   530  		var snapst SnapState
   531  		if err := Get(m.state, name, &snapst); err == state.ErrNoState {
   532  			// nothing to see here
   533  			continue
   534  		} else if err != nil {
   535  			// bad
   536  			return err
   537  		}
   538  		if info := snapst.CurrentSideInfo(); info == nil || info.SnapID == "" {
   539  			continue
   540  		}
   541  		snapst.DevMode = false
   542  		Set(m.state, name, &snapst)
   543  	}
   544  	m.state.Set("fix-forced-devmode", 1)
   545  
   546  	return nil
   547  }
   548  
   549  // changeInFlight returns true if there is any change in the state
   550  // in non-ready state.
   551  func changeInFlight(st *state.State) bool {
   552  	for _, chg := range st.Changes() {
   553  		if !chg.IsReady() {
   554  			// another change already in motion
   555  			return true
   556  		}
   557  	}
   558  	return false
   559  }
   560  
   561  // ensureSnapdSnapTransition will migrate systems to use the "snapd" snap
   562  func (m *SnapManager) ensureSnapdSnapTransition() error {
   563  	m.state.Lock()
   564  	defer m.state.Unlock()
   565  
   566  	// we only auto-transition people on classic systems, for core we
   567  	// will need to do a proper re-model
   568  	if !release.OnClassic {
   569  		return nil
   570  	}
   571  
   572  	// check if snapd snap is installed
   573  	var snapst SnapState
   574  	err := Get(m.state, "snapd", &snapst)
   575  	if err != nil && err != state.ErrNoState {
   576  		return err
   577  	}
   578  	// nothing to do
   579  	if snapst.IsInstalled() {
   580  		return nil
   581  	}
   582  
   583  	// check if the user opts into the snapd snap
   584  	optedIntoSnapdTransition, err := optedIntoSnapdSnap(m.state)
   585  	if err != nil {
   586  		return err
   587  	}
   588  	// nothing to do: the user does not want the snapd snap yet
   589  	if !optedIntoSnapdTransition {
   590  		return nil
   591  	}
   592  
   593  	// ensure we only transition systems that have snaps already
   594  	installedSnaps, err := NumSnaps(m.state)
   595  	if err != nil {
   596  		return err
   597  	}
   598  	// no installed snaps (yet): do nothing (fresh classic install)
   599  	if installedSnaps == 0 {
   600  		return nil
   601  	}
   602  
   603  	// get current core snap and use same channel/user for the snapd snap
   604  	err = Get(m.state, "core", &snapst)
   605  	// Note that state.ErrNoState should never happen in practise. However
   606  	// if it *does* happen we still want to fix those systems by installing
   607  	// the snapd snap.
   608  	if err != nil && err != state.ErrNoState {
   609  		return err
   610  	}
   611  	coreChannel := snapst.Channel
   612  	// snapd/core are never blocked on auth so we don't need to copy
   613  	// the userID from the snapst here
   614  	userID := 0
   615  
   616  	if changeInFlight(m.state) {
   617  		// check that there is no change in flight already, this is a
   618  		// precaution to ensure the snapd transition is safe
   619  		return nil
   620  	}
   621  
   622  	// ensure we limit the retries in case something goes wrong
   623  	var lastSnapdTransitionAttempt time.Time
   624  	err = m.state.Get("snapd-transition-last-retry-time", &lastSnapdTransitionAttempt)
   625  	if err != nil && err != state.ErrNoState {
   626  		return err
   627  	}
   628  	now := time.Now()
   629  	if !lastSnapdTransitionAttempt.IsZero() && lastSnapdTransitionAttempt.Add(snapdTransitionDelayWithRandomess).After(now) {
   630  		return nil
   631  	}
   632  	m.state.Set("snapd-transition-last-retry-time", now)
   633  
   634  	var retryCount int
   635  	err = m.state.Get("snapd-transition-retry", &retryCount)
   636  	if err != nil && err != state.ErrNoState {
   637  		return err
   638  	}
   639  	m.state.Set("snapd-transition-retry", retryCount+1)
   640  
   641  	ts, err := Install(context.Background(), m.state, "snapd", &RevisionOptions{Channel: coreChannel}, userID, Flags{})
   642  	if err != nil {
   643  		return err
   644  	}
   645  
   646  	msg := i18n.G("Transition to the snapd snap")
   647  	chg := m.state.NewChange("transition-to-snapd-snap", msg)
   648  	chg.AddAll(ts)
   649  
   650  	return nil
   651  }
   652  
   653  // ensureUbuntuCoreTransition will migrate systems that use "ubuntu-core"
   654  // to the new "core" snap
   655  func (m *SnapManager) ensureUbuntuCoreTransition() error {
   656  	m.state.Lock()
   657  	defer m.state.Unlock()
   658  
   659  	var snapst SnapState
   660  	err := Get(m.state, "ubuntu-core", &snapst)
   661  	if err == state.ErrNoState {
   662  		return nil
   663  	}
   664  	if err != nil && err != state.ErrNoState {
   665  		return err
   666  	}
   667  
   668  	// check that there is no change in flight already, this is a
   669  	// precaution to ensure the core transition is safe
   670  	if changeInFlight(m.state) {
   671  		// another change already in motion
   672  		return nil
   673  	}
   674  
   675  	// ensure we limit the retries in case something goes wrong
   676  	var lastUbuntuCoreTransitionAttempt time.Time
   677  	err = m.state.Get("ubuntu-core-transition-last-retry-time", &lastUbuntuCoreTransitionAttempt)
   678  	if err != nil && err != state.ErrNoState {
   679  		return err
   680  	}
   681  	now := time.Now()
   682  	if !lastUbuntuCoreTransitionAttempt.IsZero() && lastUbuntuCoreTransitionAttempt.Add(6*time.Hour).After(now) {
   683  		return nil
   684  	}
   685  
   686  	tss, trErr := TransitionCore(m.state, "ubuntu-core", "core")
   687  	if _, ok := trErr.(*ChangeConflictError); ok {
   688  		// likely just too early, retry at next Ensure
   689  		return nil
   690  	}
   691  
   692  	m.state.Set("ubuntu-core-transition-last-retry-time", now)
   693  
   694  	var retryCount int
   695  	err = m.state.Get("ubuntu-core-transition-retry", &retryCount)
   696  	if err != nil && err != state.ErrNoState {
   697  		return err
   698  	}
   699  	m.state.Set("ubuntu-core-transition-retry", retryCount+1)
   700  
   701  	if trErr != nil {
   702  		return trErr
   703  	}
   704  
   705  	msg := i18n.G("Transition ubuntu-core to core")
   706  	chg := m.state.NewChange("transition-ubuntu-core", msg)
   707  	for _, ts := range tss {
   708  		chg.AddAll(ts)
   709  	}
   710  
   711  	return nil
   712  }
   713  
   714  // atSeed implements at seeding policy for refreshes.
   715  func (m *SnapManager) atSeed() error {
   716  	m.state.Lock()
   717  	defer m.state.Unlock()
   718  	var seeded bool
   719  	err := m.state.Get("seeded", &seeded)
   720  	if err != state.ErrNoState {
   721  		// already seeded or other error
   722  		return err
   723  	}
   724  	if err := m.autoRefresh.AtSeed(); err != nil {
   725  		return err
   726  	}
   727  	if err := m.refreshHints.AtSeed(); err != nil {
   728  		return err
   729  	}
   730  	return nil
   731  }
   732  
   733  var (
   734  	localInstallCleanupWait = time.Duration(24 * time.Hour)
   735  	localInstallLastCleanup time.Time
   736  )
   737  
   738  // localInstallCleanup removes files that might've been left behind by an
   739  // old aborted local install.
   740  //
   741  // They're usually cleaned up, but if they're created and then snapd
   742  // stops before writing the change to disk (killed, light cut, etc)
   743  // it'll be left behind.
   744  //
   745  // The code that creates the files is in daemon/api.go's postSnaps
   746  func (m *SnapManager) localInstallCleanup() error {
   747  	m.state.Lock()
   748  	defer m.state.Unlock()
   749  
   750  	now := time.Now()
   751  	cutoff := now.Add(-localInstallCleanupWait)
   752  	if localInstallLastCleanup.After(cutoff) {
   753  		return nil
   754  	}
   755  	localInstallLastCleanup = now
   756  
   757  	d, err := os.Open(dirs.SnapBlobDir)
   758  	if err != nil {
   759  		if os.IsNotExist(err) {
   760  			return nil
   761  		}
   762  		return err
   763  	}
   764  	defer d.Close()
   765  
   766  	var filenames []string
   767  	var fis []os.FileInfo
   768  	for err == nil {
   769  		// TODO: if we had fstatat we could avoid a bunch of stats
   770  		fis, err = d.Readdir(100)
   771  		// fis is nil if err isn't
   772  		for _, fi := range fis {
   773  			name := fi.Name()
   774  			if !strings.HasPrefix(name, dirs.LocalInstallBlobTempPrefix) {
   775  				continue
   776  			}
   777  			if fi.ModTime().After(cutoff) {
   778  				continue
   779  			}
   780  			filenames = append(filenames, name)
   781  		}
   782  	}
   783  	if err != io.EOF {
   784  		return err
   785  	}
   786  	return osutil.UnlinkManyAt(d, filenames)
   787  }
   788  
   789  // Ensure implements StateManager.Ensure.
   790  func (m *SnapManager) Ensure() error {
   791  	// do not exit right away on error
   792  	errs := []error{
   793  		m.atSeed(),
   794  		m.ensureAliasesV2(),
   795  		m.ensureForceDevmodeDropsDevmodeFromState(),
   796  		m.ensureUbuntuCoreTransition(),
   797  		m.ensureSnapdSnapTransition(),
   798  		// we should check for full regular refreshes before
   799  		// considering issuing a hint only refresh request
   800  		m.autoRefresh.Ensure(),
   801  		m.refreshHints.Ensure(),
   802  		m.catalogRefresh.Ensure(),
   803  		m.localInstallCleanup(),
   804  	}
   805  
   806  	//FIXME: use firstErr helper
   807  	for _, e := range errs {
   808  		if e != nil {
   809  			return e
   810  		}
   811  	}
   812  
   813  	return nil
   814  }