github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/snapstate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package snapstate implements the manager and state aspects responsible for the installation and removal of snaps.
    21  package snapstate
    22  
    23  import (
    24  	"context"
    25  	"encoding/json"
    26  	"errors"
    27  	"fmt"
    28  	"os"
    29  	"sort"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/boot"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/features"
    37  	"github.com/snapcore/snapd/gadget"
    38  	"github.com/snapcore/snapd/i18n"
    39  	"github.com/snapcore/snapd/interfaces"
    40  	"github.com/snapcore/snapd/logger"
    41  	"github.com/snapcore/snapd/osutil"
    42  	"github.com/snapcore/snapd/overlord/auth"
    43  	"github.com/snapcore/snapd/overlord/configstate/config"
    44  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    45  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    46  	"github.com/snapcore/snapd/overlord/state"
    47  	"github.com/snapcore/snapd/release"
    48  	"github.com/snapcore/snapd/snap"
    49  	"github.com/snapcore/snapd/snap/channel"
    50  	"github.com/snapcore/snapd/store"
    51  	"github.com/snapcore/snapd/strutil"
    52  )
    53  
    54  // control flags for doInstall
    55  const (
    56  	skipConfigure = 1 << iota
    57  )
    58  
    59  // control flags for "Configure()"
    60  const (
    61  	IgnoreHookError = 1 << iota
    62  	TrackHookError
    63  	UseConfigDefaults
    64  )
    65  
    66  const (
    67  	DownloadAndChecksDoneEdge = state.TaskSetEdge("download-and-checks-done")
    68  	BeginEdge                 = state.TaskSetEdge("begin")
    69  	BeforeHooksEdge           = state.TaskSetEdge("before-hooks")
    70  	HooksEdge                 = state.TaskSetEdge("hooks")
    71  )
    72  
    73  var ErrNothingToDo = errors.New("nothing to do")
    74  
    75  var osutilCheckFreeSpace = osutil.CheckFreeSpace
    76  
    77  type minimalInstallInfo interface {
    78  	InstanceName() string
    79  	Type() snap.Type
    80  	SnapBase() string
    81  	DownloadSize() int64
    82  	Prereq(st *state.State) []string
    83  }
    84  
    85  type updateParamsFunc func(*snap.Info) (*RevisionOptions, Flags, *SnapState)
    86  
    87  type readyUpdateInfo interface {
    88  	minimalInstallInfo
    89  
    90  	SnapSetupForUpdate(st *state.State, params updateParamsFunc, userID int, globalFlags *Flags) (*SnapSetup, *SnapState, error)
    91  }
    92  
    93  // ByType supports sorting by snap type. The most important types come first.
    94  type byType []minimalInstallInfo
    95  
    96  func (r byType) Len() int      { return len(r) }
    97  func (r byType) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
    98  func (r byType) Less(i, j int) bool {
    99  	return r[i].Type().SortsBefore(r[j].Type())
   100  }
   101  
   102  type installSnapInfo struct {
   103  	*snap.Info
   104  }
   105  
   106  func (ins installSnapInfo) DownloadSize() int64 {
   107  	return ins.DownloadInfo.Size
   108  }
   109  
   110  // SnapBase returns the base snap of the snap.
   111  func (ins installSnapInfo) SnapBase() string {
   112  	return ins.Base
   113  }
   114  
   115  func (ins installSnapInfo) Prereq(st *state.State) []string {
   116  	return defaultContentPlugProviders(st, ins.Info)
   117  }
   118  
   119  func (ins installSnapInfo) SnapSetupForUpdate(st *state.State, params updateParamsFunc, userID int, globalFlags *Flags) (*SnapSetup, *SnapState, error) {
   120  	update := ins.Info
   121  
   122  	revnoOpts, flags, snapst := params(update)
   123  	flags.IsAutoRefresh = globalFlags.IsAutoRefresh
   124  
   125  	flags, err := earlyChecks(st, snapst, update, flags)
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  
   130  	snapUserID, err := userIDForSnap(st, snapst, userID)
   131  	if err != nil {
   132  		return nil, nil, err
   133  	}
   134  
   135  	snapsup := SnapSetup{
   136  		Base:         update.Base,
   137  		Prereq:       defaultContentPlugProviders(st, update),
   138  		Channel:      revnoOpts.Channel,
   139  		CohortKey:    revnoOpts.CohortKey,
   140  		UserID:       snapUserID,
   141  		Flags:        flags.ForSnapSetup(),
   142  		DownloadInfo: &update.DownloadInfo,
   143  		SideInfo:     &update.SideInfo,
   144  		Type:         update.Type(),
   145  		PlugsOnly:    len(update.Slots) == 0,
   146  		InstanceKey:  update.InstanceKey,
   147  		auxStoreInfo: auxStoreInfo{
   148  			Website: update.Website,
   149  			Media:   update.Media,
   150  		},
   151  	}
   152  	return &snapsup, snapst, nil
   153  }
   154  
   155  // soundness check
   156  var _ readyUpdateInfo = installSnapInfo{}
   157  
   158  // InsufficientSpaceError represents an error where there is not enough disk
   159  // space to perform an operation.
   160  type InsufficientSpaceError struct {
   161  	// Path is the filesystem path checked for available disk space
   162  	Path string
   163  	// Snaps affected by the failing operation
   164  	Snaps []string
   165  	// Kind of the change that failed
   166  	ChangeKind string
   167  	// Message is optional, otherwise one is composed from the other information
   168  	Message string
   169  }
   170  
   171  func (e *InsufficientSpaceError) Error() string {
   172  	if e.Message != "" {
   173  		return e.Message
   174  	}
   175  	if len(e.Snaps) > 0 {
   176  		snaps := strings.Join(e.Snaps, ", ")
   177  		return fmt.Sprintf("insufficient space in %q to perform %q change for the following snaps: %s", e.Path, e.ChangeKind, snaps)
   178  	}
   179  	return fmt.Sprintf("insufficient space in %q", e.Path)
   180  }
   181  
   182  // safetyMarginDiskSpace returns size plus a safety margin (5Mb)
   183  func safetyMarginDiskSpace(size uint64) uint64 {
   184  	return size + 5*1024*1024
   185  }
   186  
   187  func isParallelInstallable(snapsup *SnapSetup) error {
   188  	if snapsup.InstanceKey == "" {
   189  		return nil
   190  	}
   191  	if snapsup.Type == snap.TypeApp {
   192  		return nil
   193  	}
   194  	return fmt.Errorf("cannot install snap of type %v as %q", snapsup.Type, snapsup.InstanceName())
   195  }
   196  
   197  func optedIntoSnapdSnap(st *state.State) (bool, error) {
   198  	tr := config.NewTransaction(st)
   199  	experimentalAllowSnapd, err := features.Flag(tr, features.SnapdSnap)
   200  	if err != nil && !config.IsNoOption(err) {
   201  		return false, err
   202  	}
   203  	return experimentalAllowSnapd, nil
   204  }
   205  
   206  func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int, fromChange string, inUseCheck func(snap.Type) (boot.InUseFunc, error)) (*state.TaskSet, error) {
   207  	// NB: we should strive not to need or propagate deviceCtx
   208  	// here, the resulting effects/changes were not pleasant at
   209  	// one point
   210  	tr := config.NewTransaction(st)
   211  	experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness)
   212  	if err != nil && !config.IsNoOption(err) {
   213  		return nil, err
   214  	}
   215  
   216  	if snapsup.InstanceName() == "system" {
   217  		return nil, fmt.Errorf("cannot install reserved snap name 'system'")
   218  	}
   219  	if snapst.IsInstalled() && !snapst.Active {
   220  		return nil, fmt.Errorf("cannot update disabled snap %q", snapsup.InstanceName())
   221  	}
   222  	if snapst.IsInstalled() && !snapsup.Flags.Revert {
   223  		if inUseCheck == nil {
   224  			return nil, fmt.Errorf("internal error: doInstall: inUseCheck not provided for refresh")
   225  		}
   226  	}
   227  
   228  	if snapsup.Flags.Classic {
   229  		if !release.OnClassic {
   230  			return nil, fmt.Errorf("classic confinement is only supported on classic systems")
   231  		} else if !dirs.SupportsClassicConfinement() {
   232  			return nil, fmt.Errorf(i18n.G("classic confinement requires snaps under /snap or symlink from /snap to %s"), dirs.SnapMountDir)
   233  		}
   234  	}
   235  	if !snapst.IsInstalled() { // install?
   236  		// check that the snap command namespace doesn't conflict with an enabled alias
   237  		if err := checkSnapAliasConflict(st, snapsup.InstanceName()); err != nil {
   238  			return nil, err
   239  		}
   240  	}
   241  
   242  	if err := isParallelInstallable(snapsup); err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	if err := checkChangeConflictIgnoringOneChange(st, snapsup.InstanceName(), snapst, fromChange); err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	if snapst.IsInstalled() {
   251  		// consider also the current revision to set plugs-only hint
   252  		info, err := snapst.CurrentInfo()
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  		snapsup.PlugsOnly = snapsup.PlugsOnly && (len(info.Slots) == 0)
   257  
   258  		if experimentalRefreshAppAwareness && !snapsup.Flags.IgnoreRunning {
   259  			// Note that because we are modifying the snap state inside
   260  			// softCheckNothingRunningForRefresh, this block must be located
   261  			// after the conflict check done above.
   262  			if err := softCheckNothingRunningForRefresh(st, snapst, info); err != nil {
   263  				return nil, err
   264  			}
   265  		}
   266  	}
   267  
   268  	ts := state.NewTaskSet()
   269  
   270  	targetRevision := snapsup.Revision()
   271  	revisionStr := ""
   272  	if snapsup.SideInfo != nil {
   273  		revisionStr = fmt.Sprintf(" (%s)", targetRevision)
   274  	}
   275  
   276  	// check if we already have the revision locally (alters tasks)
   277  	revisionIsLocal := snapst.LastIndex(targetRevision) >= 0
   278  
   279  	prereq := st.NewTask("prerequisites", fmt.Sprintf(i18n.G("Ensure prerequisites for %q are available"), snapsup.InstanceName()))
   280  	prereq.Set("snap-setup", snapsup)
   281  
   282  	var prepare, prev *state.Task
   283  	fromStore := false
   284  	// if we have a local revision here we go back to that
   285  	if snapsup.SnapPath != "" || revisionIsLocal {
   286  		prepare = st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q%s"), snapsup.SnapPath, revisionStr))
   287  	} else {
   288  		fromStore = true
   289  		prepare = st.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q%s from channel %q"), snapsup.InstanceName(), revisionStr, snapsup.Channel))
   290  	}
   291  	prepare.Set("snap-setup", snapsup)
   292  	prepare.WaitFor(prereq)
   293  
   294  	tasks := []*state.Task{prereq, prepare}
   295  	addTask := func(t *state.Task) {
   296  		t.Set("snap-setup-task", prepare.ID())
   297  		t.WaitFor(prev)
   298  		tasks = append(tasks, t)
   299  	}
   300  	prev = prepare
   301  
   302  	var checkAsserts *state.Task
   303  	if fromStore {
   304  		// fetch and check assertions
   305  		checkAsserts = st.NewTask("validate-snap", fmt.Sprintf(i18n.G("Fetch and check assertions for snap %q%s"), snapsup.InstanceName(), revisionStr))
   306  		addTask(checkAsserts)
   307  		prev = checkAsserts
   308  	}
   309  
   310  	// mount
   311  	if !revisionIsLocal {
   312  		mount := st.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q%s"), snapsup.InstanceName(), revisionStr))
   313  		addTask(mount)
   314  		prev = mount
   315  	}
   316  
   317  	// run refresh hooks when updating existing snap, otherwise run install hook further down.
   318  	runRefreshHooks := (snapst.IsInstalled() && !snapsup.Flags.Revert)
   319  	if runRefreshHooks {
   320  		preRefreshHook := SetupPreRefreshHook(st, snapsup.InstanceName())
   321  		addTask(preRefreshHook)
   322  		prev = preRefreshHook
   323  	}
   324  
   325  	if snapst.IsInstalled() {
   326  		// unlink-current-snap (will stop services for copy-data)
   327  		stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), snapsup.InstanceName()))
   328  		stop.Set("stop-reason", snap.StopReasonRefresh)
   329  		addTask(stop)
   330  		prev = stop
   331  
   332  		removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), snapsup.InstanceName()))
   333  		addTask(removeAliases)
   334  		prev = removeAliases
   335  
   336  		unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapsup.InstanceName()))
   337  		addTask(unlink)
   338  		prev = unlink
   339  	}
   340  
   341  	if !release.OnClassic && (snapsup.Type == snap.TypeGadget || snapsup.Type == snap.TypeKernel) {
   342  		// XXX: gadget update currently for core systems only
   343  		gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q%s"), snapsup.Type, snapsup.InstanceName(), revisionStr))
   344  		addTask(gadgetUpdate)
   345  		prev = gadgetUpdate
   346  	}
   347  	if !release.OnClassic && snapsup.Type == snap.TypeGadget {
   348  		// kernel command line from gadget is for core systems only
   349  		gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from gadget %q%s"), snapsup.InstanceName(), revisionStr))
   350  		addTask(gadgetCmdline)
   351  		prev = gadgetCmdline
   352  	}
   353  
   354  	// copy-data (needs stopped services by unlink)
   355  	if !snapsup.Flags.Revert {
   356  		copyData := st.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapsup.InstanceName()))
   357  		addTask(copyData)
   358  		prev = copyData
   359  	}
   360  
   361  	// security
   362  	setupSecurity := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q%s security profiles"), snapsup.InstanceName(), revisionStr))
   363  	addTask(setupSecurity)
   364  	prev = setupSecurity
   365  
   366  	// finalize (wrappers+current symlink)
   367  	linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q%s available to the system"), snapsup.InstanceName(), revisionStr))
   368  	addTask(linkSnap)
   369  	prev = linkSnap
   370  
   371  	// auto-connections
   372  	autoConnect := st.NewTask("auto-connect", fmt.Sprintf(i18n.G("Automatically connect eligible plugs and slots of snap %q"), snapsup.InstanceName()))
   373  	addTask(autoConnect)
   374  	prev = autoConnect
   375  
   376  	// setup aliases
   377  	setAutoAliases := st.NewTask("set-auto-aliases", fmt.Sprintf(i18n.G("Set automatic aliases for snap %q"), snapsup.InstanceName()))
   378  	addTask(setAutoAliases)
   379  	prev = setAutoAliases
   380  
   381  	setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName()))
   382  	addTask(setupAliases)
   383  	prev = setupAliases
   384  
   385  	if !release.OnClassic && snapsup.Type == snap.TypeSnapd {
   386  		// only run for core devices and the snapd snap, run late enough
   387  		// so that the task is executed by the new snapd
   388  		bootConfigUpdate := st.NewTask("update-managed-boot-config", fmt.Sprintf(i18n.G("Update managed boot config assets from %q%s"), snapsup.InstanceName(), revisionStr))
   389  		addTask(bootConfigUpdate)
   390  		prev = bootConfigUpdate
   391  	}
   392  
   393  	if runRefreshHooks {
   394  		postRefreshHook := SetupPostRefreshHook(st, snapsup.InstanceName())
   395  		addTask(postRefreshHook)
   396  		prev = postRefreshHook
   397  	}
   398  
   399  	var installHook *state.Task
   400  	// only run install hook if installing the snap for the first time
   401  	if !snapst.IsInstalled() {
   402  		installHook = SetupInstallHook(st, snapsup.InstanceName())
   403  		addTask(installHook)
   404  		prev = installHook
   405  	}
   406  
   407  	// run new services
   408  	startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q%s services"), snapsup.InstanceName(), revisionStr))
   409  	addTask(startSnapServices)
   410  	prev = startSnapServices
   411  
   412  	// Do not do that if we are reverting to a local revision
   413  	if snapst.IsInstalled() && !snapsup.Flags.Revert {
   414  		var retain int
   415  		if err := config.NewTransaction(st).Get("core", "refresh.retain", &retain); err != nil {
   416  			// on classic we only keep 2 copies by default
   417  			if release.OnClassic {
   418  				retain = 2
   419  			} else {
   420  				retain = 3
   421  			}
   422  		}
   423  		retain-- //  we're adding one
   424  
   425  		seq := snapst.Sequence
   426  		currentIndex := snapst.LastIndex(snapst.Current)
   427  
   428  		// discard everything after "current" (we may have reverted to
   429  		// a previous versions earlier)
   430  		for i := currentIndex + 1; i < len(seq); i++ {
   431  			si := seq[i]
   432  			if si.Revision == targetRevision {
   433  				// but don't discard this one; its' the thing we're switching to!
   434  				continue
   435  			}
   436  			ts := removeInactiveRevision(st, snapsup.InstanceName(), si.SnapID, si.Revision, snapsup.Type)
   437  			ts.WaitFor(prev)
   438  			tasks = append(tasks, ts.Tasks()...)
   439  			prev = tasks[len(tasks)-1]
   440  		}
   441  
   442  		// make sure we're not scheduling the removal of the target
   443  		// revision in the case where the target revision is already in
   444  		// the sequence.
   445  		for i := 0; i < currentIndex; i++ {
   446  			si := seq[i]
   447  			if si.Revision == targetRevision {
   448  				// we do *not* want to removeInactiveRevision of this one
   449  				copy(seq[i:], seq[i+1:])
   450  				seq = seq[:len(seq)-1]
   451  				currentIndex--
   452  			}
   453  		}
   454  
   455  		// normal garbage collect
   456  		var inUse boot.InUseFunc
   457  		for i := 0; i <= currentIndex-retain; i++ {
   458  			if inUse == nil {
   459  				var err error
   460  				inUse, err = inUseCheck(snapsup.Type)
   461  				if err != nil {
   462  					return nil, err
   463  				}
   464  			}
   465  
   466  			si := seq[i]
   467  			if inUse(snapsup.InstanceName(), si.Revision) {
   468  				continue
   469  			}
   470  			ts := removeInactiveRevision(st, snapsup.InstanceName(), si.SnapID, si.Revision, snapsup.Type)
   471  			ts.WaitFor(prev)
   472  			tasks = append(tasks, ts.Tasks()...)
   473  			prev = tasks[len(tasks)-1]
   474  		}
   475  
   476  		addTask(st.NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", snapsup.InstanceName(), revisionStr)))
   477  	}
   478  
   479  	installSet := state.NewTaskSet(tasks...)
   480  	installSet.WaitAll(ts)
   481  	installSet.MarkEdge(prereq, BeginEdge)
   482  	installSet.MarkEdge(setupAliases, BeforeHooksEdge)
   483  	if installHook != nil {
   484  		installSet.MarkEdge(installHook, HooksEdge)
   485  	}
   486  	ts.AddAllWithEdges(installSet)
   487  	if checkAsserts != nil {
   488  		ts.MarkEdge(checkAsserts, DownloadAndChecksDoneEdge)
   489  	}
   490  
   491  	if flags&skipConfigure != 0 {
   492  		return installSet, nil
   493  	}
   494  
   495  	// we do not support configuration for bases or the "snapd" snap yet
   496  	if snapsup.Type != snap.TypeBase && snapsup.Type != snap.TypeSnapd {
   497  		confFlags := 0
   498  		notCore := snapsup.InstanceName() != "core"
   499  		hasSnapID := snapsup.SideInfo != nil && snapsup.SideInfo.SnapID != ""
   500  		if !snapst.IsInstalled() && hasSnapID && notCore {
   501  			// installation, run configure using the gadget defaults
   502  			// if available, system config defaults (attached to
   503  			// "core") are consumed only during seeding, via an
   504  			// explicit configure step separate from installing
   505  			confFlags |= UseConfigDefaults
   506  		}
   507  		configSet := ConfigureSnap(st, snapsup.InstanceName(), confFlags)
   508  		configSet.WaitAll(ts)
   509  		ts.AddAll(configSet)
   510  	}
   511  
   512  	healthCheck := CheckHealthHook(st, snapsup.InstanceName(), snapsup.Revision())
   513  	healthCheck.WaitAll(ts)
   514  	ts.AddTask(healthCheck)
   515  
   516  	return ts, nil
   517  }
   518  
   519  // ConfigureSnap returns a set of tasks to configure snapName as done during installation/refresh.
   520  func ConfigureSnap(st *state.State, snapName string, confFlags int) *state.TaskSet {
   521  	// This is slightly ugly, ideally we would check the type instead
   522  	// of hardcoding the name here. Unfortunately we do not have the
   523  	// type until we actually run the change.
   524  	if snapName == defaultCoreSnapName {
   525  		confFlags |= IgnoreHookError
   526  		confFlags |= TrackHookError
   527  	}
   528  	return Configure(st, snapName, nil, confFlags)
   529  }
   530  
   531  var Configure = func(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet {
   532  	panic("internal error: snapstate.Configure is unset")
   533  }
   534  
   535  var SetupInstallHook = func(st *state.State, snapName string) *state.Task {
   536  	panic("internal error: snapstate.SetupInstallHook is unset")
   537  }
   538  
   539  var SetupPreRefreshHook = func(st *state.State, snapName string) *state.Task {
   540  	panic("internal error: snapstate.SetupPreRefreshHook is unset")
   541  }
   542  
   543  var SetupPostRefreshHook = func(st *state.State, snapName string) *state.Task {
   544  	panic("internal error: snapstate.SetupPostRefreshHook is unset")
   545  }
   546  
   547  var SetupRemoveHook = func(st *state.State, snapName string) *state.Task {
   548  	panic("internal error: snapstate.SetupRemoveHook is unset")
   549  }
   550  
   551  var CheckHealthHook = func(st *state.State, snapName string, rev snap.Revision) *state.Task {
   552  	panic("internal error: snapstate.CheckHealthHook is unset")
   553  }
   554  
   555  var SetupGateAutoRefreshHook = func(st *state.State, snapName string, base, restart bool) *state.Task {
   556  	panic("internal error: snapstate.SetupAutoRefreshGatingHook is unset")
   557  }
   558  
   559  var generateSnapdWrappers = backend.GenerateSnapdWrappers
   560  
   561  // FinishRestart will return a Retry error if there is a pending restart
   562  // and a real error if anything went wrong (like a rollback across
   563  // restarts).
   564  // For snapd snap updates this will also rerun wrappers generation to fully
   565  // catch up with any change.
   566  func FinishRestart(task *state.Task, snapsup *SnapSetup) (err error) {
   567  	if ok, _ := task.State().Restarting(); ok {
   568  		// don't continue until we are in the restarted snapd
   569  		task.Logf("Waiting for automatic snapd restart...")
   570  		return &state.Retry{}
   571  	}
   572  
   573  	if snapsup.Type == snap.TypeSnapd {
   574  		if os.Getenv("SNAPD_REVERT_TO_REV") != "" {
   575  			return fmt.Errorf("there was a snapd rollback across the restart")
   576  		}
   577  
   578  		// if we have restarted and snapd was refreshed, then we need to generate
   579  		// snapd wrappers again with current snapd, as the logic of generating
   580  		// wrappers may have changed between previous and new snapd code.
   581  		if !release.OnClassic {
   582  			snapdInfo, err := snap.ReadCurrentInfo(snapsup.SnapName())
   583  			if err != nil {
   584  				return fmt.Errorf("cannot get current snapd snap info: %v", err)
   585  			}
   586  			// TODO: if future changes to wrappers need one more snapd restart,
   587  			// then it should be handled here as well.
   588  			if err := generateSnapdWrappers(snapdInfo); err != nil {
   589  				return err
   590  			}
   591  		}
   592  	}
   593  
   594  	// consider kernel and base
   595  
   596  	deviceCtx, err := DeviceCtx(task.State(), task, nil)
   597  	if err != nil {
   598  		return err
   599  	}
   600  
   601  	// Check if there was a rollback. A reboot can be triggered by:
   602  	// - core (old core16 world, system-reboot)
   603  	// - bootable base snap (new core18 world, system-reboot)
   604  	// - kernel
   605  	//
   606  	// On classic and in ephemeral run modes (like install, recover)
   607  	// there can never be a rollback so we can skip the check there.
   608  	//
   609  	// TODO: Detect "snapd" snap daemon-restarts here that
   610  	//       fallback into the old version (once we have
   611  	//       better snapd rollback support in core18).
   612  	if deviceCtx.RunMode() && !release.OnClassic {
   613  		// get the name of the name relevant for booting
   614  		// based on the given type
   615  		model := deviceCtx.Model()
   616  		var bootName string
   617  		switch snapsup.Type {
   618  		case snap.TypeKernel:
   619  			bootName = model.Kernel()
   620  		case snap.TypeOS, snap.TypeBase:
   621  			bootName = "core"
   622  			if model.Base() != "" {
   623  				bootName = model.Base()
   624  			}
   625  		default:
   626  			return nil
   627  		}
   628  		// if it is not a snap related to our booting we are not
   629  		// interested
   630  		if snapsup.InstanceName() != bootName {
   631  			return nil
   632  		}
   633  
   634  		// compare what we think is "current" for snapd with what
   635  		// actually booted. The bootloader may revert on a failed
   636  		// boot from a bad os/base/kernel to a good one and in this
   637  		// case we need to catch this and error accordingly
   638  		current, err := boot.GetCurrentBoot(snapsup.Type, deviceCtx)
   639  		if err == boot.ErrBootNameAndRevisionNotReady {
   640  			return &state.Retry{After: 5 * time.Second}
   641  		}
   642  		if err != nil {
   643  			return err
   644  		}
   645  		if snapsup.InstanceName() != current.SnapName() || snapsup.SideInfo.Revision != current.SnapRevision() {
   646  			// TODO: make sure this revision gets ignored for
   647  			//       automatic refreshes
   648  			return fmt.Errorf("cannot finish %s installation, there was a rollback across reboot", snapsup.InstanceName())
   649  		}
   650  	}
   651  
   652  	return nil
   653  }
   654  
   655  func contentAttr(attrer interfaces.Attrer) string {
   656  	var s string
   657  	err := attrer.Attr("content", &s)
   658  	if err != nil {
   659  		return ""
   660  	}
   661  	return s
   662  }
   663  
   664  func contentIfaceAvailable(st *state.State) map[string]bool {
   665  	repo := ifacerepo.Get(st)
   666  	contentSlots := repo.AllSlots("content")
   667  	avail := make(map[string]bool, len(contentSlots))
   668  	for _, slot := range contentSlots {
   669  		contentTag := contentAttr(slot)
   670  		if contentTag == "" {
   671  			continue
   672  		}
   673  		avail[contentTag] = true
   674  	}
   675  	return avail
   676  }
   677  
   678  // defaultContentPlugProviders takes a snap.Info and returns what
   679  // default providers there are.
   680  func defaultContentPlugProviders(st *state.State, info *snap.Info) []string {
   681  	needed := snap.NeededDefaultProviders(info)
   682  	if len(needed) == 0 {
   683  		return nil
   684  	}
   685  	avail := contentIfaceAvailable(st)
   686  	out := []string{}
   687  	for snapInstance, contentTags := range needed {
   688  		for _, contentTag := range contentTags {
   689  			if !avail[contentTag] {
   690  				out = append(out, snapInstance)
   691  				break
   692  			}
   693  		}
   694  	}
   695  	return out
   696  }
   697  
   698  // validateFeatureFlags validates the given snap only uses experimental
   699  // features that are enabled by the user.
   700  func validateFeatureFlags(st *state.State, info *snap.Info) error {
   701  	tr := config.NewTransaction(st)
   702  
   703  	if len(info.Layout) > 0 {
   704  		flag, err := features.Flag(tr, features.Layouts)
   705  		if err != nil {
   706  			return err
   707  		}
   708  		if !flag {
   709  			return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.layouts' to true")
   710  		}
   711  	}
   712  
   713  	if info.InstanceKey != "" {
   714  		flag, err := features.Flag(tr, features.ParallelInstances)
   715  		if err != nil {
   716  			return err
   717  		}
   718  		if !flag {
   719  			return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.parallel-instances' to true")
   720  		}
   721  	}
   722  
   723  	var hasUserService, usesDbusActivation bool
   724  	for _, app := range info.Apps {
   725  		if app.IsService() && app.DaemonScope == snap.UserDaemon {
   726  			hasUserService = true
   727  		}
   728  		if len(app.ActivatesOn) != 0 {
   729  			usesDbusActivation = true
   730  		}
   731  	}
   732  
   733  	if hasUserService {
   734  		flag, err := features.Flag(tr, features.UserDaemons)
   735  		if err != nil {
   736  			return err
   737  		}
   738  		if !flag {
   739  			return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.user-daemons' to true")
   740  		}
   741  		if !release.SystemctlSupportsUserUnits() {
   742  			return fmt.Errorf("user session daemons are not supported on this release")
   743  		}
   744  	}
   745  
   746  	if usesDbusActivation {
   747  		flag, err := features.Flag(tr, features.DbusActivation)
   748  		if err != nil {
   749  			return err
   750  		}
   751  		if !flag {
   752  			return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.dbus-activation' to true")
   753  		}
   754  	}
   755  
   756  	return nil
   757  }
   758  
   759  func ensureInstallPreconditions(st *state.State, info *snap.Info, flags Flags, snapst *SnapState) (Flags, error) {
   760  	// if snap is allowed to be devmode via the dangerous model and it's
   761  	// confinement is indeed devmode, promote the flags.DevMode to true
   762  	if flags.ApplySnapDevMode && info.NeedsDevMode() {
   763  		// TODO: what about jail-mode? will we also allow putting devmode
   764  		// snaps (i.e. snaps with snap.yaml with confinement: devmode) into
   765  		// strict confinement via the model assertion?
   766  		flags.DevMode = true
   767  	}
   768  
   769  	if flags.Classic && !info.NeedsClassic() {
   770  		// snap does not require classic confinement, silently drop the flag
   771  		flags.Classic = false
   772  	}
   773  
   774  	if err := validateInfoAndFlags(info, snapst, flags); err != nil {
   775  		return flags, err
   776  	}
   777  	if err := validateFeatureFlags(st, info); err != nil {
   778  		return flags, err
   779  	}
   780  	if err := checkDBusServiceConflicts(st, info); err != nil {
   781  		return flags, err
   782  	}
   783  	return flags, nil
   784  }
   785  
   786  // InstallPath returns a set of tasks for installing a snap from a file path
   787  // and the snap.Info for the given snap.
   788  //
   789  // Note that the state must be locked by the caller.
   790  // The provided SideInfo can contain just a name which results in a
   791  // local revision and sideloading, or full metadata in which case it
   792  // the snap will appear as installed from the store.
   793  func InstallPath(st *state.State, si *snap.SideInfo, path, instanceName, channel string, flags Flags) (*state.TaskSet, *snap.Info, error) {
   794  	if si.RealName == "" {
   795  		return nil, nil, fmt.Errorf("internal error: snap name to install %q not provided", path)
   796  	}
   797  
   798  	if instanceName == "" {
   799  		instanceName = si.RealName
   800  	}
   801  
   802  	deviceCtx, err := DeviceCtxFromState(st, nil)
   803  	if err != nil {
   804  		return nil, nil, err
   805  	}
   806  
   807  	var snapst SnapState
   808  	err = Get(st, instanceName, &snapst)
   809  	if err != nil && err != state.ErrNoState {
   810  		return nil, nil, err
   811  	}
   812  
   813  	if si.SnapID != "" {
   814  		if si.Revision.Unset() {
   815  			return nil, nil, fmt.Errorf("internal error: snap id set to install %q but revision is unset", path)
   816  		}
   817  	}
   818  
   819  	channel, err = resolveChannel(st, instanceName, snapst.TrackingChannel, channel, deviceCtx)
   820  	if err != nil {
   821  		return nil, nil, err
   822  	}
   823  
   824  	var instFlags int
   825  	if flags.SkipConfigure {
   826  		// extract it as a doInstall flag, this is not passed
   827  		// into SnapSetup
   828  		instFlags |= skipConfigure
   829  	}
   830  
   831  	// It is ok do open the snap file here because we either
   832  	// have side info or the user passed --dangerous
   833  	info, container, err := backend.OpenSnapFile(path, si)
   834  	if err != nil {
   835  		return nil, nil, err
   836  	}
   837  
   838  	if err := validateContainer(container, info, logger.Noticef); err != nil {
   839  		return nil, nil, err
   840  	}
   841  	if err := snap.ValidateInstanceName(instanceName); err != nil {
   842  		return nil, nil, fmt.Errorf("invalid instance name: %v", err)
   843  	}
   844  
   845  	snapName, instanceKey := snap.SplitInstanceName(instanceName)
   846  	if info.SnapName() != snapName {
   847  		return nil, nil, fmt.Errorf("cannot install snap %q, the name does not match the metadata %q", instanceName, info.SnapName())
   848  	}
   849  	info.InstanceKey = instanceKey
   850  
   851  	flags, err = ensureInstallPreconditions(st, info, flags, &snapst)
   852  	if err != nil {
   853  		return nil, nil, err
   854  	}
   855  	// this might be a refresh; check the epoch before proceeding
   856  	if err := earlyEpochCheck(info, &snapst); err != nil {
   857  		return nil, nil, err
   858  	}
   859  
   860  	snapsup := &SnapSetup{
   861  		Base:        info.Base,
   862  		Prereq:      defaultContentPlugProviders(st, info),
   863  		SideInfo:    si,
   864  		SnapPath:    path,
   865  		Channel:     channel,
   866  		Flags:       flags.ForSnapSetup(),
   867  		Type:        info.Type(),
   868  		PlugsOnly:   len(info.Slots) == 0,
   869  		InstanceKey: info.InstanceKey,
   870  	}
   871  
   872  	ts, err := doInstall(st, &snapst, snapsup, instFlags, "", inUseFor(deviceCtx))
   873  	return ts, info, err
   874  }
   875  
   876  // TryPath returns a set of tasks for trying a snap from a file path.
   877  // Note that the state must be locked by the caller.
   878  func TryPath(st *state.State, name, path string, flags Flags) (*state.TaskSet, error) {
   879  	flags.TryMode = true
   880  
   881  	ts, _, err := InstallPath(st, &snap.SideInfo{RealName: name}, path, "", "", flags)
   882  	return ts, err
   883  }
   884  
   885  // Install returns a set of tasks for installing a snap.
   886  // Note that the state must be locked by the caller.
   887  //
   888  // The returned TaskSet will contain a DownloadAndChecksDoneEdge.
   889  func Install(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) {
   890  	return InstallWithDeviceContext(ctx, st, name, opts, userID, flags, nil, "")
   891  }
   892  
   893  // InstallWithDeviceContext returns a set of tasks for installing a snap.
   894  // It will query for the snap with the given deviceCtx.
   895  // Note that the state must be locked by the caller.
   896  //
   897  // The returned TaskSet will contain a DownloadAndChecksDoneEdge.
   898  func InstallWithDeviceContext(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) {
   899  	if opts == nil {
   900  		opts = &RevisionOptions{}
   901  	}
   902  	if opts.CohortKey != "" && !opts.Revision.Unset() {
   903  		return nil, errors.New("cannot specify revision and cohort")
   904  	}
   905  
   906  	if opts.Channel == "" {
   907  		opts.Channel = "stable"
   908  	}
   909  
   910  	var snapst SnapState
   911  	err := Get(st, name, &snapst)
   912  	if err != nil && err != state.ErrNoState {
   913  		return nil, err
   914  	}
   915  	if snapst.IsInstalled() {
   916  		return nil, &snap.AlreadyInstalledError{Snap: name}
   917  	}
   918  	// need to have a model set before trying to talk the store
   919  	deviceCtx, err = DevicePastSeeding(st, deviceCtx)
   920  	if err != nil {
   921  		return nil, err
   922  	}
   923  
   924  	if err := snap.ValidateInstanceName(name); err != nil {
   925  		return nil, fmt.Errorf("invalid instance name: %v", err)
   926  	}
   927  
   928  	sar, err := installInfo(ctx, st, name, opts, userID, deviceCtx)
   929  	if err != nil {
   930  		return nil, err
   931  	}
   932  	info := sar.Info
   933  
   934  	if flags.RequireTypeBase && info.Type() != snap.TypeBase && info.Type() != snap.TypeOS {
   935  		return nil, fmt.Errorf("unexpected snap type %q, instead of 'base'", info.Type())
   936  	}
   937  
   938  	flags, err = ensureInstallPreconditions(st, info, flags, &snapst)
   939  	if err != nil {
   940  		return nil, err
   941  	}
   942  
   943  	tr := config.NewTransaction(st)
   944  	checkDiskSpaceInstall, err := features.Flag(tr, features.CheckDiskSpaceInstall)
   945  	if err != nil && !config.IsNoOption(err) {
   946  		return nil, err
   947  	}
   948  	if checkDiskSpaceInstall {
   949  		// check if there is enough disk space for requested snap and its
   950  		// prerequisites.
   951  		totalSize, err := installSize(st, []minimalInstallInfo{installSnapInfo{info}}, userID)
   952  		if err != nil {
   953  			return nil, err
   954  		}
   955  		requiredSpace := safetyMarginDiskSpace(totalSize)
   956  		path := dirs.SnapdStateDir(dirs.GlobalRootDir)
   957  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
   958  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
   959  				return nil, &InsufficientSpaceError{
   960  					Path:       path,
   961  					Snaps:      []string{info.InstanceName()},
   962  					ChangeKind: "install",
   963  				}
   964  			}
   965  			return nil, err
   966  		}
   967  	}
   968  
   969  	snapsup := &SnapSetup{
   970  		Channel:      opts.Channel,
   971  		Base:         info.Base,
   972  		Prereq:       defaultContentPlugProviders(st, info),
   973  		UserID:       userID,
   974  		Flags:        flags.ForSnapSetup(),
   975  		DownloadInfo: &info.DownloadInfo,
   976  		SideInfo:     &info.SideInfo,
   977  		Type:         info.Type(),
   978  		PlugsOnly:    len(info.Slots) == 0,
   979  		InstanceKey:  info.InstanceKey,
   980  		auxStoreInfo: auxStoreInfo{
   981  			Media:   info.Media,
   982  			Website: info.Website,
   983  		},
   984  		CohortKey: opts.CohortKey,
   985  	}
   986  
   987  	if sar.RedirectChannel != "" {
   988  		snapsup.Channel = sar.RedirectChannel
   989  	}
   990  
   991  	return doInstall(st, &snapst, snapsup, 0, fromChange, nil)
   992  }
   993  
   994  // InstallMany installs everything from the given list of names.
   995  // Note that the state must be locked by the caller.
   996  func InstallMany(st *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
   997  	// need to have a model set before trying to talk the store
   998  	deviceCtx, err := DevicePastSeeding(st, nil)
   999  	if err != nil {
  1000  		return nil, nil, err
  1001  	}
  1002  
  1003  	toInstall := make([]string, 0, len(names))
  1004  	for _, name := range names {
  1005  		var snapst SnapState
  1006  		err := Get(st, name, &snapst)
  1007  		if err != nil && err != state.ErrNoState {
  1008  			return nil, nil, err
  1009  		}
  1010  		if snapst.IsInstalled() {
  1011  			continue
  1012  		}
  1013  
  1014  		if err := snap.ValidateInstanceName(name); err != nil {
  1015  			return nil, nil, fmt.Errorf("invalid instance name: %v", err)
  1016  		}
  1017  
  1018  		toInstall = append(toInstall, name)
  1019  	}
  1020  
  1021  	user, err := userFromUserID(st, userID)
  1022  	if err != nil {
  1023  		return nil, nil, err
  1024  	}
  1025  
  1026  	installs, err := installCandidates(st, toInstall, "stable", user)
  1027  	if err != nil {
  1028  		return nil, nil, err
  1029  	}
  1030  
  1031  	tr := config.NewTransaction(st)
  1032  	checkDiskSpaceInstall, err := features.Flag(tr, features.CheckDiskSpaceInstall)
  1033  	if err != nil && !config.IsNoOption(err) {
  1034  		return nil, nil, err
  1035  	}
  1036  	if checkDiskSpaceInstall {
  1037  		// check if there is enough disk space for requested snaps and their
  1038  		// prerequisites.
  1039  		snapInfos := make([]minimalInstallInfo, len(installs))
  1040  		for i, sar := range installs {
  1041  			snapInfos[i] = installSnapInfo{sar.Info}
  1042  		}
  1043  		totalSize, err := installSize(st, snapInfos, userID)
  1044  		if err != nil {
  1045  			return nil, nil, err
  1046  		}
  1047  
  1048  		requiredSpace := safetyMarginDiskSpace(totalSize)
  1049  		path := dirs.SnapdStateDir(dirs.GlobalRootDir)
  1050  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
  1051  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
  1052  				return nil, nil, &InsufficientSpaceError{
  1053  					Path:       path,
  1054  					Snaps:      toInstall,
  1055  					ChangeKind: "install",
  1056  				}
  1057  			}
  1058  			return nil, nil, err
  1059  		}
  1060  	}
  1061  
  1062  	tasksets := make([]*state.TaskSet, 0, len(installs))
  1063  	for _, sar := range installs {
  1064  		info := sar.Info
  1065  		var snapst SnapState
  1066  		var flags Flags
  1067  
  1068  		flags, err := ensureInstallPreconditions(st, info, flags, &snapst)
  1069  		if err != nil {
  1070  			return nil, nil, err
  1071  		}
  1072  
  1073  		channel := "stable"
  1074  		if sar.RedirectChannel != "" {
  1075  			channel = sar.RedirectChannel
  1076  		}
  1077  
  1078  		snapsup := &SnapSetup{
  1079  			Channel:      channel,
  1080  			Base:         info.Base,
  1081  			Prereq:       defaultContentPlugProviders(st, info),
  1082  			UserID:       userID,
  1083  			Flags:        flags.ForSnapSetup(),
  1084  			DownloadInfo: &info.DownloadInfo,
  1085  			SideInfo:     &info.SideInfo,
  1086  			Type:         info.Type(),
  1087  			PlugsOnly:    len(info.Slots) == 0,
  1088  			InstanceKey:  info.InstanceKey,
  1089  		}
  1090  
  1091  		ts, err := doInstall(st, &snapst, snapsup, 0, "", inUseFor(deviceCtx))
  1092  		if err != nil {
  1093  			return nil, nil, err
  1094  		}
  1095  		ts.JoinLane(st.NewLane())
  1096  		tasksets = append(tasksets, ts)
  1097  	}
  1098  
  1099  	return toInstall, tasksets, nil
  1100  }
  1101  
  1102  // RefreshCandidates gets a list of candidates for update
  1103  // Note that the state must be locked by the caller.
  1104  func RefreshCandidates(st *state.State, user *auth.UserState) ([]*snap.Info, error) {
  1105  	updates, _, _, err := refreshCandidates(context.TODO(), st, nil, user, nil)
  1106  	return updates, err
  1107  }
  1108  
  1109  // ValidateRefreshes allows to hook validation into the handling of refresh candidates.
  1110  var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx DeviceContext) (validated []*snap.Info, err error)
  1111  
  1112  // UpdateMany updates everything from the given list of names that the
  1113  // store says is updateable. If the list is empty, update everything.
  1114  // Note that the state must be locked by the caller.
  1115  func UpdateMany(ctx context.Context, st *state.State, names []string, userID int, flags *Flags) ([]string, []*state.TaskSet, error) {
  1116  	return updateManyFiltered(ctx, st, names, userID, nil, flags, "")
  1117  }
  1118  
  1119  // updateFilter is the type of function that can be passed to
  1120  // updateManyFromChange so it filters the updates.
  1121  //
  1122  // If the filter returns true, the update for that snap proceeds. If
  1123  // it returns false, the snap is removed from the list of updates to
  1124  // consider.
  1125  type updateFilter func(*snap.Info, *SnapState) bool
  1126  
  1127  func updateManyFiltered(ctx context.Context, st *state.State, names []string, userID int, filter updateFilter, flags *Flags, fromChange string) ([]string, []*state.TaskSet, error) {
  1128  	if flags == nil {
  1129  		flags = &Flags{}
  1130  	}
  1131  	user, err := userFromUserID(st, userID)
  1132  	if err != nil {
  1133  		return nil, nil, err
  1134  	}
  1135  
  1136  	// need to have a model set before trying to talk the store
  1137  	deviceCtx, err := DevicePastSeeding(st, nil)
  1138  	if err != nil {
  1139  		return nil, nil, err
  1140  	}
  1141  
  1142  	refreshOpts := &store.RefreshOptions{IsAutoRefresh: flags.IsAutoRefresh}
  1143  	updates, stateByInstanceName, ignoreValidation, err := refreshCandidates(ctx, st, names, user, refreshOpts)
  1144  	if err != nil {
  1145  		return nil, nil, err
  1146  	}
  1147  
  1148  	if filter != nil {
  1149  		actual := updates[:0]
  1150  		for _, update := range updates {
  1151  			if filter(update, stateByInstanceName[update.InstanceName()]) {
  1152  				actual = append(actual, update)
  1153  			}
  1154  		}
  1155  		updates = actual
  1156  	}
  1157  
  1158  	if ValidateRefreshes != nil && len(updates) != 0 {
  1159  		updates, err = ValidateRefreshes(st, updates, ignoreValidation, userID, deviceCtx)
  1160  		if err != nil {
  1161  			// not doing "refresh all" report the error
  1162  			if len(names) != 0 {
  1163  				return nil, nil, err
  1164  			}
  1165  			// doing "refresh all", log the problems
  1166  			logger.Noticef("cannot refresh some snaps: %v", err)
  1167  		}
  1168  	}
  1169  
  1170  	params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) {
  1171  		snapst := stateByInstanceName[update.InstanceName()]
  1172  		// setting options to what's in state as multi-refresh doesn't let you change these
  1173  		opts := &RevisionOptions{
  1174  			Channel:   snapst.TrackingChannel,
  1175  			CohortKey: snapst.CohortKey,
  1176  		}
  1177  		return opts, snapst.Flags, snapst
  1178  
  1179  	}
  1180  
  1181  	toUpdate := make([]minimalInstallInfo, len(updates))
  1182  	for i, up := range updates {
  1183  		toUpdate[i] = installSnapInfo{up}
  1184  	}
  1185  
  1186  	tr := config.NewTransaction(st)
  1187  	checkDiskSpaceRefresh, err := features.Flag(tr, features.CheckDiskSpaceRefresh)
  1188  	if err != nil && !config.IsNoOption(err) {
  1189  		return nil, nil, err
  1190  	}
  1191  	if checkDiskSpaceRefresh {
  1192  		// check if there is enough disk space for requested snap and its
  1193  		// prerequisites.
  1194  		totalSize, err := installSize(st, toUpdate, userID)
  1195  		if err != nil {
  1196  			return nil, nil, err
  1197  		}
  1198  		requiredSpace := safetyMarginDiskSpace(totalSize)
  1199  		path := dirs.SnapdStateDir(dirs.GlobalRootDir)
  1200  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
  1201  			snaps := make([]string, len(updates))
  1202  			for i, up := range updates {
  1203  				snaps[i] = up.InstanceName()
  1204  			}
  1205  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
  1206  				return nil, nil, &InsufficientSpaceError{
  1207  					Path:       path,
  1208  					Snaps:      snaps,
  1209  					ChangeKind: "refresh",
  1210  				}
  1211  			}
  1212  			return nil, nil, err
  1213  		}
  1214  	}
  1215  
  1216  	updated, tasksets, err := doUpdate(ctx, st, names, toUpdate, params, userID, flags, deviceCtx, fromChange)
  1217  	if err != nil {
  1218  		return nil, nil, err
  1219  	}
  1220  	tasksets = finalizeUpdate(st, tasksets, len(updates) > 0, updated, userID, flags)
  1221  	return updated, tasksets, nil
  1222  }
  1223  
  1224  func doUpdate(ctx context.Context, st *state.State, names []string, updates []minimalInstallInfo, params updateParamsFunc, userID int, globalFlags *Flags, deviceCtx DeviceContext, fromChange string) ([]string, []*state.TaskSet, error) {
  1225  	if globalFlags == nil {
  1226  		globalFlags = &Flags{}
  1227  	}
  1228  
  1229  	tasksets := make([]*state.TaskSet, 0, len(updates)+2) // 1 for auto-aliases, 1 for re-refresh
  1230  
  1231  	refreshAll := len(names) == 0
  1232  	var nameSet map[string]bool
  1233  	if len(names) != 0 {
  1234  		nameSet = make(map[string]bool, len(names))
  1235  		for _, name := range names {
  1236  			nameSet[name] = true
  1237  		}
  1238  	}
  1239  
  1240  	newAutoAliases, mustPruneAutoAliases, transferTargets, err := autoAliasesUpdate(st, names, updates)
  1241  	if err != nil {
  1242  		return nil, nil, err
  1243  	}
  1244  
  1245  	reportUpdated := make(map[string]bool, len(updates))
  1246  	var pruningAutoAliasesTs *state.TaskSet
  1247  
  1248  	if len(mustPruneAutoAliases) != 0 {
  1249  		var err error
  1250  		pruningAutoAliasesTs, err = applyAutoAliasesDelta(st, mustPruneAutoAliases, "prune", refreshAll, fromChange, func(snapName string, _ *state.TaskSet) {
  1251  			if nameSet[snapName] {
  1252  				reportUpdated[snapName] = true
  1253  			}
  1254  		})
  1255  		if err != nil {
  1256  			return nil, nil, err
  1257  		}
  1258  		tasksets = append(tasksets, pruningAutoAliasesTs)
  1259  	}
  1260  
  1261  	// wait for the auto-alias prune tasks as needed
  1262  	scheduleUpdate := func(snapName string, ts *state.TaskSet) {
  1263  		if pruningAutoAliasesTs != nil && (mustPruneAutoAliases[snapName] != nil || transferTargets[snapName]) {
  1264  			ts.WaitAll(pruningAutoAliasesTs)
  1265  		}
  1266  		reportUpdated[snapName] = true
  1267  	}
  1268  
  1269  	// first snapd, core, bases, then rest
  1270  	sort.Stable(byType(updates))
  1271  	prereqs := make(map[string]*state.TaskSet)
  1272  	waitPrereq := func(ts *state.TaskSet, prereqName string) {
  1273  		preTs := prereqs[prereqName]
  1274  		if preTs != nil {
  1275  			ts.WaitAll(preTs)
  1276  		}
  1277  	}
  1278  	var kernelTs, gadgetTs *state.TaskSet
  1279  
  1280  	// updates is sorted by kind so this will process first core
  1281  	// and bases and then other snaps
  1282  	for _, update := range updates {
  1283  		snapsup, snapst, err := update.(readyUpdateInfo).SnapSetupForUpdate(st, params, userID, globalFlags)
  1284  		if err != nil {
  1285  			if refreshAll {
  1286  				logger.Noticef("cannot update %q: %v", update.InstanceName(), err)
  1287  				continue
  1288  			}
  1289  			return nil, nil, err
  1290  		}
  1291  
  1292  		ts, err := doInstall(st, snapst, snapsup, 0, fromChange, inUseFor(deviceCtx))
  1293  		if err != nil {
  1294  			if refreshAll {
  1295  				// doing "refresh all", just skip this snap
  1296  				logger.Noticef("cannot refresh snap %q: %v", update.InstanceName(), err)
  1297  				continue
  1298  			}
  1299  			return nil, nil, err
  1300  		}
  1301  		ts.JoinLane(st.NewLane())
  1302  
  1303  		// because of the sorting of updates we fill prereqs
  1304  		// first (if branch) and only then use it to setup
  1305  		// waits (else branch)
  1306  		if t := update.Type(); t == snap.TypeOS || t == snap.TypeBase || t == snap.TypeSnapd {
  1307  			// prereq types come first in updates, we
  1308  			// also assume bases don't have hooks, otherwise
  1309  			// they would need to wait on core or snapd
  1310  			prereqs[update.InstanceName()] = ts
  1311  		} else {
  1312  			// prereqs were processed already, wait for
  1313  			// them as necessary for the other kind of
  1314  			// snaps
  1315  			waitPrereq(ts, defaultCoreSnapName)
  1316  			waitPrereq(ts, "snapd")
  1317  			if update.SnapBase() != "" {
  1318  				waitPrereq(ts, update.SnapBase())
  1319  			}
  1320  		}
  1321  		// keep track of kernel/gadget udpates
  1322  		switch update.Type() {
  1323  		case snap.TypeKernel:
  1324  			kernelTs = ts
  1325  		case snap.TypeGadget:
  1326  			gadgetTs = ts
  1327  		}
  1328  
  1329  		scheduleUpdate(update.InstanceName(), ts)
  1330  		tasksets = append(tasksets, ts)
  1331  	}
  1332  	// Kernel must wait for gadget because the gadget may define
  1333  	// new "$kernel:refs". Sorting the other way is impossible
  1334  	// because a kernel with new kernel-assets would never refresh
  1335  	// because the matching gadget could never get installed
  1336  	// because the gadget always waits for the kernel and if the
  1337  	// kernel aborts the wait tasks (the gadget) is put on "Hold".
  1338  	if kernelTs != nil && gadgetTs != nil {
  1339  		kernelTs.WaitAll(gadgetTs)
  1340  	}
  1341  
  1342  	if len(newAutoAliases) != 0 {
  1343  		addAutoAliasesTs, err := applyAutoAliasesDelta(st, newAutoAliases, "refresh", refreshAll, fromChange, scheduleUpdate)
  1344  		if err != nil {
  1345  			return nil, nil, err
  1346  		}
  1347  		tasksets = append(tasksets, addAutoAliasesTs)
  1348  	}
  1349  
  1350  	updated := make([]string, 0, len(reportUpdated))
  1351  	for name := range reportUpdated {
  1352  		updated = append(updated, name)
  1353  	}
  1354  
  1355  	return updated, tasksets, nil
  1356  }
  1357  
  1358  func finalizeUpdate(st *state.State, tasksets []*state.TaskSet, hasUpdates bool, updated []string, userID int, globalFlags *Flags) []*state.TaskSet {
  1359  	if hasUpdates && !globalFlags.NoReRefresh {
  1360  		// re-refresh will check the lanes to decide what to
  1361  		// _actually_ re-refresh, but it'll be a subset of updated
  1362  		// (and equal to updated if nothing goes wrong)
  1363  		rerefresh := st.NewTask("check-rerefresh", fmt.Sprintf("Consider re-refresh of %s", strutil.Quoted(updated)))
  1364  		rerefresh.Set("rerefresh-setup", reRefreshSetup{
  1365  			UserID: userID,
  1366  			Flags:  globalFlags,
  1367  		})
  1368  		tasksets = append(tasksets, state.NewTaskSet(rerefresh))
  1369  	}
  1370  	return tasksets
  1371  }
  1372  
  1373  func applyAutoAliasesDelta(st *state.State, delta map[string][]string, op string, refreshAll bool, fromChange string, linkTs func(instanceName string, ts *state.TaskSet)) (*state.TaskSet, error) {
  1374  	applyTs := state.NewTaskSet()
  1375  	kind := "refresh-aliases"
  1376  	msg := i18n.G("Refresh aliases for snap %q")
  1377  	if op == "prune" {
  1378  		kind = "prune-auto-aliases"
  1379  		msg = i18n.G("Prune automatic aliases for snap %q")
  1380  	}
  1381  	for instanceName, aliases := range delta {
  1382  		if err := checkChangeConflictIgnoringOneChange(st, instanceName, nil, fromChange); err != nil {
  1383  			if refreshAll {
  1384  				// doing "refresh all", just skip this snap
  1385  				logger.Noticef("cannot %s automatic aliases for snap %q: %v", op, instanceName, err)
  1386  				continue
  1387  			}
  1388  			return nil, err
  1389  		}
  1390  
  1391  		snapName, instanceKey := snap.SplitInstanceName(instanceName)
  1392  		snapsup := &SnapSetup{
  1393  			SideInfo:    &snap.SideInfo{RealName: snapName},
  1394  			InstanceKey: instanceKey,
  1395  		}
  1396  		alias := st.NewTask(kind, fmt.Sprintf(msg, snapsup.InstanceName()))
  1397  		alias.Set("snap-setup", &snapsup)
  1398  		if op == "prune" {
  1399  			alias.Set("aliases", aliases)
  1400  		}
  1401  		ts := state.NewTaskSet(alias)
  1402  		linkTs(instanceName, ts)
  1403  		applyTs.AddAll(ts)
  1404  	}
  1405  	return applyTs, nil
  1406  }
  1407  
  1408  func autoAliasesUpdate(st *state.State, names []string, updates []minimalInstallInfo) (changed map[string][]string, mustPrune map[string][]string, transferTargets map[string]bool, err error) {
  1409  	changed, dropped, err := autoAliasesDelta(st, nil)
  1410  	if err != nil {
  1411  		if len(names) != 0 {
  1412  			// not "refresh all", error
  1413  			return nil, nil, nil, err
  1414  		}
  1415  		// log and continue
  1416  		logger.Noticef("cannot find the delta for automatic aliases for some snaps: %v", err)
  1417  	}
  1418  
  1419  	refreshAll := len(names) == 0
  1420  
  1421  	// dropped alias -> snapName
  1422  	droppedAliases := make(map[string][]string, len(dropped))
  1423  	for instanceName, aliases := range dropped {
  1424  		for _, alias := range aliases {
  1425  			droppedAliases[alias] = append(droppedAliases[alias], instanceName)
  1426  		}
  1427  	}
  1428  
  1429  	// filter changed considering only names if set:
  1430  	// we add auto-aliases only for mentioned snaps
  1431  	if !refreshAll && len(changed) != 0 {
  1432  		filteredChanged := make(map[string][]string, len(changed))
  1433  		for _, name := range names {
  1434  			if changed[name] != nil {
  1435  				filteredChanged[name] = changed[name]
  1436  			}
  1437  		}
  1438  		changed = filteredChanged
  1439  	}
  1440  
  1441  	// mark snaps that are sources or target of transfers
  1442  	transferSources := make(map[string]bool, len(dropped))
  1443  	transferTargets = make(map[string]bool, len(changed))
  1444  	for instanceName, aliases := range changed {
  1445  		for _, alias := range aliases {
  1446  			if sources := droppedAliases[alias]; len(sources) != 0 {
  1447  				transferTargets[instanceName] = true
  1448  				for _, source := range sources {
  1449  					transferSources[source] = true
  1450  				}
  1451  			}
  1452  		}
  1453  	}
  1454  
  1455  	// snaps with updates
  1456  	updating := make(map[string]bool, len(updates))
  1457  	for _, info := range updates {
  1458  		updating[info.InstanceName()] = true
  1459  	}
  1460  
  1461  	// add explicitly auto-aliases only for snaps that are not updated
  1462  	for instanceName := range changed {
  1463  		if updating[instanceName] {
  1464  			delete(changed, instanceName)
  1465  		}
  1466  	}
  1467  
  1468  	// prune explicitly auto-aliases only for snaps that are mentioned
  1469  	// and not updated OR the source of transfers
  1470  	mustPrune = make(map[string][]string, len(dropped))
  1471  	for instanceName := range transferSources {
  1472  		mustPrune[instanceName] = dropped[instanceName]
  1473  	}
  1474  	if refreshAll {
  1475  		for instanceName, aliases := range dropped {
  1476  			if !updating[instanceName] {
  1477  				mustPrune[instanceName] = aliases
  1478  			}
  1479  		}
  1480  	} else {
  1481  		for _, name := range names {
  1482  			if !updating[name] && dropped[name] != nil {
  1483  				mustPrune[name] = dropped[name]
  1484  			}
  1485  		}
  1486  	}
  1487  
  1488  	return changed, mustPrune, transferTargets, nil
  1489  }
  1490  
  1491  // resolveChannel returns the effective channel to use, based on the requested
  1492  // channel and constrains set by device model, or an error if switching to
  1493  // requested channel is forbidden.
  1494  func resolveChannel(st *state.State, snapName, oldChannel, newChannel string, deviceCtx DeviceContext) (effectiveChannel string, err error) {
  1495  	if newChannel == "" {
  1496  		return "", nil
  1497  	}
  1498  
  1499  	// ensure we do not switch away from the kernel-track in the model
  1500  	model := deviceCtx.Model()
  1501  
  1502  	var pinnedTrack, which string
  1503  	if snapName == model.Kernel() && model.KernelTrack() != "" {
  1504  		pinnedTrack, which = model.KernelTrack(), "kernel"
  1505  	}
  1506  	if snapName == model.Gadget() && model.GadgetTrack() != "" {
  1507  		pinnedTrack, which = model.GadgetTrack(), "gadget"
  1508  	}
  1509  
  1510  	if pinnedTrack == "" {
  1511  		// no pinned track
  1512  		return channel.Resolve(oldChannel, newChannel)
  1513  	}
  1514  
  1515  	// channel name is valid and consist of risk level or
  1516  	// risk/branch only, do the right thing and default to risk (or
  1517  	// risk/branch) within the pinned track
  1518  	resChannel, err := channel.ResolvePinned(pinnedTrack, newChannel)
  1519  	if err == channel.ErrPinnedTrackSwitch {
  1520  		// switching to a different track is not allowed
  1521  		return "", fmt.Errorf("cannot switch from %s track %q as specified for the (device) model to %q", which, pinnedTrack, newChannel)
  1522  
  1523  	}
  1524  	if err != nil {
  1525  		return "", err
  1526  	}
  1527  	return resChannel, nil
  1528  }
  1529  
  1530  var errRevisionSwitch = errors.New("cannot switch revision")
  1531  
  1532  func switchSummary(snap, chanFrom, chanTo, cohFrom, cohTo string) string {
  1533  	if cohFrom != cohTo {
  1534  		if cohTo == "" {
  1535  			// leave cohort
  1536  			if chanFrom == chanTo {
  1537  				return fmt.Sprintf(i18n.G("Switch snap %q away from cohort %q"),
  1538  					snap, strutil.ElliptLeft(cohFrom, 10))
  1539  			}
  1540  			if chanFrom == "" {
  1541  				return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and away from cohort %q"),
  1542  					snap, chanTo, strutil.ElliptLeft(cohFrom, 10),
  1543  				)
  1544  			}
  1545  			return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and away from cohort %q"),
  1546  				snap, chanFrom, chanTo, strutil.ElliptLeft(cohFrom, 10),
  1547  			)
  1548  		}
  1549  		if cohFrom == "" {
  1550  			// moving into a cohort
  1551  			if chanFrom == chanTo {
  1552  				return fmt.Sprintf(i18n.G("Switch snap %q from no cohort to %q"),
  1553  					snap, strutil.ElliptLeft(cohTo, 10))
  1554  			}
  1555  			if chanFrom == "" {
  1556  				return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and from no cohort to %q"),
  1557  					snap, chanTo, strutil.ElliptLeft(cohTo, 10),
  1558  				)
  1559  			}
  1560  			// chanTo == "" is not interesting
  1561  			return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and from no cohort to %q"),
  1562  				snap, chanFrom, chanTo, strutil.ElliptLeft(cohTo, 10),
  1563  			)
  1564  		}
  1565  		if chanFrom == chanTo {
  1566  			return fmt.Sprintf(i18n.G("Switch snap %q from cohort %q to %q"),
  1567  				snap, strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10))
  1568  		}
  1569  		if chanFrom == "" {
  1570  			return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and from cohort %q to %q"),
  1571  				snap, chanTo, strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10),
  1572  			)
  1573  		}
  1574  		return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and from cohort %q to %q"),
  1575  			snap, chanFrom, chanTo,
  1576  			strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10),
  1577  		)
  1578  	}
  1579  
  1580  	if chanFrom == "" {
  1581  		return fmt.Sprintf(i18n.G("Switch snap %q to channel %q"),
  1582  			snap, chanTo)
  1583  	}
  1584  	if chanFrom != chanTo {
  1585  		return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q"),
  1586  			snap, chanFrom, chanTo)
  1587  	}
  1588  	// a no-change switch is accepted for idempotency
  1589  	return "No change switch (no-op)"
  1590  }
  1591  
  1592  // Switch switches a snap to a new channel and/or cohort
  1593  func Switch(st *state.State, name string, opts *RevisionOptions) (*state.TaskSet, error) {
  1594  	if opts == nil {
  1595  		opts = &RevisionOptions{}
  1596  	}
  1597  	if !opts.Revision.Unset() {
  1598  		return nil, errRevisionSwitch
  1599  	}
  1600  	var snapst SnapState
  1601  	err := Get(st, name, &snapst)
  1602  	if err != nil && err != state.ErrNoState {
  1603  		return nil, err
  1604  	}
  1605  	if !snapst.IsInstalled() {
  1606  		return nil, &snap.NotInstalledError{Snap: name}
  1607  	}
  1608  
  1609  	if err := CheckChangeConflict(st, name, nil); err != nil {
  1610  		return nil, err
  1611  	}
  1612  
  1613  	deviceCtx, err := DeviceCtxFromState(st, nil)
  1614  	if err != nil {
  1615  		return nil, err
  1616  	}
  1617  
  1618  	opts.Channel, err = resolveChannel(st, name, snapst.TrackingChannel, opts.Channel, deviceCtx)
  1619  	if err != nil {
  1620  		return nil, err
  1621  	}
  1622  
  1623  	snapsup := &SnapSetup{
  1624  		SideInfo:    snapst.CurrentSideInfo(),
  1625  		InstanceKey: snapst.InstanceKey,
  1626  		// set the from state (i.e. no change), they are overridden from opts as needed below
  1627  		CohortKey: snapst.CohortKey,
  1628  		Channel:   snapst.TrackingChannel,
  1629  	}
  1630  
  1631  	if opts.Channel != "" {
  1632  		snapsup.Channel = opts.Channel
  1633  	}
  1634  	if opts.CohortKey != "" {
  1635  		snapsup.CohortKey = opts.CohortKey
  1636  	}
  1637  	if opts.LeaveCohort {
  1638  		snapsup.CohortKey = ""
  1639  	}
  1640  
  1641  	summary := switchSummary(snapsup.InstanceName(), snapst.TrackingChannel, snapsup.Channel, snapst.CohortKey, snapsup.CohortKey)
  1642  	switchSnap := st.NewTask("switch-snap", summary)
  1643  	switchSnap.Set("snap-setup", &snapsup)
  1644  
  1645  	return state.NewTaskSet(switchSnap), nil
  1646  }
  1647  
  1648  // RevisionOptions control the selection of a snap revision.
  1649  type RevisionOptions struct {
  1650  	Channel     string
  1651  	Revision    snap.Revision
  1652  	CohortKey   string
  1653  	LeaveCohort bool
  1654  }
  1655  
  1656  // Update initiates a change updating a snap.
  1657  // Note that the state must be locked by the caller.
  1658  //
  1659  // The returned TaskSet will contain a DownloadAndChecksDoneEdge.
  1660  func Update(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) {
  1661  	return UpdateWithDeviceContext(st, name, opts, userID, flags, nil, "")
  1662  }
  1663  
  1664  // UpdateWithDeviceContext initiates a change updating a snap.
  1665  // It will query for the snap with the given deviceCtx.
  1666  // Note that the state must be locked by the caller.
  1667  //
  1668  // The returned TaskSet will contain a DownloadAndChecksDoneEdge.
  1669  func UpdateWithDeviceContext(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) {
  1670  	if opts == nil {
  1671  		opts = &RevisionOptions{}
  1672  	}
  1673  	var snapst SnapState
  1674  	err := Get(st, name, &snapst)
  1675  	if err != nil && err != state.ErrNoState {
  1676  		return nil, err
  1677  	}
  1678  	if !snapst.IsInstalled() {
  1679  		return nil, &snap.NotInstalledError{Snap: name}
  1680  	}
  1681  
  1682  	// FIXME: snaps that are not active are skipped for now
  1683  	//        until we know what we want to do
  1684  	if !snapst.Active {
  1685  		return nil, fmt.Errorf("refreshing disabled snap %q not supported", name)
  1686  	}
  1687  
  1688  	// need to have a model set before trying to talk the store
  1689  	deviceCtx, err = DevicePastSeeding(st, deviceCtx)
  1690  	if err != nil {
  1691  		return nil, err
  1692  	}
  1693  
  1694  	opts.Channel, err = resolveChannel(st, name, snapst.TrackingChannel, opts.Channel, deviceCtx)
  1695  	if err != nil {
  1696  		return nil, err
  1697  	}
  1698  
  1699  	if opts.Channel == "" {
  1700  		// default to tracking the same channel
  1701  		opts.Channel = snapst.TrackingChannel
  1702  	}
  1703  	if opts.CohortKey == "" {
  1704  		// default to being in the same cohort
  1705  		opts.CohortKey = snapst.CohortKey
  1706  	}
  1707  	if opts.LeaveCohort {
  1708  		opts.CohortKey = ""
  1709  	}
  1710  
  1711  	// TODO: make flags be per revision to avoid this logic (that
  1712  	//       leaves corner cases all over the place)
  1713  	if !(flags.JailMode || flags.DevMode) {
  1714  		flags.Classic = flags.Classic || snapst.Flags.Classic
  1715  	}
  1716  
  1717  	var updates []*snap.Info
  1718  	info, infoErr := infoForUpdate(st, &snapst, name, opts, userID, flags, deviceCtx)
  1719  	switch infoErr {
  1720  	case nil:
  1721  		updates = append(updates, info)
  1722  	case store.ErrNoUpdateAvailable:
  1723  		// there may be some new auto-aliases
  1724  	default:
  1725  		return nil, infoErr
  1726  	}
  1727  
  1728  	toUpdate := make([]minimalInstallInfo, len(updates))
  1729  	for i, up := range updates {
  1730  		toUpdate[i] = installSnapInfo{up}
  1731  	}
  1732  
  1733  	tr := config.NewTransaction(st)
  1734  	checkDiskSpaceRefresh, err := features.Flag(tr, features.CheckDiskSpaceRefresh)
  1735  	if err != nil && !config.IsNoOption(err) {
  1736  		return nil, err
  1737  	}
  1738  	if checkDiskSpaceRefresh {
  1739  		// check if there is enough disk space for requested snap and its
  1740  		// prerequisites.
  1741  		totalSize, err := installSize(st, toUpdate, userID)
  1742  		if err != nil {
  1743  			return nil, err
  1744  		}
  1745  		requiredSpace := safetyMarginDiskSpace(totalSize)
  1746  		path := dirs.SnapdStateDir(dirs.GlobalRootDir)
  1747  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
  1748  			snaps := make([]string, len(updates))
  1749  			for i, up := range updates {
  1750  				snaps[i] = up.InstanceName()
  1751  			}
  1752  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
  1753  				return nil, &InsufficientSpaceError{
  1754  					Path:       path,
  1755  					Snaps:      snaps,
  1756  					ChangeKind: "refresh",
  1757  				}
  1758  			}
  1759  			return nil, err
  1760  		}
  1761  	}
  1762  
  1763  	params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) {
  1764  		return opts, flags, &snapst
  1765  	}
  1766  
  1767  	_, tts, err := doUpdate(context.TODO(), st, []string{name}, toUpdate, params, userID, &flags, deviceCtx, fromChange)
  1768  	if err != nil {
  1769  		return nil, err
  1770  	}
  1771  
  1772  	// see if we need to switch the channel or cohort, or toggle ignore-validation
  1773  	switchChannel := snapst.TrackingChannel != opts.Channel
  1774  	switchCohortKey := snapst.CohortKey != opts.CohortKey
  1775  	toggleIgnoreValidation := snapst.IgnoreValidation != flags.IgnoreValidation
  1776  	if infoErr == store.ErrNoUpdateAvailable && (switchChannel || switchCohortKey || toggleIgnoreValidation) {
  1777  		if err := checkChangeConflictIgnoringOneChange(st, name, nil, fromChange); err != nil {
  1778  			return nil, err
  1779  		}
  1780  
  1781  		snapsup := &SnapSetup{
  1782  			SideInfo:    snapst.CurrentSideInfo(),
  1783  			Flags:       snapst.Flags.ForSnapSetup(),
  1784  			InstanceKey: snapst.InstanceKey,
  1785  			CohortKey:   opts.CohortKey,
  1786  		}
  1787  
  1788  		if switchChannel || switchCohortKey {
  1789  			// update the tracked channel and cohort
  1790  			snapsup.Channel = opts.Channel
  1791  			snapsup.CohortKey = opts.CohortKey
  1792  			// Update the current snap channel as well. This ensures that
  1793  			// the UI displays the right values.
  1794  			snapsup.SideInfo.Channel = opts.Channel
  1795  
  1796  			summary := switchSummary(snapsup.InstanceName(), snapst.TrackingChannel, opts.Channel, snapst.CohortKey, opts.CohortKey)
  1797  			switchSnap := st.NewTask("switch-snap-channel", summary)
  1798  			switchSnap.Set("snap-setup", &snapsup)
  1799  
  1800  			switchSnapTs := state.NewTaskSet(switchSnap)
  1801  			for _, ts := range tts {
  1802  				switchSnapTs.WaitAll(ts)
  1803  			}
  1804  			tts = append(tts, switchSnapTs)
  1805  		}
  1806  
  1807  		if toggleIgnoreValidation {
  1808  			snapsup.IgnoreValidation = flags.IgnoreValidation
  1809  			toggle := st.NewTask("toggle-snap-flags", fmt.Sprintf(i18n.G("Toggle snap %q flags"), snapsup.InstanceName()))
  1810  			toggle.Set("snap-setup", &snapsup)
  1811  
  1812  			toggleTs := state.NewTaskSet(toggle)
  1813  			for _, ts := range tts {
  1814  				toggleTs.WaitAll(ts)
  1815  			}
  1816  			tts = append(tts, toggleTs)
  1817  		}
  1818  	}
  1819  
  1820  	if len(tts) == 0 && len(updates) == 0 {
  1821  		// really nothing to do, return the original no-update-available error
  1822  		return nil, infoErr
  1823  	}
  1824  
  1825  	tts = finalizeUpdate(st, tts, len(updates) > 0, []string{name}, userID, &flags)
  1826  
  1827  	flat := state.NewTaskSet()
  1828  	for _, ts := range tts {
  1829  		// The tasksets we get from "doUpdate" contain important
  1830  		// "TaskEdge" information that is needed for "Remodel".
  1831  		// To preserve those we need to use "AddAllWithEdges()".
  1832  		if err := flat.AddAllWithEdges(ts); err != nil {
  1833  			return nil, err
  1834  		}
  1835  	}
  1836  	return flat, nil
  1837  }
  1838  
  1839  func infoForUpdate(st *state.State, snapst *SnapState, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext) (*snap.Info, error) {
  1840  	if opts.Revision.Unset() {
  1841  		// good ol' refresh
  1842  		info, err := updateInfo(st, snapst, opts, userID, flags, deviceCtx)
  1843  		if err != nil {
  1844  			return nil, err
  1845  		}
  1846  		if ValidateRefreshes != nil && !flags.IgnoreValidation {
  1847  			_, err := ValidateRefreshes(st, []*snap.Info{info}, nil, userID, deviceCtx)
  1848  			if err != nil {
  1849  				return nil, err
  1850  			}
  1851  		}
  1852  		return info, nil
  1853  	}
  1854  	var sideInfo *snap.SideInfo
  1855  	for _, si := range snapst.Sequence {
  1856  		if si.Revision == opts.Revision {
  1857  			sideInfo = si
  1858  			break
  1859  		}
  1860  	}
  1861  	if sideInfo == nil {
  1862  		// refresh from given revision from store
  1863  		return updateToRevisionInfo(st, snapst, opts.Revision, userID, deviceCtx)
  1864  	}
  1865  
  1866  	// refresh-to-local, this assumes the snap revision is mounted
  1867  	return readInfo(name, sideInfo, errorOnBroken)
  1868  }
  1869  
  1870  // AutoRefreshAssertions allows to hook fetching of important assertions
  1871  // into the Autorefresh function.
  1872  var AutoRefreshAssertions func(st *state.State, userID int) error
  1873  
  1874  // AutoRefresh is the wrapper that will do a refresh of all the installed
  1875  // snaps on the system. In addition to that it will also refresh important
  1876  // assertions.
  1877  func AutoRefresh(ctx context.Context, st *state.State) ([]string, []*state.TaskSet, error) {
  1878  	userID := 0
  1879  
  1880  	if AutoRefreshAssertions != nil {
  1881  		if err := AutoRefreshAssertions(st, userID); err != nil {
  1882  			return nil, nil, err
  1883  		}
  1884  	}
  1885  
  1886  	tr := config.NewTransaction(st)
  1887  	gateAutoRefreshHook, err := features.Flag(tr, features.GateAutoRefreshHook)
  1888  	if err != nil && !config.IsNoOption(err) {
  1889  		return nil, nil, err
  1890  	}
  1891  	if !gateAutoRefreshHook {
  1892  		// old-style refresh (gate-auto-refresh-hook feature disabled)
  1893  		return UpdateMany(ctx, st, nil, userID, &Flags{IsAutoRefresh: true})
  1894  	}
  1895  
  1896  	// TODO: rename to autoRefreshTasks when old auto refresh logic gets removed.
  1897  	return autoRefreshPhase1(ctx, st)
  1898  }
  1899  
  1900  // autoRefreshPhase1 creates gate-auto-refresh hooks and conditional-auto-refresh
  1901  // task that initiates actual refresh.
  1902  // The state needs to be locked by the caller.
  1903  func autoRefreshPhase1(ctx context.Context, st *state.State) ([]string, []*state.TaskSet, error) {
  1904  	user, err := userFromUserID(st, 0)
  1905  	if err != nil {
  1906  		return nil, nil, err
  1907  	}
  1908  
  1909  	refreshOpts := &store.RefreshOptions{IsAutoRefresh: true}
  1910  	candidates, snapstateByInstance, ignoreValidationByInstanceName, err := refreshCandidates(ctx, st, nil, user, refreshOpts)
  1911  	if err != nil {
  1912  		// XXX: should we reset "refresh-candidates" to nil in state for some types
  1913  		// of errors?
  1914  		return nil, nil, err
  1915  	}
  1916  	deviceCtx, err := DeviceCtxFromState(st, nil)
  1917  	if err != nil {
  1918  		return nil, nil, err
  1919  	}
  1920  	hints, err := refreshHintsFromCandidates(st, candidates, ignoreValidationByInstanceName, deviceCtx)
  1921  	if err != nil {
  1922  		return nil, nil, err
  1923  	}
  1924  	st.Set("refresh-candidates", hints)
  1925  
  1926  	var updates []*snap.Info
  1927  
  1928  	// check conflicts
  1929  	fromChange := ""
  1930  	for _, up := range candidates {
  1931  		if _, ok := hints[up.InstanceName()]; !ok {
  1932  			// filtered out by refreshHintsFromCandidates
  1933  			continue
  1934  		}
  1935  		snapst := snapstateByInstance[up.InstanceName()]
  1936  		if err := checkChangeConflictIgnoringOneChange(st, up.InstanceName(), snapst, fromChange); err != nil {
  1937  			logger.Noticef("cannot refresh snap %q: %v", up.InstanceName(), err)
  1938  		} else {
  1939  			updates = append(updates, up)
  1940  		}
  1941  	}
  1942  
  1943  	if len(updates) == 0 {
  1944  		return nil, nil, nil
  1945  	}
  1946  
  1947  	// all snaps in updates are now considered to be operated on and should
  1948  	// provoke conflicts until updated or until we know (after running
  1949  	// gate-auto-refresh hooks) that some are not going to be updated
  1950  	// and can stop conflicting.
  1951  
  1952  	affectedSnaps, err := affectedByRefresh(st, updates)
  1953  	if err != nil {
  1954  		return nil, nil, err
  1955  	}
  1956  
  1957  	// affectedByRefresh only considers snaps affected by updates, add
  1958  	// updates themselves as long as they have gate-auto-refresh hooks.
  1959  	for _, up := range updates {
  1960  		snapst := snapstateByInstance[up.InstanceName()]
  1961  		inf, err := snapst.CurrentInfo()
  1962  		if err != nil {
  1963  			return nil, nil, err
  1964  		}
  1965  		if affectedSnaps[up.InstanceName()] == nil && inf.Hooks[gateAutoRefreshHookName] != nil {
  1966  			affectedSnaps[up.InstanceName()] = &affectedSnapInfo{}
  1967  		}
  1968  	}
  1969  
  1970  	var hooks *state.TaskSet
  1971  	if len(affectedSnaps) > 0 {
  1972  		hooks = createGateAutoRefreshHooks(st, affectedSnaps)
  1973  	}
  1974  
  1975  	// gate-auto-refresh hooks, followed by conditional-auto-refresh task waiting
  1976  	// for all hooks.
  1977  	ar := st.NewTask("conditional-auto-refresh", "Run auto-refresh for ready snaps")
  1978  	tss := []*state.TaskSet{state.NewTaskSet(ar)}
  1979  	if hooks != nil {
  1980  		ar.WaitAll(hooks)
  1981  		tss = append(tss, hooks)
  1982  	}
  1983  
  1984  	// return all names as potentially getting updated even though some may be
  1985  	// held.
  1986  	names := make([]string, len(updates))
  1987  	toUpdate := make(map[string]*refreshCandidate, len(updates))
  1988  	for i, up := range updates {
  1989  		names[i] = up.InstanceName()
  1990  		toUpdate[up.InstanceName()] = hints[up.InstanceName()]
  1991  	}
  1992  	sort.Strings(names)
  1993  
  1994  	// store the list of snaps to update on the conditional-auto-refresh task
  1995  	// (this may be a subset of refresh-candidates due to conflicts).
  1996  	ar.Set("snaps", toUpdate)
  1997  
  1998  	return names, tss, nil
  1999  }
  2000  
  2001  // autoRefreshPhase2 creates tasks for refreshing snaps from updates.
  2002  func autoRefreshPhase2(ctx context.Context, st *state.State, updates []*refreshCandidate) ([]*state.TaskSet, error) {
  2003  	fromChange := ""
  2004  	flags := &Flags{IsAutoRefresh: true}
  2005  	userID := 0
  2006  
  2007  	deviceCtx, err := DeviceCtx(st, nil, nil)
  2008  	if err != nil {
  2009  		return nil, err
  2010  	}
  2011  
  2012  	toUpdate := make([]minimalInstallInfo, len(updates))
  2013  	for i, up := range updates {
  2014  		toUpdate[i] = up
  2015  	}
  2016  
  2017  	tr := config.NewTransaction(st)
  2018  	checkDiskSpaceRefresh, err := features.Flag(tr, features.CheckDiskSpaceRefresh)
  2019  	if err != nil && !config.IsNoOption(err) {
  2020  		return nil, err
  2021  	}
  2022  	if checkDiskSpaceRefresh {
  2023  		// check if there is enough disk space for requested snaps and their
  2024  		// prerequisites.
  2025  		totalSize, err := installSize(st, toUpdate, 0)
  2026  		if err != nil {
  2027  			return nil, err
  2028  		}
  2029  		requiredSpace := safetyMarginDiskSpace(totalSize)
  2030  		path := dirs.SnapdStateDir(dirs.GlobalRootDir)
  2031  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
  2032  			snaps := make([]string, len(updates))
  2033  			for i, up := range updates {
  2034  				snaps[i] = up.InstanceName()
  2035  			}
  2036  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
  2037  				return nil, &InsufficientSpaceError{
  2038  					Path:       path,
  2039  					Snaps:      snaps,
  2040  					ChangeKind: "refresh",
  2041  				}
  2042  			}
  2043  			return nil, err
  2044  		}
  2045  	}
  2046  
  2047  	_, tasksets, err := doUpdate(ctx, st, nil, toUpdate, nil, userID, flags, deviceCtx, fromChange)
  2048  	if err != nil {
  2049  		return nil, err
  2050  	}
  2051  
  2052  	tasksets = finalizeUpdate(st, tasksets, len(updates) > 0, nil, userID, flags)
  2053  	return tasksets, nil
  2054  }
  2055  
  2056  // LinkNewBaseOrKernel will create prepare/link-snap tasks for a remodel
  2057  func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) {
  2058  	var snapst SnapState
  2059  	err := Get(st, name, &snapst)
  2060  	if err == state.ErrNoState {
  2061  		return nil, &snap.NotInstalledError{Snap: name}
  2062  	}
  2063  	if err != nil {
  2064  		return nil, err
  2065  	}
  2066  
  2067  	if err := CheckChangeConflict(st, name, nil); err != nil {
  2068  		return nil, err
  2069  	}
  2070  
  2071  	info, err := snapst.CurrentInfo()
  2072  	if err != nil {
  2073  		return nil, err
  2074  	}
  2075  
  2076  	switch info.Type() {
  2077  	case snap.TypeOS, snap.TypeBase, snap.TypeKernel:
  2078  		// good
  2079  	default:
  2080  		// bad
  2081  		return nil, fmt.Errorf("cannot link type %v", info.Type())
  2082  	}
  2083  
  2084  	snapsup := &SnapSetup{
  2085  		SideInfo:    snapst.CurrentSideInfo(),
  2086  		Flags:       snapst.Flags.ForSnapSetup(),
  2087  		Type:        info.Type(),
  2088  		PlugsOnly:   len(info.Slots) == 0,
  2089  		InstanceKey: snapst.InstanceKey,
  2090  	}
  2091  
  2092  	prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current))
  2093  	prepareSnap.Set("snap-setup", &snapsup)
  2094  
  2095  	linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapst.Current))
  2096  	linkSnap.Set("snap-setup-task", prepareSnap.ID())
  2097  	linkSnap.WaitFor(prepareSnap)
  2098  
  2099  	// we need this for remodel
  2100  	ts := state.NewTaskSet(prepareSnap, linkSnap)
  2101  	ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge)
  2102  	return ts, nil
  2103  }
  2104  
  2105  // Enable sets a snap to the active state
  2106  func Enable(st *state.State, name string) (*state.TaskSet, error) {
  2107  	var snapst SnapState
  2108  	err := Get(st, name, &snapst)
  2109  	if err == state.ErrNoState {
  2110  		return nil, &snap.NotInstalledError{Snap: name}
  2111  	}
  2112  	if err != nil {
  2113  		return nil, err
  2114  	}
  2115  
  2116  	if snapst.Active {
  2117  		return nil, fmt.Errorf("snap %q already enabled", name)
  2118  	}
  2119  
  2120  	if err := CheckChangeConflict(st, name, nil); err != nil {
  2121  		return nil, err
  2122  	}
  2123  
  2124  	info, err := snapst.CurrentInfo()
  2125  	if err != nil {
  2126  		return nil, err
  2127  	}
  2128  
  2129  	snapsup := &SnapSetup{
  2130  		SideInfo:    snapst.CurrentSideInfo(),
  2131  		Flags:       snapst.Flags.ForSnapSetup(),
  2132  		Type:        info.Type(),
  2133  		PlugsOnly:   len(info.Slots) == 0,
  2134  		InstanceKey: snapst.InstanceKey,
  2135  	}
  2136  
  2137  	prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), snapsup.InstanceName(), snapst.Current))
  2138  	prepareSnap.Set("snap-setup", &snapsup)
  2139  
  2140  	setupProfiles := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q (%s) security profiles"), snapsup.InstanceName(), snapst.Current))
  2141  	setupProfiles.Set("snap-setup-task", prepareSnap.ID())
  2142  	setupProfiles.WaitFor(prepareSnap)
  2143  
  2144  	linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), snapsup.InstanceName(), snapst.Current))
  2145  	linkSnap.Set("snap-setup-task", prepareSnap.ID())
  2146  	linkSnap.WaitFor(setupProfiles)
  2147  
  2148  	// setup aliases
  2149  	setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName()))
  2150  	setupAliases.Set("snap-setup-task", prepareSnap.ID())
  2151  	setupAliases.WaitFor(linkSnap)
  2152  
  2153  	startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), snapsup.InstanceName(), snapst.Current))
  2154  	startSnapServices.Set("snap-setup-task", prepareSnap.ID())
  2155  	startSnapServices.WaitFor(setupAliases)
  2156  
  2157  	return state.NewTaskSet(prepareSnap, setupProfiles, linkSnap, setupAliases, startSnapServices), nil
  2158  }
  2159  
  2160  // Disable sets a snap to the inactive state
  2161  func Disable(st *state.State, name string) (*state.TaskSet, error) {
  2162  	var snapst SnapState
  2163  	err := Get(st, name, &snapst)
  2164  	if err == state.ErrNoState {
  2165  		return nil, &snap.NotInstalledError{Snap: name}
  2166  	}
  2167  	if err != nil {
  2168  		return nil, err
  2169  	}
  2170  	if !snapst.Active {
  2171  		return nil, fmt.Errorf("snap %q already disabled", name)
  2172  	}
  2173  
  2174  	info, err := Info(st, name, snapst.Current)
  2175  	if err != nil {
  2176  		return nil, err
  2177  	}
  2178  	if !canDisable(info) {
  2179  		return nil, fmt.Errorf("snap %q cannot be disabled", name)
  2180  	}
  2181  
  2182  	if err := CheckChangeConflict(st, name, nil); err != nil {
  2183  		return nil, err
  2184  	}
  2185  
  2186  	snapsup := &SnapSetup{
  2187  		SideInfo: &snap.SideInfo{
  2188  			RealName: snap.InstanceSnap(name),
  2189  			Revision: snapst.Current,
  2190  		},
  2191  		Type:        info.Type(),
  2192  		PlugsOnly:   len(info.Slots) == 0,
  2193  		InstanceKey: snapst.InstanceKey,
  2194  	}
  2195  
  2196  	stopSnapServices := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), snapsup.InstanceName(), snapst.Current))
  2197  	stopSnapServices.Set("snap-setup", &snapsup)
  2198  	stopSnapServices.Set("stop-reason", snap.StopReasonDisable)
  2199  
  2200  	removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), snapsup.InstanceName()))
  2201  	removeAliases.Set("snap-setup-task", stopSnapServices.ID())
  2202  	removeAliases.WaitFor(stopSnapServices)
  2203  
  2204  	unlinkSnap := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), snapsup.InstanceName(), snapst.Current))
  2205  	unlinkSnap.Set("snap-setup-task", stopSnapServices.ID())
  2206  	unlinkSnap.WaitFor(removeAliases)
  2207  
  2208  	removeProfiles := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profiles of snap %q"), snapsup.InstanceName()))
  2209  	removeProfiles.Set("snap-setup-task", stopSnapServices.ID())
  2210  	removeProfiles.WaitFor(unlinkSnap)
  2211  
  2212  	return state.NewTaskSet(stopSnapServices, removeAliases, unlinkSnap, removeProfiles), nil
  2213  }
  2214  
  2215  // canDisable verifies that a snap can be deactivated.
  2216  func canDisable(si *snap.Info) bool {
  2217  	for _, importantSnapType := range []snap.Type{snap.TypeGadget, snap.TypeKernel, snap.TypeOS} {
  2218  		if importantSnapType == si.Type() {
  2219  			return false
  2220  		}
  2221  	}
  2222  
  2223  	return true
  2224  }
  2225  
  2226  // canRemove verifies that a snap can be removed.
  2227  func canRemove(st *state.State, si *snap.Info, snapst *SnapState, removeAll bool, deviceCtx DeviceContext) error {
  2228  	rev := snap.Revision{}
  2229  	if !removeAll {
  2230  		rev = si.Revision
  2231  	}
  2232  
  2233  	return PolicyFor(si.Type(), deviceCtx.Model()).CanRemove(st, snapst, rev, deviceCtx)
  2234  }
  2235  
  2236  // RemoveFlags are used to pass additional flags to the Remove operation.
  2237  type RemoveFlags struct {
  2238  	// Remove the snap without creating snapshot data
  2239  	Purge bool
  2240  }
  2241  
  2242  // Remove returns a set of tasks for removing snap.
  2243  // Note that the state must be locked by the caller.
  2244  func Remove(st *state.State, name string, revision snap.Revision, flags *RemoveFlags) (*state.TaskSet, error) {
  2245  	ts, snapshotSize, err := removeTasks(st, name, revision, flags)
  2246  	// removeTasks() checks check-disk-space-remove feature flag, so snapshotSize
  2247  	// will only be greater than 0 if the feature is enabled.
  2248  	if snapshotSize > 0 {
  2249  		requiredSpace := safetyMarginDiskSpace(snapshotSize)
  2250  		path := dirs.SnapdStateDir(dirs.GlobalRootDir)
  2251  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
  2252  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
  2253  				return nil, &InsufficientSpaceError{
  2254  					Path:       path,
  2255  					Snaps:      []string{name},
  2256  					ChangeKind: "remove",
  2257  					Message:    fmt.Sprintf("cannot create automatic snapshot when removing last revision of the snap: %v", err)}
  2258  			}
  2259  			return nil, err
  2260  		}
  2261  	}
  2262  	return ts, err
  2263  }
  2264  
  2265  // removeTasks provides the task set to remove snap name after taking a snapshot
  2266  // if flags.Purge is not true, it also computes an estimate of the latter size.
  2267  func removeTasks(st *state.State, name string, revision snap.Revision, flags *RemoveFlags) (removeTs *state.TaskSet, snapshotSize uint64, err error) {
  2268  	var snapst SnapState
  2269  	err = Get(st, name, &snapst)
  2270  	if err != nil && err != state.ErrNoState {
  2271  		return nil, 0, err
  2272  	}
  2273  
  2274  	if !snapst.IsInstalled() {
  2275  		return nil, 0, &snap.NotInstalledError{Snap: name, Rev: snap.R(0)}
  2276  	}
  2277  
  2278  	if err := CheckChangeConflict(st, name, nil); err != nil {
  2279  		return nil, 0, err
  2280  	}
  2281  
  2282  	deviceCtx, err := DeviceCtxFromState(st, nil)
  2283  	if err != nil {
  2284  		return nil, 0, err
  2285  	}
  2286  
  2287  	active := snapst.Active
  2288  	var removeAll bool
  2289  	if revision.Unset() {
  2290  		revision = snapst.Current
  2291  		removeAll = true
  2292  	} else {
  2293  		if active {
  2294  			if revision == snapst.Current {
  2295  				msg := "cannot remove active revision %s of snap %q"
  2296  				if len(snapst.Sequence) > 1 {
  2297  					msg += " (revert first?)"
  2298  				}
  2299  				return nil, 0, fmt.Errorf(msg, revision, name)
  2300  			}
  2301  			active = false
  2302  		}
  2303  
  2304  		if !revisionInSequence(&snapst, revision) {
  2305  			return nil, 0, &snap.NotInstalledError{Snap: name, Rev: revision}
  2306  		}
  2307  
  2308  		removeAll = len(snapst.Sequence) == 1
  2309  	}
  2310  
  2311  	info, err := Info(st, name, revision)
  2312  	if err != nil {
  2313  		return nil, 0, err
  2314  	}
  2315  
  2316  	// check if this is something that can be removed
  2317  	if err := canRemove(st, info, &snapst, removeAll, deviceCtx); err != nil {
  2318  		return nil, 0, fmt.Errorf("snap %q is not removable: %v", name, err)
  2319  	}
  2320  
  2321  	// main/current SnapSetup
  2322  	snapsup := SnapSetup{
  2323  		SideInfo: &snap.SideInfo{
  2324  			SnapID:   info.SnapID,
  2325  			RealName: snap.InstanceSnap(name),
  2326  			Revision: revision,
  2327  		},
  2328  		Type:        info.Type(),
  2329  		PlugsOnly:   len(info.Slots) == 0,
  2330  		InstanceKey: snapst.InstanceKey,
  2331  	}
  2332  
  2333  	// trigger remove
  2334  
  2335  	removeTs = state.NewTaskSet()
  2336  	var chain *state.TaskSet
  2337  
  2338  	addNext := func(ts *state.TaskSet) {
  2339  		if chain != nil {
  2340  			ts.WaitAll(chain)
  2341  		}
  2342  		removeTs.AddAll(ts)
  2343  		chain = ts
  2344  	}
  2345  
  2346  	var prev *state.Task
  2347  	var stopSnapServices *state.Task
  2348  	if active {
  2349  		stopSnapServices = st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name))
  2350  		stopSnapServices.Set("snap-setup", snapsup)
  2351  		stopSnapServices.Set("stop-reason", snap.StopReasonRemove)
  2352  		addNext(state.NewTaskSet(stopSnapServices))
  2353  		prev = stopSnapServices
  2354  	}
  2355  
  2356  	// only run remove hook if uninstalling the snap completely
  2357  	if removeAll {
  2358  		removeHook := SetupRemoveHook(st, snapsup.InstanceName())
  2359  		addNext(state.NewTaskSet(removeHook))
  2360  		prev = removeHook
  2361  
  2362  		// run disconnect hooks
  2363  		disconnect := st.NewTask("auto-disconnect", fmt.Sprintf(i18n.G("Disconnect interfaces of snap %q"), snapsup.InstanceName()))
  2364  		disconnect.Set("snap-setup", snapsup)
  2365  		if prev != nil {
  2366  			disconnect.WaitFor(prev)
  2367  		}
  2368  		addNext(state.NewTaskSet(disconnect))
  2369  		prev = disconnect
  2370  	}
  2371  
  2372  	// 'purge' flag disables automatic snapshot for given remove op
  2373  	if flags == nil || !flags.Purge {
  2374  		if tp, _ := snapst.Type(); tp == snap.TypeApp && removeAll {
  2375  			ts, err := AutomaticSnapshot(st, name)
  2376  			if err == nil {
  2377  				tr := config.NewTransaction(st)
  2378  				checkDiskSpaceRemove, err := features.Flag(tr, features.CheckDiskSpaceRemove)
  2379  				if err != nil && !config.IsNoOption(err) {
  2380  					return nil, 0, err
  2381  				}
  2382  				if checkDiskSpaceRemove {
  2383  					snapshotSize, err = EstimateSnapshotSize(st, name, nil)
  2384  					if err != nil {
  2385  						return nil, 0, err
  2386  					}
  2387  				}
  2388  				addNext(ts)
  2389  			} else {
  2390  				if err != ErrNothingToDo {
  2391  					return nil, 0, err
  2392  				}
  2393  			}
  2394  		}
  2395  	}
  2396  
  2397  	if active { // unlink
  2398  		var tasks []*state.Task
  2399  
  2400  		removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), name))
  2401  		removeAliases.WaitFor(prev) // prev is not needed beyond here
  2402  		removeAliases.Set("snap-setup-task", stopSnapServices.ID())
  2403  
  2404  		unlink := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), name))
  2405  		unlink.Set("snap-setup-task", stopSnapServices.ID())
  2406  		unlink.WaitFor(removeAliases)
  2407  
  2408  		removeSecurity := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profile for snap %q (%s)"), name, revision))
  2409  		removeSecurity.WaitFor(unlink)
  2410  		removeSecurity.Set("snap-setup-task", stopSnapServices.ID())
  2411  
  2412  		tasks = append(tasks, removeAliases, unlink, removeSecurity)
  2413  		addNext(state.NewTaskSet(tasks...))
  2414  	}
  2415  
  2416  	if removeAll {
  2417  		seq := snapst.Sequence
  2418  		currentIndex := snapst.LastIndex(snapst.Current)
  2419  		for i := len(seq) - 1; i >= 0; i-- {
  2420  			if i != currentIndex {
  2421  				si := seq[i]
  2422  				addNext(removeInactiveRevision(st, name, info.SnapID, si.Revision, snapsup.Type))
  2423  			}
  2424  		}
  2425  		// add tasks for removing the current revision last,
  2426  		// this is then also when common data will be removed
  2427  		if currentIndex >= 0 {
  2428  			addNext(removeInactiveRevision(st, name, info.SnapID, seq[currentIndex].Revision, snapsup.Type))
  2429  		}
  2430  	} else {
  2431  		addNext(removeInactiveRevision(st, name, info.SnapID, revision, snapsup.Type))
  2432  	}
  2433  
  2434  	return removeTs, snapshotSize, nil
  2435  }
  2436  
  2437  func removeInactiveRevision(st *state.State, name, snapID string, revision snap.Revision, typ snap.Type) *state.TaskSet {
  2438  	snapName, instanceKey := snap.SplitInstanceName(name)
  2439  	snapsup := SnapSetup{
  2440  		SideInfo: &snap.SideInfo{
  2441  			RealName: snapName,
  2442  			SnapID:   snapID,
  2443  			Revision: revision,
  2444  		},
  2445  		InstanceKey: instanceKey,
  2446  		Type:        typ,
  2447  	}
  2448  
  2449  	clearData := st.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q (%s)"), name, revision))
  2450  	clearData.Set("snap-setup", snapsup)
  2451  
  2452  	discardSnap := st.NewTask("discard-snap", fmt.Sprintf(i18n.G("Remove snap %q (%s) from the system"), name, revision))
  2453  	discardSnap.WaitFor(clearData)
  2454  	discardSnap.Set("snap-setup-task", clearData.ID())
  2455  
  2456  	return state.NewTaskSet(clearData, discardSnap)
  2457  }
  2458  
  2459  // RemoveMany removes everything from the given list of names.
  2460  // Note that the state must be locked by the caller.
  2461  func RemoveMany(st *state.State, names []string) ([]string, []*state.TaskSet, error) {
  2462  	removed := make([]string, 0, len(names))
  2463  	tasksets := make([]*state.TaskSet, 0, len(names))
  2464  
  2465  	var totalSnapshotsSize uint64
  2466  	path := dirs.SnapdStateDir(dirs.GlobalRootDir)
  2467  
  2468  	for _, name := range names {
  2469  		ts, snapshotSize, err := removeTasks(st, name, snap.R(0), nil)
  2470  		// FIXME: is this expected behavior?
  2471  		if _, ok := err.(*snap.NotInstalledError); ok {
  2472  			continue
  2473  		}
  2474  		if err != nil {
  2475  			return nil, nil, err
  2476  		}
  2477  		totalSnapshotsSize += snapshotSize
  2478  		removed = append(removed, name)
  2479  		ts.JoinLane(st.NewLane())
  2480  		tasksets = append(tasksets, ts)
  2481  	}
  2482  
  2483  	// removeTasks() checks check-disk-space-remove feature flag, so totalSnapshotsSize
  2484  	// will only be greater than 0 if the feature is enabled.
  2485  	if totalSnapshotsSize > 0 {
  2486  		requiredSpace := safetyMarginDiskSpace(totalSnapshotsSize)
  2487  		if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
  2488  			if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
  2489  				return nil, nil, &InsufficientSpaceError{
  2490  					Path:       path,
  2491  					Snaps:      names,
  2492  					ChangeKind: "remove",
  2493  				}
  2494  			}
  2495  			return nil, nil, err
  2496  		}
  2497  	}
  2498  
  2499  	return removed, tasksets, nil
  2500  }
  2501  
  2502  // Revert returns a set of tasks for reverting to the previous version of the snap.
  2503  // Note that the state must be locked by the caller.
  2504  func Revert(st *state.State, name string, flags Flags) (*state.TaskSet, error) {
  2505  	var snapst SnapState
  2506  	err := Get(st, name, &snapst)
  2507  	if err != nil && err != state.ErrNoState {
  2508  		return nil, err
  2509  	}
  2510  
  2511  	pi := snapst.previousSideInfo()
  2512  	if pi == nil {
  2513  		return nil, fmt.Errorf("no revision to revert to")
  2514  	}
  2515  
  2516  	return RevertToRevision(st, name, pi.Revision, flags)
  2517  }
  2518  
  2519  func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Flags) (*state.TaskSet, error) {
  2520  	var snapst SnapState
  2521  	err := Get(st, name, &snapst)
  2522  	if err != nil && err != state.ErrNoState {
  2523  		return nil, err
  2524  	}
  2525  
  2526  	if snapst.Current == rev {
  2527  		return nil, fmt.Errorf("already on requested revision")
  2528  	}
  2529  
  2530  	if !snapst.Active {
  2531  		return nil, fmt.Errorf("cannot revert inactive snaps")
  2532  	}
  2533  	i := snapst.LastIndex(rev)
  2534  	if i < 0 {
  2535  		return nil, fmt.Errorf("cannot find revision %s for snap %q", rev, name)
  2536  	}
  2537  
  2538  	flags.Revert = true
  2539  	// TODO: make flags be per revision to avoid this logic (that
  2540  	//       leaves corner cases all over the place)
  2541  	if !(flags.JailMode || flags.DevMode || flags.Classic) {
  2542  		if snapst.Flags.DevMode {
  2543  			flags.DevMode = true
  2544  		}
  2545  		if snapst.Flags.JailMode {
  2546  			flags.JailMode = true
  2547  		}
  2548  		if snapst.Flags.Classic {
  2549  			flags.Classic = true
  2550  		}
  2551  	}
  2552  
  2553  	info, err := Info(st, name, rev)
  2554  	if err != nil {
  2555  		return nil, err
  2556  	}
  2557  
  2558  	snapsup := &SnapSetup{
  2559  		Base:        info.Base,
  2560  		SideInfo:    snapst.Sequence[i],
  2561  		Flags:       flags.ForSnapSetup(),
  2562  		Type:        info.Type(),
  2563  		PlugsOnly:   len(info.Slots) == 0,
  2564  		InstanceKey: snapst.InstanceKey,
  2565  	}
  2566  	return doInstall(st, &snapst, snapsup, 0, "", nil)
  2567  }
  2568  
  2569  // TransitionCore transitions from an old core snap name to a new core
  2570  // snap name. It is used for the ubuntu-core -> core transition (that
  2571  // is not just a rename because the two snaps have different snapIDs)
  2572  //
  2573  // Note that this function makes some assumptions like:
  2574  // - no aliases setup for both snaps
  2575  // - no data needs to be copied
  2576  // - all interfaces are absolutely identical on both new and old
  2577  // Do not use this as a general way to transition from snap A to snap B.
  2578  func TransitionCore(st *state.State, oldName, newName string) ([]*state.TaskSet, error) {
  2579  	var oldSnapst, newSnapst SnapState
  2580  	err := Get(st, oldName, &oldSnapst)
  2581  	if err != nil && err != state.ErrNoState {
  2582  		return nil, err
  2583  	}
  2584  	if !oldSnapst.IsInstalled() {
  2585  		return nil, fmt.Errorf("cannot transition snap %q: not installed", oldName)
  2586  	}
  2587  
  2588  	var all []*state.TaskSet
  2589  	// install new core (if not already installed)
  2590  	err = Get(st, newName, &newSnapst)
  2591  	if err != nil && err != state.ErrNoState {
  2592  		return nil, err
  2593  	}
  2594  	if !newSnapst.IsInstalled() {
  2595  		var userID int
  2596  		newInfo, err := installInfo(context.TODO(), st, newName, &RevisionOptions{Channel: oldSnapst.TrackingChannel}, userID, nil)
  2597  		if err != nil {
  2598  			return nil, err
  2599  		}
  2600  
  2601  		// start by installing the new snap
  2602  		tsInst, err := doInstall(st, &newSnapst, &SnapSetup{
  2603  			Channel:      oldSnapst.TrackingChannel,
  2604  			DownloadInfo: &newInfo.DownloadInfo,
  2605  			SideInfo:     &newInfo.SideInfo,
  2606  			Type:         newInfo.Type(),
  2607  		}, 0, "", nil)
  2608  		if err != nil {
  2609  			return nil, err
  2610  		}
  2611  		all = append(all, tsInst)
  2612  	}
  2613  
  2614  	// then transition the interface connections over
  2615  	transIf := st.NewTask("transition-ubuntu-core", fmt.Sprintf(i18n.G("Transition security profiles from %q to %q"), oldName, newName))
  2616  	transIf.Set("old-name", oldName)
  2617  	transIf.Set("new-name", newName)
  2618  	if len(all) > 0 {
  2619  		transIf.WaitAll(all[0])
  2620  	}
  2621  	tsTrans := state.NewTaskSet(transIf)
  2622  	all = append(all, tsTrans)
  2623  
  2624  	// FIXME: this is just here for the tests
  2625  	transIf.Set("snap-setup", &SnapSetup{
  2626  		SideInfo: &snap.SideInfo{
  2627  			RealName: oldName,
  2628  		},
  2629  	})
  2630  
  2631  	// then remove the old snap
  2632  	tsRm, err := Remove(st, oldName, snap.R(0), nil)
  2633  	if err != nil {
  2634  		return nil, err
  2635  	}
  2636  	tsRm.WaitFor(transIf)
  2637  	all = append(all, tsRm)
  2638  
  2639  	return all, nil
  2640  }
  2641  
  2642  // State/info accessors
  2643  
  2644  // Installing returns whether there's an in-progress installation.
  2645  func Installing(st *state.State) bool {
  2646  	for _, task := range st.Tasks() {
  2647  		k := task.Kind()
  2648  		chg := task.Change()
  2649  		if k == "mount-snap" && chg != nil && !chg.Status().Ready() {
  2650  			return true
  2651  		}
  2652  	}
  2653  	return false
  2654  }
  2655  
  2656  // Info returns the information about the snap with given name and revision.
  2657  // Works also for a mounted candidate snap in the process of being installed.
  2658  func Info(st *state.State, name string, revision snap.Revision) (*snap.Info, error) {
  2659  	var snapst SnapState
  2660  	err := Get(st, name, &snapst)
  2661  	if err == state.ErrNoState {
  2662  		return nil, &snap.NotInstalledError{Snap: name}
  2663  	}
  2664  	if err != nil {
  2665  		return nil, err
  2666  	}
  2667  
  2668  	for i := len(snapst.Sequence) - 1; i >= 0; i-- {
  2669  		if si := snapst.Sequence[i]; si.Revision == revision {
  2670  			return readInfo(name, si, 0)
  2671  		}
  2672  	}
  2673  
  2674  	return nil, fmt.Errorf("cannot find snap %q at revision %s", name, revision.String())
  2675  }
  2676  
  2677  // CurrentInfo returns the information about the current revision of a snap with the given name.
  2678  func CurrentInfo(st *state.State, name string) (*snap.Info, error) {
  2679  	var snapst SnapState
  2680  	err := Get(st, name, &snapst)
  2681  	if err != nil && err != state.ErrNoState {
  2682  		return nil, err
  2683  	}
  2684  	info, err := snapst.CurrentInfo()
  2685  	if err == ErrNoCurrent {
  2686  		return nil, &snap.NotInstalledError{Snap: name}
  2687  	}
  2688  	return info, err
  2689  }
  2690  
  2691  // Get retrieves the SnapState of the given snap.
  2692  func Get(st *state.State, name string, snapst *SnapState) error {
  2693  	if snapst == nil {
  2694  		return fmt.Errorf("internal error: snapst is nil")
  2695  	}
  2696  	// SnapState is (un-)marshalled from/to JSON, fields having omitempty
  2697  	// tag will not appear in the output (if empty) and subsequently will
  2698  	// not be unmarshalled to (or cleared); if the caller reuses the same
  2699  	// struct though subsequent calls, it is possible that they end up with
  2700  	// garbage inside, clear the destination struct so that we always
  2701  	// unmarshal to a clean state
  2702  	*snapst = SnapState{}
  2703  
  2704  	var snaps map[string]*json.RawMessage
  2705  	err := st.Get("snaps", &snaps)
  2706  	if err != nil {
  2707  		return err
  2708  	}
  2709  	raw, ok := snaps[name]
  2710  	if !ok {
  2711  		return state.ErrNoState
  2712  	}
  2713  
  2714  	// XXX: &snapst pointer isn't needed here but it is likely historical
  2715  	// (a bug in old JSON marshaling probably).
  2716  	err = json.Unmarshal([]byte(*raw), &snapst)
  2717  	if err != nil {
  2718  		return fmt.Errorf("cannot unmarshal snap state: %v", err)
  2719  	}
  2720  	return nil
  2721  }
  2722  
  2723  // All retrieves return a map from name to SnapState for all current snaps in the system state.
  2724  func All(st *state.State) (map[string]*SnapState, error) {
  2725  	// XXX: result is a map because sideloaded snaps carry no name
  2726  	// atm in their sideinfos
  2727  	var stateMap map[string]*SnapState
  2728  	if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState {
  2729  		return nil, err
  2730  	}
  2731  	curStates := make(map[string]*SnapState, len(stateMap))
  2732  	for instanceName, snapst := range stateMap {
  2733  		curStates[instanceName] = snapst
  2734  	}
  2735  	return curStates, nil
  2736  }
  2737  
  2738  // NumSnaps returns the number of installed snaps.
  2739  func NumSnaps(st *state.State) (int, error) {
  2740  	var snaps map[string]*json.RawMessage
  2741  	if err := st.Get("snaps", &snaps); err != nil && err != state.ErrNoState {
  2742  		return -1, err
  2743  	}
  2744  	return len(snaps), nil
  2745  }
  2746  
  2747  // Set sets the SnapState of the given snap, overwriting any earlier state.
  2748  // Note that a SnapState with an empty Sequence will be treated as if snapst was
  2749  // nil and name will be deleted from the state.
  2750  func Set(st *state.State, name string, snapst *SnapState) {
  2751  	var snaps map[string]*json.RawMessage
  2752  	err := st.Get("snaps", &snaps)
  2753  	if err != nil && err != state.ErrNoState {
  2754  		panic("internal error: cannot unmarshal snaps state: " + err.Error())
  2755  	}
  2756  	if snaps == nil {
  2757  		snaps = make(map[string]*json.RawMessage)
  2758  	}
  2759  	if snapst == nil || (len(snapst.Sequence) == 0) {
  2760  		delete(snaps, name)
  2761  	} else {
  2762  		data, err := json.Marshal(snapst)
  2763  		if err != nil {
  2764  			panic("internal error: cannot marshal snap state: " + err.Error())
  2765  		}
  2766  		raw := json.RawMessage(data)
  2767  		snaps[name] = &raw
  2768  	}
  2769  	st.Set("snaps", snaps)
  2770  }
  2771  
  2772  // ActiveInfos returns information about all active snaps.
  2773  func ActiveInfos(st *state.State) ([]*snap.Info, error) {
  2774  	var stateMap map[string]*SnapState
  2775  	var infos []*snap.Info
  2776  	if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState {
  2777  		return nil, err
  2778  	}
  2779  	for instanceName, snapst := range stateMap {
  2780  		if !snapst.Active {
  2781  			continue
  2782  		}
  2783  		snapInfo, err := snapst.CurrentInfo()
  2784  		if err != nil {
  2785  			logger.Noticef("cannot retrieve info for snap %q: %s", instanceName, err)
  2786  			continue
  2787  		}
  2788  		infos = append(infos, snapInfo)
  2789  	}
  2790  	return infos, nil
  2791  }
  2792  
  2793  func HasSnapOfType(st *state.State, snapType snap.Type) (bool, error) {
  2794  	var stateMap map[string]*SnapState
  2795  	if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState {
  2796  		return false, err
  2797  	}
  2798  
  2799  	for _, snapst := range stateMap {
  2800  		typ, err := snapst.Type()
  2801  		if err != nil {
  2802  			return false, err
  2803  		}
  2804  		if typ == snapType {
  2805  			return true, nil
  2806  		}
  2807  	}
  2808  
  2809  	return false, nil
  2810  }
  2811  
  2812  func infosForType(st *state.State, snapType snap.Type) ([]*snap.Info, error) {
  2813  	var stateMap map[string]*SnapState
  2814  	if err := st.Get("snaps", &stateMap); err != nil && err != state.ErrNoState {
  2815  		return nil, err
  2816  	}
  2817  
  2818  	var res []*snap.Info
  2819  	for _, snapst := range stateMap {
  2820  		if !snapst.IsInstalled() {
  2821  			continue
  2822  		}
  2823  		typ, err := snapst.Type()
  2824  		if err != nil {
  2825  			return nil, err
  2826  		}
  2827  		if typ != snapType {
  2828  			continue
  2829  		}
  2830  		si, err := snapst.CurrentInfo()
  2831  		if err != nil {
  2832  			return nil, err
  2833  		}
  2834  		res = append(res, si)
  2835  	}
  2836  
  2837  	if len(res) == 0 {
  2838  		return nil, state.ErrNoState
  2839  	}
  2840  
  2841  	return res, nil
  2842  }
  2843  
  2844  func infoForDeviceSnap(st *state.State, deviceCtx DeviceContext, which string, whichName func(*asserts.Model) string) (*snap.Info, error) {
  2845  	if deviceCtx == nil {
  2846  		return nil, fmt.Errorf("internal error: unset deviceCtx")
  2847  	}
  2848  	model := deviceCtx.Model()
  2849  	snapName := whichName(model)
  2850  	if snapName == "" {
  2851  		return nil, state.ErrNoState
  2852  	}
  2853  	var snapst SnapState
  2854  	err := Get(st, snapName, &snapst)
  2855  	if err != nil {
  2856  		return nil, err
  2857  	}
  2858  	return snapst.CurrentInfo()
  2859  }
  2860  
  2861  // GadgetInfo finds the gadget snap's info for the given device context.
  2862  func GadgetInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) {
  2863  	return infoForDeviceSnap(st, deviceCtx, "gadget", (*asserts.Model).Gadget)
  2864  }
  2865  
  2866  // KernelInfo finds the kernel snap's info for the given device context.
  2867  func KernelInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) {
  2868  	return infoForDeviceSnap(st, deviceCtx, "kernel", (*asserts.Model).Kernel)
  2869  }
  2870  
  2871  // BootBaseInfo finds the boot base snap's info for the given device context.
  2872  func BootBaseInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) {
  2873  	baseName := func(mod *asserts.Model) string {
  2874  		base := mod.Base()
  2875  		if base == "" {
  2876  			return "core"
  2877  		}
  2878  		return base
  2879  	}
  2880  	return infoForDeviceSnap(st, deviceCtx, "boot base", baseName)
  2881  }
  2882  
  2883  // TODO: reintroduce a KernelInfo(state.State, DeviceContext) if needed
  2884  // KernelInfo finds the current kernel snap's info.
  2885  
  2886  // coreInfo finds the current OS snap's info. If both
  2887  // "core" and "ubuntu-core" is installed then "core"
  2888  // is preferred. Different core names are not supported
  2889  // currently and will result in an error.
  2890  func coreInfo(st *state.State) (*snap.Info, error) {
  2891  	res, err := infosForType(st, snap.TypeOS)
  2892  	if err != nil {
  2893  		return nil, err
  2894  	}
  2895  
  2896  	// a single core: just return it
  2897  	if len(res) == 1 {
  2898  		return res[0], nil
  2899  	}
  2900  
  2901  	// some systems have two cores: ubuntu-core/core
  2902  	// we always return "core" in this case
  2903  	if len(res) == 2 {
  2904  		if res[0].InstanceName() == defaultCoreSnapName && res[1].InstanceName() == "ubuntu-core" {
  2905  			return res[0], nil
  2906  		}
  2907  		if res[0].InstanceName() == "ubuntu-core" && res[1].InstanceName() == defaultCoreSnapName {
  2908  			return res[1], nil
  2909  		}
  2910  		return nil, fmt.Errorf("unexpected cores %q and %q", res[0].InstanceName(), res[1].InstanceName())
  2911  	}
  2912  
  2913  	return nil, fmt.Errorf("unexpected number of cores, got %d", len(res))
  2914  }
  2915  
  2916  // ConfigDefaults returns the configuration defaults for the snap as
  2917  // specified in the gadget for the given device context.
  2918  // If gadget is absent or the snap has no snap-id it returns
  2919  // ErrNoState.
  2920  func ConfigDefaults(st *state.State, deviceCtx DeviceContext, snapName string) (map[string]interface{}, error) {
  2921  	info, err := GadgetInfo(st, deviceCtx)
  2922  	if err != nil {
  2923  		return nil, err
  2924  	}
  2925  
  2926  	// system configuration is kept under "core" so apply its defaults when
  2927  	// configuring "core"
  2928  	isSystemDefaults := snapName == defaultCoreSnapName
  2929  	var snapst SnapState
  2930  	if err := Get(st, snapName, &snapst); err != nil && err != state.ErrNoState {
  2931  		return nil, err
  2932  	}
  2933  
  2934  	var snapID string
  2935  	if snapst.IsInstalled() {
  2936  		snapID = snapst.CurrentSideInfo().SnapID
  2937  	}
  2938  	// system snaps (core and snapd) snaps can be addressed even without a
  2939  	// snap-id via the special "system" value in the config; first-boot
  2940  	// always configures the core snap with UseConfigDefaults
  2941  	if snapID == "" && !isSystemDefaults {
  2942  		return nil, state.ErrNoState
  2943  	}
  2944  
  2945  	// no constraints enforced: those should have been checked before already
  2946  	gadgetInfo, err := gadget.ReadInfo(info.MountDir(), nil)
  2947  	if err != nil {
  2948  		return nil, err
  2949  	}
  2950  
  2951  	// we support setting core defaults via "system"
  2952  	if isSystemDefaults {
  2953  		if defaults, ok := gadgetInfo.Defaults["system"]; ok {
  2954  			if _, ok := gadgetInfo.Defaults[snapID]; ok && snapID != "" {
  2955  				logger.Noticef("core snap configuration defaults found under both 'system' key and core-snap-id, preferring 'system'")
  2956  			}
  2957  
  2958  			return defaults, nil
  2959  		}
  2960  	}
  2961  
  2962  	defaults, ok := gadgetInfo.Defaults[snapID]
  2963  	if !ok {
  2964  		return nil, state.ErrNoState
  2965  	}
  2966  
  2967  	return defaults, nil
  2968  }
  2969  
  2970  // GadgetConnections returns the interface connection instructions
  2971  // specified in the gadget for the given device context.
  2972  // If gadget is absent it returns ErrNoState.
  2973  func GadgetConnections(st *state.State, deviceCtx DeviceContext) ([]gadget.Connection, error) {
  2974  	info, err := GadgetInfo(st, deviceCtx)
  2975  	if err != nil {
  2976  		return nil, err
  2977  	}
  2978  
  2979  	// no constraints enforced: those should have been checked before already
  2980  	gadgetInfo, err := gadget.ReadInfo(info.MountDir(), nil)
  2981  	if err != nil {
  2982  		return nil, err
  2983  	}
  2984  
  2985  	return gadgetInfo.Connections, nil
  2986  }
  2987  
  2988  func MockOsutilCheckFreeSpace(mock func(path string, minSize uint64) error) (restore func()) {
  2989  	old := osutilCheckFreeSpace
  2990  	osutilCheckFreeSpace = mock
  2991  	return func() { osutilCheckFreeSpace = old }
  2992  }