github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/overlord/devicestate/handlers_remodel.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  /*
     3   * Copyright (C) 2016-2017 Canonical Ltd
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License version 3 as
     7   * published by the Free Software Foundation.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   *
    17   */
    18  
    19  package devicestate
    20  
    21  import (
    22  	"fmt"
    23  
    24  	"gopkg.in/tomb.v2"
    25  
    26  	"github.com/snapcore/snapd/asserts"
    27  	"github.com/snapcore/snapd/boot"
    28  	"github.com/snapcore/snapd/gadget"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/overlord/assertstate"
    31  	"github.com/snapcore/snapd/overlord/snapstate"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  	"github.com/snapcore/snapd/release"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/snap/naming"
    36  )
    37  
    38  func isSameAssertsRevision(err error) bool {
    39  	if e, ok := err.(*asserts.RevisionError); ok {
    40  		if e.Used == e.Current {
    41  			return true
    42  		}
    43  	}
    44  	return false
    45  }
    46  
    47  var injectedSetModelError error
    48  
    49  // InjectSetModelError will trigger the selected error in the doSetModel
    50  // handler. This is only useful for testing.
    51  func InjectSetModelError(err error) {
    52  	injectedSetModelError = err
    53  }
    54  
    55  func (m *DeviceManager) doSetModel(t *state.Task, _ *tomb.Tomb) (err error) {
    56  	st := t.State()
    57  	st.Lock()
    58  	defer st.Unlock()
    59  
    60  	remodCtx, err := remodelCtxFromTask(t)
    61  	if err != nil {
    62  		return err
    63  	}
    64  	new := remodCtx.Model()
    65  
    66  	// unmark no-longer required snaps
    67  	var cleanedRequiredSnaps []string
    68  	requiredSnaps := getAllRequiredSnapsForModel(new)
    69  	// TODO|XXX: have AllByRef
    70  	snapStates, err := snapstate.All(st)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	for snapName, snapst := range snapStates {
    75  		// TODO: remove this type restriction once we remodel
    76  		//       gadgets and add tests that ensure
    77  		//       that the required flag is properly set/unset
    78  		typ, err := snapst.Type()
    79  		if err != nil {
    80  			return err
    81  		}
    82  		if typ != snap.TypeApp && typ != snap.TypeBase && typ != snap.TypeKernel {
    83  			continue
    84  		}
    85  		// clean required flag if no-longer needed
    86  		if snapst.Flags.Required && !requiredSnaps.Contains(naming.Snap(snapName)) {
    87  			snapst.Flags.Required = false
    88  			snapstate.Set(st, snapName, snapst)
    89  			cleanedRequiredSnaps = append(cleanedRequiredSnaps, snapName)
    90  		}
    91  		// TODO: clean "required" flag of "core" if a remodel
    92  		//       moves from the "core" snap to a different
    93  		//       bootable base snap.
    94  	}
    95  	// ensure  we undo the cleanedRequiredSnaps if e.g. remodCtx
    96  	defer func() {
    97  		if err == nil {
    98  			return
    99  		}
   100  		var snapst snapstate.SnapState
   101  		for _, snapName := range cleanedRequiredSnaps {
   102  			if err := snapstate.Get(st, snapName, &snapst); err == nil {
   103  				snapst.Flags.Required = true
   104  				snapstate.Set(st, snapName, &snapst)
   105  			}
   106  		}
   107  	}()
   108  
   109  	// only useful for testing
   110  	if injectedSetModelError != nil {
   111  		return injectedSetModelError
   112  	}
   113  
   114  	// add the assertion only after everything else was successful
   115  	err = assertstate.Add(st, new)
   116  	if err != nil && !isSameAssertsRevision(err) {
   117  		return err
   118  	}
   119  
   120  	var recoverySetup *recoverySystemSetup
   121  	if new.Grade() != asserts.ModelGradeUnset {
   122  		var triedSystems []string
   123  		if err := st.Get("tried-systems", &triedSystems); err != nil {
   124  			return fmt.Errorf("cannot obtain tried recovery systems: %v", err)
   125  		}
   126  		recoverySetup, err = taskRecoverySystemSetup(t)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		// should promoting or any of the later steps fails, the cleanup
   131  		// will be done in finalize-recovery-system undo
   132  		if err := boot.PromoteTriedRecoverySystem(remodCtx, recoverySetup.Label, triedSystems); err != nil {
   133  			return err
   134  		}
   135  		remodCtx.setRecoverySystemLabel(recoverySetup.Label)
   136  	}
   137  
   138  	logEverywhere := func(format string, args ...interface{}) {
   139  		t.Logf(format, args)
   140  		logger.Noticef(format, args)
   141  	}
   142  
   143  	// and finish (this will set the new model), note that changes done in
   144  	// here are not recoverable even if an error occurs
   145  	if err := remodCtx.Finish(); err != nil {
   146  		logEverywhere("cannot complete remodel: %v", err)
   147  	}
   148  
   149  	t.SetStatus(state.DoneStatus)
   150  
   151  	return nil
   152  }
   153  
   154  func (m *DeviceManager) cleanupRemodel(t *state.Task, _ *tomb.Tomb) error {
   155  	st := t.State()
   156  	st.Lock()
   157  	defer st.Unlock()
   158  	// cleanup the cached remodel context
   159  	cleanupRemodelCtx(t.Change())
   160  	return nil
   161  }
   162  
   163  func (m *DeviceManager) doPrepareRemodeling(t *state.Task, tmb *tomb.Tomb) error {
   164  	st := t.State()
   165  	st.Lock()
   166  	defer st.Unlock()
   167  
   168  	remodCtx, err := remodelCtxFromTask(t)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	current, err := findModel(st)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	sto := remodCtx.Store()
   178  	if sto == nil {
   179  		return fmt.Errorf("internal error: re-registration remodeling should have built a store")
   180  	}
   181  	// ensure a new session accounting for the new brand/model
   182  	st.Unlock()
   183  	_, err = sto.EnsureDeviceSession()
   184  	st.Lock()
   185  	if err != nil {
   186  		return fmt.Errorf("cannot get a store session based on the new model assertion: %v", err)
   187  	}
   188  
   189  	chgID := t.Change().ID()
   190  
   191  	tss, err := remodelTasks(tmb.Context(nil), st, current, remodCtx.Model(), remodCtx, chgID)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	allTs := state.NewTaskSet()
   197  	for _, ts := range tss {
   198  		allTs.AddAll(ts)
   199  	}
   200  	snapstate.InjectTasks(t, allTs)
   201  
   202  	st.EnsureBefore(0)
   203  	t.SetStatus(state.DoneStatus)
   204  
   205  	return nil
   206  }
   207  
   208  var (
   209  	gadgetIsCompatible = gadget.IsCompatible
   210  )
   211  
   212  func checkGadgetRemodelCompatible(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error {
   213  	if release.OnClassic {
   214  		return nil
   215  	}
   216  	if snapInfo.Type() != snap.TypeGadget {
   217  		// We are only interested in gadget snaps.
   218  		return nil
   219  	}
   220  	if deviceCtx == nil || !deviceCtx.ForRemodeling() {
   221  		// We are only interesting in a remodeling scenario.
   222  		return nil
   223  	}
   224  
   225  	if curInfo == nil {
   226  		// snap isn't installed yet, we are likely remodeling to a new
   227  		// gadget, identify the old gadget
   228  		curInfo, _ = snapstate.GadgetInfo(st, deviceCtx.GroundContext())
   229  	}
   230  	if curInfo == nil {
   231  		return fmt.Errorf("cannot identify the current gadget snap")
   232  	}
   233  
   234  	pendingInfo, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model())
   235  	if err != nil {
   236  		return fmt.Errorf("cannot read new gadget metadata: %v", err)
   237  	}
   238  
   239  	currentData, err := gadgetDataFromInfo(curInfo, deviceCtx.GroundContext().Model())
   240  	if err != nil {
   241  		return fmt.Errorf("cannot read current gadget metadata: %v", err)
   242  	}
   243  
   244  	if err := gadgetIsCompatible(currentData.Info, pendingInfo); err != nil {
   245  		return fmt.Errorf("cannot remodel to an incompatible gadget: %v", err)
   246  	}
   247  	return nil
   248  }