github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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/gadget"
    28  	"github.com/snapcore/snapd/overlord/assertstate"
    29  	"github.com/snapcore/snapd/overlord/snapstate"
    30  	"github.com/snapcore/snapd/overlord/state"
    31  	"github.com/snapcore/snapd/release"
    32  	"github.com/snapcore/snapd/snap"
    33  	"github.com/snapcore/snapd/snap/naming"
    34  )
    35  
    36  func isSameAssertsRevision(err error) bool {
    37  	if e, ok := err.(*asserts.RevisionError); ok {
    38  		if e.Used == e.Current {
    39  			return true
    40  		}
    41  	}
    42  	return false
    43  }
    44  
    45  var injectedSetModelError error
    46  
    47  // InjectSetModelError will trigger the selected error in the doSetModel
    48  // handler. This is only useful for testing.
    49  func InjectSetModelError(err error) {
    50  	injectedSetModelError = err
    51  }
    52  
    53  func (m *DeviceManager) doSetModel(t *state.Task, _ *tomb.Tomb) (err error) {
    54  	st := t.State()
    55  	st.Lock()
    56  	defer st.Unlock()
    57  
    58  	remodCtx, err := remodelCtxFromTask(t)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	new := remodCtx.Model()
    63  
    64  	// unmark no-longer required snaps
    65  	var cleanedRequiredSnaps []string
    66  	requiredSnaps := getAllRequiredSnapsForModel(new)
    67  	// TODO|XXX: have AllByRef
    68  	snapStates, err := snapstate.All(st)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	for snapName, snapst := range snapStates {
    73  		// TODO: remove this type restriction once we remodel
    74  		//       gadgets and add tests that ensure
    75  		//       that the required flag is properly set/unset
    76  		typ, err := snapst.Type()
    77  		if err != nil {
    78  			return err
    79  		}
    80  		if typ != snap.TypeApp && typ != snap.TypeBase && typ != snap.TypeKernel {
    81  			continue
    82  		}
    83  		// clean required flag if no-longer needed
    84  		if snapst.Flags.Required && !requiredSnaps.Contains(naming.Snap(snapName)) {
    85  			snapst.Flags.Required = false
    86  			snapstate.Set(st, snapName, snapst)
    87  			cleanedRequiredSnaps = append(cleanedRequiredSnaps, snapName)
    88  		}
    89  		// TODO: clean "required" flag of "core" if a remodel
    90  		//       moves from the "core" snap to a different
    91  		//       bootable base snap.
    92  	}
    93  	// ensure  we undo the cleanedRequiredSnaps if e.g. remodCtx
    94  	defer func() {
    95  		if err == nil {
    96  			return
    97  		}
    98  		var snapst snapstate.SnapState
    99  		for _, snapName := range cleanedRequiredSnaps {
   100  			if err := snapstate.Get(st, snapName, &snapst); err == nil {
   101  				snapst.Flags.Required = true
   102  				snapstate.Set(st, snapName, &snapst)
   103  			}
   104  		}
   105  	}()
   106  
   107  	// only useful for testing
   108  	if injectedSetModelError != nil {
   109  		return injectedSetModelError
   110  	}
   111  
   112  	// add the assertion only after everything else was successful
   113  	err = assertstate.Add(st, new)
   114  	if err != nil && !isSameAssertsRevision(err) {
   115  		return err
   116  	}
   117  
   118  	// and finish (this will set the new model)
   119  	return remodCtx.Finish()
   120  }
   121  
   122  func (m *DeviceManager) cleanupRemodel(t *state.Task, _ *tomb.Tomb) error {
   123  	st := t.State()
   124  	st.Lock()
   125  	defer st.Unlock()
   126  	// cleanup the cached remodel context
   127  	cleanupRemodelCtx(t.Change())
   128  	return nil
   129  }
   130  
   131  func (m *DeviceManager) doPrepareRemodeling(t *state.Task, tmb *tomb.Tomb) error {
   132  	st := t.State()
   133  	st.Lock()
   134  	defer st.Unlock()
   135  
   136  	remodCtx, err := remodelCtxFromTask(t)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	current, err := findModel(st)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	sto := remodCtx.Store()
   146  	if sto == nil {
   147  		return fmt.Errorf("internal error: re-registration remodeling should have built a store")
   148  	}
   149  	// ensure a new session accounting for the new brand/model
   150  	st.Unlock()
   151  	_, err = sto.EnsureDeviceSession()
   152  	st.Lock()
   153  	if err != nil {
   154  		return fmt.Errorf("cannot get a store session based on the new model assertion: %v", err)
   155  	}
   156  
   157  	chgID := t.Change().ID()
   158  
   159  	tss, err := remodelTasks(tmb.Context(nil), st, current, remodCtx.Model(), remodCtx, chgID)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	allTs := state.NewTaskSet()
   165  	for _, ts := range tss {
   166  		allTs.AddAll(ts)
   167  	}
   168  	snapstate.InjectTasks(t, allTs)
   169  
   170  	st.EnsureBefore(0)
   171  	t.SetStatus(state.DoneStatus)
   172  
   173  	return nil
   174  }
   175  
   176  var (
   177  	gadgetIsCompatible = gadget.IsCompatible
   178  )
   179  
   180  func checkGadgetRemodelCompatible(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error {
   181  	if release.OnClassic {
   182  		return nil
   183  	}
   184  	if snapInfo.Type() != snap.TypeGadget {
   185  		// We are only interested in gadget snaps.
   186  		return nil
   187  	}
   188  	if deviceCtx == nil || !deviceCtx.ForRemodeling() {
   189  		// We are only interesting in a remodeling scenario.
   190  		return nil
   191  	}
   192  
   193  	if curInfo == nil {
   194  		// snap isn't installed yet, we are likely remodeling to a new
   195  		// gadget, identify the old gadget
   196  		curInfo, _ = snapstate.GadgetInfo(st, deviceCtx.GroundContext())
   197  	}
   198  	if curInfo == nil {
   199  		return fmt.Errorf("cannot identify the current gadget snap")
   200  	}
   201  
   202  	pendingInfo, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model())
   203  	if err != nil {
   204  		return fmt.Errorf("cannot read new gadget metadata: %v", err)
   205  	}
   206  
   207  	currentData, err := gadgetDataFromInfo(curInfo, deviceCtx.GroundContext().Model())
   208  	if err != nil {
   209  		return fmt.Errorf("cannot read current gadget metadata: %v", err)
   210  	}
   211  
   212  	if err := gadgetIsCompatible(currentData.Info, pendingInfo); err != nil {
   213  		return fmt.Errorf("cannot remodel to an incompatible gadget: %v", err)
   214  	}
   215  	return nil
   216  }