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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2019 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 devicestate implements the manager and state aspects responsible
    21  // for the device identity and policies.
    22  package devicestate
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"sync"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/i18n"
    31  	"github.com/snapcore/snapd/logger"
    32  	"github.com/snapcore/snapd/netutil"
    33  	"github.com/snapcore/snapd/overlord/assertstate"
    34  	"github.com/snapcore/snapd/overlord/auth"
    35  	"github.com/snapcore/snapd/overlord/configstate/config"
    36  	"github.com/snapcore/snapd/overlord/devicestate/internal"
    37  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    38  	"github.com/snapcore/snapd/overlord/snapstate"
    39  	"github.com/snapcore/snapd/overlord/state"
    40  	"github.com/snapcore/snapd/release"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/naming"
    43  )
    44  
    45  var (
    46  	snapstateInstallWithDeviceContext = snapstate.InstallWithDeviceContext
    47  	snapstateUpdateWithDeviceContext  = snapstate.UpdateWithDeviceContext
    48  )
    49  
    50  // findModel returns the device model assertion.
    51  func findModel(st *state.State) (*asserts.Model, error) {
    52  	device, err := internal.Device(st)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	if device.Brand == "" || device.Model == "" {
    58  		return nil, state.ErrNoState
    59  	}
    60  
    61  	a, err := assertstate.DB(st).Find(asserts.ModelType, map[string]string{
    62  		"series":   release.Series,
    63  		"brand-id": device.Brand,
    64  		"model":    device.Model,
    65  	})
    66  	if asserts.IsNotFound(err) {
    67  		return nil, state.ErrNoState
    68  	}
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	return a.(*asserts.Model), nil
    74  }
    75  
    76  // findSerial returns the device serial assertion. device is optional and used instead of the global state if provided.
    77  func findSerial(st *state.State, device *auth.DeviceState) (*asserts.Serial, error) {
    78  	if device == nil {
    79  		var err error
    80  		device, err = internal.Device(st)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  	}
    85  
    86  	if device.Serial == "" {
    87  		return nil, state.ErrNoState
    88  	}
    89  
    90  	a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{
    91  		"brand-id": device.Brand,
    92  		"model":    device.Model,
    93  		"serial":   device.Serial,
    94  	})
    95  	if asserts.IsNotFound(err) {
    96  		return nil, state.ErrNoState
    97  	}
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return a.(*asserts.Serial), nil
   103  }
   104  
   105  // auto-refresh
   106  func canAutoRefresh(st *state.State) (bool, error) {
   107  	// we need to be seeded first
   108  	var seeded bool
   109  	st.Get("seeded", &seeded)
   110  	if !seeded {
   111  		return false, nil
   112  	}
   113  
   114  	// Either we have a serial or we try anyway if we attempted
   115  	// for a while to get a serial, this would allow us to at
   116  	// least upgrade core if that can help.
   117  	if ensureOperationalAttempts(st) >= 3 {
   118  		return true, nil
   119  	}
   120  
   121  	// Check model exists, for sanity. We always have a model, either
   122  	// seeded or a generic one that ships with snapd.
   123  	_, err := findModel(st)
   124  	if err == state.ErrNoState {
   125  		return false, nil
   126  	}
   127  	if err != nil {
   128  		return false, err
   129  	}
   130  
   131  	_, err = findSerial(st, nil)
   132  	if err == state.ErrNoState {
   133  		return false, nil
   134  	}
   135  	if err != nil {
   136  		return false, err
   137  	}
   138  
   139  	return true, nil
   140  }
   141  
   142  func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error {
   143  	kind := ""
   144  	var snapType snap.Type
   145  	var getName func(*asserts.Model) string
   146  	switch snapInfo.GetType() {
   147  	case snap.TypeGadget:
   148  		kind = "gadget"
   149  		snapType = snap.TypeGadget
   150  		getName = (*asserts.Model).Gadget
   151  	case snap.TypeKernel:
   152  		if release.OnClassic {
   153  			return fmt.Errorf("cannot install a kernel snap on classic")
   154  		}
   155  
   156  		kind = "kernel"
   157  		snapType = snap.TypeKernel
   158  		getName = (*asserts.Model).Kernel
   159  	default:
   160  		// not a relevant check
   161  		return nil
   162  	}
   163  
   164  	model := deviceCtx.Model()
   165  
   166  	if snapInfo.SnapID != "" {
   167  		snapDecl, err := assertstate.SnapDeclaration(st, snapInfo.SnapID)
   168  		if err != nil {
   169  			return fmt.Errorf("internal error: cannot find snap declaration for %q: %v", snapInfo.InstanceName(), err)
   170  		}
   171  		publisher := snapDecl.PublisherID()
   172  		if publisher != "canonical" && publisher != model.BrandID() {
   173  			return fmt.Errorf("cannot install %s %q published by %q for model by %q", kind, snapInfo.InstanceName(), publisher, model.BrandID())
   174  		}
   175  	} else {
   176  		logger.Noticef("installing unasserted %s %q", kind, snapInfo.InstanceName())
   177  	}
   178  
   179  	found, err := snapstate.HasSnapOfType(st, snapType)
   180  	if err != nil {
   181  		return fmt.Errorf("cannot detect original %s snap: %v", kind, err)
   182  	}
   183  	if found {
   184  		// already installed, snapstate takes care
   185  		return nil
   186  	}
   187  	// first installation of a gadget/kernel
   188  
   189  	expectedName := getName(model)
   190  	if expectedName == "" { // can happen only on classic
   191  		return fmt.Errorf("cannot install %s snap on classic if not requested by the model", kind)
   192  	}
   193  
   194  	if snapInfo.InstanceName() != snapInfo.SnapName() {
   195  		return fmt.Errorf("cannot install %q, parallel installation of kernel or gadget snaps is not supported", snapInfo.InstanceName())
   196  	}
   197  
   198  	if snapInfo.InstanceName() != expectedName {
   199  		return fmt.Errorf("cannot install %s %q, model assertion requests %q", kind, snapInfo.InstanceName(), expectedName)
   200  	}
   201  
   202  	return nil
   203  }
   204  
   205  var once sync.Once
   206  
   207  func delayedCrossMgrInit() {
   208  	once.Do(func() {
   209  		snapstate.AddCheckSnapCallback(checkGadgetOrKernel)
   210  	})
   211  	snapstate.CanAutoRefresh = canAutoRefresh
   212  	snapstate.CanManageRefreshes = CanManageRefreshes
   213  	snapstate.IsOnMeteredConnection = netutil.IsOnMeteredConnection
   214  	snapstate.DeviceCtx = DeviceCtx
   215  	snapstate.Remodeling = Remodeling
   216  }
   217  
   218  // proxyStore returns the store assertion for the proxy store if one is set.
   219  func proxyStore(st *state.State, tr *config.Transaction) (*asserts.Store, error) {
   220  	var proxyStore string
   221  	err := tr.GetMaybe("core", "proxy.store", &proxyStore)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	if proxyStore == "" {
   226  		return nil, state.ErrNoState
   227  	}
   228  
   229  	a, err := assertstate.DB(st).Find(asserts.StoreType, map[string]string{
   230  		"store": proxyStore,
   231  	})
   232  	if asserts.IsNotFound(err) {
   233  		return nil, state.ErrNoState
   234  	}
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	return a.(*asserts.Store), nil
   240  }
   241  
   242  // interfaceConnected returns true if the given snap/interface names
   243  // are connected
   244  func interfaceConnected(st *state.State, snapName, ifName string) bool {
   245  	conns, err := ifacerepo.Get(st).Connected(snapName, ifName)
   246  	return err == nil && len(conns) > 0
   247  }
   248  
   249  // CanManageRefreshes returns true if the device can be
   250  // switched to the "core.refresh.schedule=managed" mode.
   251  //
   252  // TODO:
   253  // - Move the CanManageRefreshes code into the ifstate
   254  // - Look at the connections and find the connection for snapd-control
   255  //   with the managed attribute
   256  // - Take the snap from this connection and look at the snapstate to see
   257  //   if that snap has a snap declaration (to ensure it comes from the store)
   258  func CanManageRefreshes(st *state.State) bool {
   259  	snapStates, err := snapstate.All(st)
   260  	if err != nil {
   261  		return false
   262  	}
   263  	for _, snapst := range snapStates {
   264  		// Always get the current info even if the snap is currently
   265  		// being operated on or if its disabled.
   266  		info, err := snapst.CurrentInfo()
   267  		if err != nil {
   268  			continue
   269  		}
   270  		if info.Broken != "" {
   271  			continue
   272  		}
   273  		// The snap must have a snap declaration (implies that
   274  		// its from the store)
   275  		if _, err := assertstate.SnapDeclaration(st, info.SideInfo.SnapID); err != nil {
   276  			continue
   277  		}
   278  
   279  		for _, plugInfo := range info.Plugs {
   280  			if plugInfo.Interface == "snapd-control" && plugInfo.Attrs["refresh-schedule"] == "managed" {
   281  				snapName := info.InstanceName()
   282  				plugName := plugInfo.Name
   283  				if interfaceConnected(st, snapName, plugName) {
   284  					return true
   285  				}
   286  			}
   287  		}
   288  	}
   289  
   290  	return false
   291  }
   292  
   293  func getAllRequiredSnapsForModel(model *asserts.Model) *naming.SnapSet {
   294  	reqSnaps := model.RequiredWithEssentialSnaps()
   295  	return naming.NewSnapSet(reqSnaps)
   296  }
   297  
   298  // extractDownloadInstallEdgesFromTs extracts the first, last download
   299  // phase and install phase tasks from a TaskSet
   300  func extractDownloadInstallEdgesFromTs(ts *state.TaskSet) (firstDl, lastDl, firstInst, lastInst *state.Task, err error) {
   301  	edgeTask, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge)
   302  	if err != nil {
   303  		return nil, nil, nil, nil, err
   304  	}
   305  	tasks := ts.Tasks()
   306  	// we know we always start with downloads
   307  	firstDl = tasks[0]
   308  	// and always end with installs
   309  	lastInst = tasks[len(tasks)-1]
   310  
   311  	var edgeTaskIndex int
   312  	for i, task := range tasks {
   313  		if task == edgeTask {
   314  			edgeTaskIndex = i
   315  			break
   316  		}
   317  	}
   318  	return firstDl, tasks[edgeTaskIndex], tasks[edgeTaskIndex+1], lastInst, nil
   319  }
   320  
   321  func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) {
   322  	userID := 0
   323  	var tss []*state.TaskSet
   324  
   325  	// adjust kernel track
   326  	if current.Kernel() == new.Kernel() && current.KernelTrack() != new.KernelTrack() {
   327  		ts, err := snapstateUpdateWithDeviceContext(st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange)
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  		tss = append(tss, ts)
   332  	}
   333  	// add new kernel
   334  	if current.Kernel() != new.Kernel() {
   335  		// TODO: we need to support corner cases here like:
   336  		//  0. start with "old-kernel"
   337  		//  1. remodel to "new-kernel"
   338  		//  2. remodel back to "old-kernel"
   339  		// In step (2) we will get a "already-installed" error
   340  		// here right now (workaround: remove "old-kernel")
   341  		ts, err := snapstateInstallWithDeviceContext(ctx, st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange)
   342  		if err != nil {
   343  			return nil, err
   344  		}
   345  		tss = append(tss, ts)
   346  	}
   347  
   348  	// add new required-snaps, no longer required snaps will be cleaned
   349  	// in "set-model"
   350  	for _, snapRef := range new.RequiredNoEssentialSnaps() {
   351  		// TODO|XXX: have methods that take refs directly
   352  		// to respect the snap ids
   353  		_, err := snapstate.CurrentInfo(st, snapRef.SnapName())
   354  		// If the snap is not installed we need to install it now.
   355  		if _, ok := err.(*snap.NotInstalledError); ok {
   356  			ts, err := snapstateInstallWithDeviceContext(ctx, st, snapRef.SnapName(), nil, userID, snapstate.Flags{Required: true}, deviceCtx, fromChange)
   357  			if err != nil {
   358  				return nil, err
   359  			}
   360  			tss = append(tss, ts)
   361  		} else if err != nil {
   362  			return nil, err
   363  		}
   364  	}
   365  	// TODO: Validate that all bases and default-providers are part
   366  	//       of the install tasksets and error if not. If the
   367  	//       prereq task handler check starts adding installs into
   368  	//       our remodel change our carefully constructed wait chain
   369  	//       breaks down.
   370  
   371  	// Ensure all download/check tasks are run *before* the install
   372  	// tasks. During a remodel the network may not be available so
   373  	// we need to ensure we have everything local.
   374  	var lastDownloadInChain, firstInstallInChain *state.Task
   375  	var prevDownload, prevInstall *state.Task
   376  	for _, ts := range tss {
   377  		// make sure all things happen sequentially
   378  		// Terminology
   379  		// A <- B means B waits for A
   380  		// "download,verify" are part of the "Download" phase
   381  		// "link,start" is part of "Install" phase
   382  		//
   383  		// - all tasks inside ts{Download,Install} already wait for
   384  		//   each other so the chains look something like this:
   385  		//     download1 <- verify1 <- install1
   386  		//     download2 <- verify2 <- install2
   387  		//     download3 <- verify3 <- install3
   388  		// - add wait of each first ts{Download,Install} task for
   389  		//   the last previous ts{Download,Install} task
   390  		//   Our chains now looks like:
   391  		//     download1 <- verify1 <- install1 (as before)
   392  		//     download2 <- verify2 <- install2 (as before)
   393  		//     download3 <- verify3 <- install3 (as before)
   394  		//     verify1 <- download2 (added)
   395  		//     verify2 <- download3 (added)
   396  		//     install1  <- install2 (added)
   397  		//     install2  <- install3 (added)
   398  		downloadStart, downloadLast, installFirst, installLast, err := extractDownloadInstallEdgesFromTs(ts)
   399  		if err != nil {
   400  			return nil, fmt.Errorf("cannot remodel: %v", err)
   401  		}
   402  		if prevDownload != nil {
   403  			// XXX: we don't strictly need to serialize the download
   404  			downloadStart.WaitFor(prevDownload)
   405  		}
   406  		if prevInstall != nil {
   407  			installFirst.WaitFor(prevInstall)
   408  		}
   409  		prevDownload = downloadLast
   410  		prevInstall = installLast
   411  		// update global state
   412  		lastDownloadInChain = downloadLast
   413  		if firstInstallInChain == nil {
   414  			firstInstallInChain = installFirst
   415  		}
   416  	}
   417  	// Make sure the first install waits for the last download. With this
   418  	// our (simplified) wait chain looks like:
   419  	// download1 <- verify1 <- download2 <- verify2 <- download3 <- verify3 <- install1 <- install2 <- install3
   420  	if firstInstallInChain != nil && lastDownloadInChain != nil {
   421  		firstInstallInChain.WaitFor(lastDownloadInChain)
   422  	}
   423  
   424  	// Set the new model assertion - this *must* be the last thing done
   425  	// by the change.
   426  	setModel := st.NewTask("set-model", i18n.G("Set new model assertion"))
   427  	for _, tsPrev := range tss {
   428  		setModel.WaitAll(tsPrev)
   429  	}
   430  	tss = append(tss, state.NewTaskSet(setModel))
   431  
   432  	return tss, nil
   433  }
   434  
   435  // Remodel takes a new model assertion and generates a change that
   436  // takes the device from the old to the new model or an error if the
   437  // transition is not possible.
   438  //
   439  // TODO:
   440  // - Check estimated disk size delta
   441  // - Reapply gadget connections as needed
   442  // - Check all relevant snaps exist in new store
   443  //   (need to check that even unchanged snaps are accessible)
   444  func Remodel(st *state.State, new *asserts.Model) (*state.Change, error) {
   445  	var seeded bool
   446  	err := st.Get("seeded", &seeded)
   447  	if err != nil && err != state.ErrNoState {
   448  		return nil, err
   449  	}
   450  	if !seeded {
   451  		return nil, fmt.Errorf("cannot remodel until fully seeded")
   452  	}
   453  
   454  	current, err := findModel(st)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  	if current.Series() != new.Series() {
   459  		return nil, fmt.Errorf("cannot remodel to different series yet")
   460  	}
   461  
   462  	// TODO: we need dedicated assertion language to permit for
   463  	// model transitions before we allow cross vault
   464  	// transitions.
   465  
   466  	remodelKind := ClassifyRemodel(current, new)
   467  
   468  	// TODO: should we restrict remodel from one arch to another?
   469  	// There are valid use-cases here though, i.e. amd64 machine that
   470  	// remodels itself to/from i386 (if the HW can do both 32/64 bit)
   471  	if current.Architecture() != new.Architecture() {
   472  		return nil, fmt.Errorf("cannot remodel to different architectures yet")
   473  	}
   474  
   475  	// calculate snap differences between the two models
   476  	// FIXME: this needs work to switch the base to boot as well
   477  	if current.Base() != new.Base() {
   478  		return nil, fmt.Errorf("cannot remodel to different bases yet")
   479  	}
   480  	if current.Gadget() != new.Gadget() {
   481  		return nil, fmt.Errorf("cannot remodel to different gadgets yet")
   482  	}
   483  
   484  	// TODO: should we run a remodel only while no other change is
   485  	// running?  do we add a task upfront that waits for that to be
   486  	// true? Do we do this only for the more complicated cases
   487  	// (anything more than adding required-snaps really)?
   488  
   489  	remodCtx, err := remodelCtx(st, current, new)
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  
   494  	var tss []*state.TaskSet
   495  	switch remodelKind {
   496  	case ReregRemodel:
   497  		// nothing else can be in-flight
   498  		for _, chg := range st.Changes() {
   499  			if !chg.IsReady() {
   500  				return nil, &snapstate.ChangeConflictError{Message: "cannot start complete remodel, other changes are in progress"}
   501  			}
   502  		}
   503  
   504  		requestSerial := st.NewTask("request-serial", i18n.G("Request new device serial"))
   505  
   506  		prepare := st.NewTask("prepare-remodeling", i18n.G("Prepare remodeling"))
   507  		prepare.WaitFor(requestSerial)
   508  		ts := state.NewTaskSet(requestSerial, prepare)
   509  		tss = []*state.TaskSet{ts}
   510  	case StoreSwitchRemodel:
   511  		sto := remodCtx.Store()
   512  		if sto == nil {
   513  			return nil, fmt.Errorf("internal error: a store switch remodeling should have built a store")
   514  		}
   515  		// ensure a new session accounting for the new brand store
   516  		st.Unlock()
   517  		_, err := sto.EnsureDeviceSession()
   518  		st.Lock()
   519  		if err != nil {
   520  			return nil, fmt.Errorf("cannot get a store session based on the new model assertion: %v", err)
   521  		}
   522  		fallthrough
   523  	case UpdateRemodel:
   524  		var err error
   525  		tss, err = remodelTasks(context.TODO(), st, current, new, remodCtx, "")
   526  		if err != nil {
   527  			return nil, err
   528  		}
   529  	}
   530  
   531  	// we potentially released the lock a couple of times here:
   532  	// make sure the current model is essentially the same as when
   533  	// we started
   534  	current1, err := findModel(st)
   535  	if err != nil {
   536  		return nil, err
   537  	}
   538  	if current.BrandID() != current1.BrandID() || current.Model() != current1.Model() || current.Revision() != current1.Revision() {
   539  		return nil, &snapstate.ChangeConflictError{Message: fmt.Sprintf("cannot start remodel, clashing with concurrent remodel to %v/%v (%v)", current1.BrandID(), current1.Model(), current1.Revision())}
   540  	}
   541  	// make sure another unfinished remodel wasn't already setup either
   542  	if Remodeling(st) {
   543  		return nil, &snapstate.ChangeConflictError{Message: "cannot start remodel, clashing with concurrent one"}
   544  	}
   545  
   546  	var msg string
   547  	if current.BrandID() == new.BrandID() && current.Model() == new.Model() {
   548  		msg = fmt.Sprintf(i18n.G("Refresh model assertion from revision %v to %v"), current.Revision(), new.Revision())
   549  	} else {
   550  		msg = fmt.Sprintf(i18n.G("Remodel device to %v/%v (%v)"), new.BrandID(), new.Model(), new.Revision())
   551  	}
   552  
   553  	chg := st.NewChange("remodel", msg)
   554  	remodCtx.Init(chg)
   555  	for _, ts := range tss {
   556  		chg.AddAll(ts)
   557  	}
   558  
   559  	return chg, nil
   560  }
   561  
   562  // Remodeling returns true whether there's a remodeling in progress
   563  func Remodeling(st *state.State) bool {
   564  	for _, chg := range st.Changes() {
   565  		if !chg.IsReady() && chg.Kind() == "remodel" {
   566  			return true
   567  		}
   568  	}
   569  	return false
   570  }