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