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