github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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-time,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  	AddAffectedSnapsByKind("conditional-auto-refresh", conditionalAutoRefreshAffectedSnaps)
   472  
   473  	return m, nil
   474  }
   475  
   476  // StartUp implements StateStarterUp.Startup.
   477  func (m *SnapManager) StartUp() error {
   478  	writeSnapReadme()
   479  
   480  	m.state.Lock()
   481  	defer m.state.Unlock()
   482  	if err := m.SyncCookies(m.state); err != nil {
   483  		return fmt.Errorf("failed to generate cookies: %q", err)
   484  	}
   485  	return nil
   486  }
   487  
   488  func (m *SnapManager) CanStandby() bool {
   489  	if n, err := NumSnaps(m.state); err == nil && n == 0 {
   490  		return true
   491  	}
   492  	return false
   493  }
   494  
   495  func genRefreshRequestSalt(st *state.State) error {
   496  	var refreshPrivacyKey string
   497  
   498  	st.Lock()
   499  	defer st.Unlock()
   500  
   501  	if err := st.Get("refresh-privacy-key", &refreshPrivacyKey); err != nil && err != state.ErrNoState {
   502  		return err
   503  	}
   504  	if refreshPrivacyKey != "" {
   505  		// nothing to do
   506  		return nil
   507  	}
   508  
   509  	refreshPrivacyKey = randutil.RandomString(16)
   510  	st.Set("refresh-privacy-key", refreshPrivacyKey)
   511  
   512  	return nil
   513  }
   514  
   515  func (m *SnapManager) blockedTask(cand *state.Task, running []*state.Task) bool {
   516  	// Serialize "prerequisites", the state lock is not enough as
   517  	// Install() inside doPrerequisites() will unlock to talk to
   518  	// the store.
   519  	if cand.Kind() == "prerequisites" {
   520  		for _, t := range running {
   521  			if t.Kind() == "prerequisites" {
   522  				return true
   523  			}
   524  		}
   525  	}
   526  
   527  	return false
   528  }
   529  
   530  // NextRefresh returns the time the next update of the system's snaps
   531  // will be attempted.
   532  // The caller should be holding the state lock.
   533  func (m *SnapManager) NextRefresh() time.Time {
   534  	return m.autoRefresh.NextRefresh()
   535  }
   536  
   537  // EffectiveRefreshHold returns the time until to which refreshes are
   538  // held if refresh.hold configuration is set and accounting for the
   539  // max postponement since the last refresh.
   540  // The caller should be holding the state lock.
   541  func (m *SnapManager) EffectiveRefreshHold() (time.Time, error) {
   542  	return m.autoRefresh.EffectiveRefreshHold()
   543  }
   544  
   545  // LastRefresh returns the time the last snap update.
   546  // The caller should be holding the state lock.
   547  func (m *SnapManager) LastRefresh() (time.Time, error) {
   548  	return m.autoRefresh.LastRefresh()
   549  }
   550  
   551  // RefreshSchedule returns the current refresh schedule as a string suitable for
   552  // display to a user and a flag indicating whether the schedule is a legacy one.
   553  // The caller should be holding the state lock.
   554  func (m *SnapManager) RefreshSchedule() (string, bool, error) {
   555  	return m.autoRefresh.RefreshSchedule()
   556  }
   557  
   558  // EnsureAutoRefreshesAreDelayed will delay refreshes for the specified amount
   559  // of time, as well as return any active auto-refresh changes that are currently
   560  // not ready so that the client can wait for those.
   561  func (m *SnapManager) EnsureAutoRefreshesAreDelayed(delay time.Duration) ([]*state.Change, error) {
   562  	// always delay for at least the specified time, this ensures that even if
   563  	// there are active refreshes right now, there won't be more auto-refreshes
   564  	// that happen after the current set finish
   565  	err := m.autoRefresh.ensureRefreshHoldAtLeast(delay)
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  
   570  	// look for auto refresh changes in progress
   571  	autoRefreshChgsInFlight := []*state.Change{}
   572  	for _, chg := range m.state.Changes() {
   573  		if chg.Kind() == "auto-refresh" && !chg.Status().Ready() {
   574  			autoRefreshChgsInFlight = append(autoRefreshChgsInFlight, chg)
   575  		}
   576  	}
   577  
   578  	return autoRefreshChgsInFlight, nil
   579  }
   580  
   581  // ensureForceDevmodeDropsDevmodeFromState undoes the forced devmode
   582  // in snapstate for forced devmode distros.
   583  func (m *SnapManager) ensureForceDevmodeDropsDevmodeFromState() error {
   584  	if !sandbox.ForceDevMode() {
   585  		return nil
   586  	}
   587  
   588  	m.state.Lock()
   589  	defer m.state.Unlock()
   590  
   591  	// int because we might want to come back and do a second pass at cleanup
   592  	var fixed int
   593  	if err := m.state.Get("fix-forced-devmode", &fixed); err != nil && err != state.ErrNoState {
   594  		return err
   595  	}
   596  
   597  	if fixed > 0 {
   598  		return nil
   599  	}
   600  
   601  	for _, name := range []string{"core", "ubuntu-core"} {
   602  		var snapst SnapState
   603  		if err := Get(m.state, name, &snapst); err == state.ErrNoState {
   604  			// nothing to see here
   605  			continue
   606  		} else if err != nil {
   607  			// bad
   608  			return err
   609  		}
   610  		if info := snapst.CurrentSideInfo(); info == nil || info.SnapID == "" {
   611  			continue
   612  		}
   613  		snapst.DevMode = false
   614  		Set(m.state, name, &snapst)
   615  	}
   616  	m.state.Set("fix-forced-devmode", 1)
   617  
   618  	return nil
   619  }
   620  
   621  // changeInFlight returns true if there is any change in the state
   622  // in non-ready state.
   623  func changeInFlight(st *state.State) bool {
   624  	for _, chg := range st.Changes() {
   625  		if !chg.IsReady() {
   626  			// another change already in motion
   627  			return true
   628  		}
   629  	}
   630  	return false
   631  }
   632  
   633  // ensureSnapdSnapTransition will migrate systems to use the "snapd" snap
   634  func (m *SnapManager) ensureSnapdSnapTransition() error {
   635  	m.state.Lock()
   636  	defer m.state.Unlock()
   637  
   638  	// we only auto-transition people on classic systems, for core we
   639  	// will need to do a proper re-model
   640  	if !release.OnClassic {
   641  		return nil
   642  	}
   643  
   644  	// check if snapd snap is installed
   645  	var snapst SnapState
   646  	err := Get(m.state, "snapd", &snapst)
   647  	if err != nil && err != state.ErrNoState {
   648  		return err
   649  	}
   650  	// nothing to do
   651  	if snapst.IsInstalled() {
   652  		return nil
   653  	}
   654  
   655  	// check if the user opts into the snapd snap
   656  	optedIntoSnapdTransition, err := optedIntoSnapdSnap(m.state)
   657  	if err != nil {
   658  		return err
   659  	}
   660  	// nothing to do: the user does not want the snapd snap yet
   661  	if !optedIntoSnapdTransition {
   662  		return nil
   663  	}
   664  
   665  	// ensure we only transition systems that have snaps already
   666  	installedSnaps, err := NumSnaps(m.state)
   667  	if err != nil {
   668  		return err
   669  	}
   670  	// no installed snaps (yet): do nothing (fresh classic install)
   671  	if installedSnaps == 0 {
   672  		return nil
   673  	}
   674  
   675  	// get current core snap and use same channel/user for the snapd snap
   676  	err = Get(m.state, "core", &snapst)
   677  	// Note that state.ErrNoState should never happen in practise. However
   678  	// if it *does* happen we still want to fix those systems by installing
   679  	// the snapd snap.
   680  	if err != nil && err != state.ErrNoState {
   681  		return err
   682  	}
   683  	coreChannel := snapst.TrackingChannel
   684  	// snapd/core are never blocked on auth so we don't need to copy
   685  	// the userID from the snapst here
   686  	userID := 0
   687  
   688  	if changeInFlight(m.state) {
   689  		// check that there is no change in flight already, this is a
   690  		// precaution to ensure the snapd transition is safe
   691  		return nil
   692  	}
   693  
   694  	// ensure we limit the retries in case something goes wrong
   695  	var lastSnapdTransitionAttempt time.Time
   696  	err = m.state.Get("snapd-transition-last-retry-time", &lastSnapdTransitionAttempt)
   697  	if err != nil && err != state.ErrNoState {
   698  		return err
   699  	}
   700  	now := time.Now()
   701  	if !lastSnapdTransitionAttempt.IsZero() && lastSnapdTransitionAttempt.Add(snapdTransitionDelayWithRandomess).After(now) {
   702  		return nil
   703  	}
   704  	m.state.Set("snapd-transition-last-retry-time", now)
   705  
   706  	var retryCount int
   707  	err = m.state.Get("snapd-transition-retry", &retryCount)
   708  	if err != nil && err != state.ErrNoState {
   709  		return err
   710  	}
   711  	m.state.Set("snapd-transition-retry", retryCount+1)
   712  
   713  	ts, err := Install(context.Background(), m.state, "snapd", &RevisionOptions{Channel: coreChannel}, userID, Flags{})
   714  	if err != nil {
   715  		return err
   716  	}
   717  
   718  	msg := i18n.G("Transition to the snapd snap")
   719  	chg := m.state.NewChange("transition-to-snapd-snap", msg)
   720  	chg.AddAll(ts)
   721  
   722  	return nil
   723  }
   724  
   725  // ensureUbuntuCoreTransition will migrate systems that use "ubuntu-core"
   726  // to the new "core" snap
   727  func (m *SnapManager) ensureUbuntuCoreTransition() error {
   728  	m.state.Lock()
   729  	defer m.state.Unlock()
   730  
   731  	var snapst SnapState
   732  	err := Get(m.state, "ubuntu-core", &snapst)
   733  	if err == state.ErrNoState {
   734  		return nil
   735  	}
   736  	if err != nil && err != state.ErrNoState {
   737  		return err
   738  	}
   739  
   740  	// check that there is no change in flight already, this is a
   741  	// precaution to ensure the core transition is safe
   742  	if changeInFlight(m.state) {
   743  		// another change already in motion
   744  		return nil
   745  	}
   746  
   747  	// ensure we limit the retries in case something goes wrong
   748  	var lastUbuntuCoreTransitionAttempt time.Time
   749  	err = m.state.Get("ubuntu-core-transition-last-retry-time", &lastUbuntuCoreTransitionAttempt)
   750  	if err != nil && err != state.ErrNoState {
   751  		return err
   752  	}
   753  	now := time.Now()
   754  	if !lastUbuntuCoreTransitionAttempt.IsZero() && lastUbuntuCoreTransitionAttempt.Add(6*time.Hour).After(now) {
   755  		return nil
   756  	}
   757  
   758  	tss, trErr := TransitionCore(m.state, "ubuntu-core", "core")
   759  	if _, ok := trErr.(*ChangeConflictError); ok {
   760  		// likely just too early, retry at next Ensure
   761  		return nil
   762  	}
   763  
   764  	m.state.Set("ubuntu-core-transition-last-retry-time", now)
   765  
   766  	var retryCount int
   767  	err = m.state.Get("ubuntu-core-transition-retry", &retryCount)
   768  	if err != nil && err != state.ErrNoState {
   769  		return err
   770  	}
   771  	m.state.Set("ubuntu-core-transition-retry", retryCount+1)
   772  
   773  	if trErr != nil {
   774  		return trErr
   775  	}
   776  
   777  	msg := i18n.G("Transition ubuntu-core to core")
   778  	chg := m.state.NewChange("transition-ubuntu-core", msg)
   779  	for _, ts := range tss {
   780  		chg.AddAll(ts)
   781  	}
   782  
   783  	return nil
   784  }
   785  
   786  // atSeed implements at seeding policy for refreshes.
   787  func (m *SnapManager) atSeed() error {
   788  	m.state.Lock()
   789  	defer m.state.Unlock()
   790  	var seeded bool
   791  	err := m.state.Get("seeded", &seeded)
   792  	if err != state.ErrNoState {
   793  		// already seeded or other error
   794  		return err
   795  	}
   796  	if err := m.autoRefresh.AtSeed(); err != nil {
   797  		return err
   798  	}
   799  	if err := m.refreshHints.AtSeed(); err != nil {
   800  		return err
   801  	}
   802  	return nil
   803  }
   804  
   805  var (
   806  	localInstallCleanupWait = time.Duration(24 * time.Hour)
   807  	localInstallLastCleanup time.Time
   808  )
   809  
   810  // localInstallCleanup removes files that might've been left behind by an
   811  // old aborted local install.
   812  //
   813  // They're usually cleaned up, but if they're created and then snapd
   814  // stops before writing the change to disk (killed, light cut, etc)
   815  // it'll be left behind.
   816  //
   817  // The code that creates the files is in daemon/api.go's postSnaps
   818  func (m *SnapManager) localInstallCleanup() error {
   819  	m.state.Lock()
   820  	defer m.state.Unlock()
   821  
   822  	now := time.Now()
   823  	cutoff := now.Add(-localInstallCleanupWait)
   824  	if localInstallLastCleanup.After(cutoff) {
   825  		return nil
   826  	}
   827  	localInstallLastCleanup = now
   828  
   829  	d, err := os.Open(dirs.SnapBlobDir)
   830  	if err != nil {
   831  		if os.IsNotExist(err) {
   832  			return nil
   833  		}
   834  		return err
   835  	}
   836  	defer d.Close()
   837  
   838  	var filenames []string
   839  	var fis []os.FileInfo
   840  	for err == nil {
   841  		// TODO: if we had fstatat we could avoid a bunch of stats
   842  		fis, err = d.Readdir(100)
   843  		// fis is nil if err isn't
   844  		for _, fi := range fis {
   845  			name := fi.Name()
   846  			if !strings.HasPrefix(name, dirs.LocalInstallBlobTempPrefix) {
   847  				continue
   848  			}
   849  			if fi.ModTime().After(cutoff) {
   850  				continue
   851  			}
   852  			filenames = append(filenames, name)
   853  		}
   854  	}
   855  	if err != io.EOF {
   856  		return err
   857  	}
   858  	return osutil.UnlinkManyAt(d, filenames)
   859  }
   860  
   861  // Ensure implements StateManager.Ensure.
   862  func (m *SnapManager) Ensure() error {
   863  	if m.preseed {
   864  		return nil
   865  	}
   866  
   867  	// do not exit right away on error
   868  	errs := []error{
   869  		m.atSeed(),
   870  		m.ensureAliasesV2(),
   871  		m.ensureForceDevmodeDropsDevmodeFromState(),
   872  		m.ensureUbuntuCoreTransition(),
   873  		m.ensureSnapdSnapTransition(),
   874  		// we should check for full regular refreshes before
   875  		// considering issuing a hint only refresh request
   876  		m.autoRefresh.Ensure(),
   877  		m.refreshHints.Ensure(),
   878  		m.catalogRefresh.Ensure(),
   879  		m.localInstallCleanup(),
   880  	}
   881  
   882  	//FIXME: use firstErr helper
   883  	for _, e := range errs {
   884  		if e != nil {
   885  			return e
   886  		}
   887  	}
   888  
   889  	return nil
   890  }