github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  	if new.Grade() != asserts.ModelGradeUnset {
   119  		// TODO: update ubuntu-boot/device/model
   120  		// TODO: reseal for both the old and current model
   121  	}
   122  
   123  	// and finish (this will set the new model)
   124  	return remodCtx.Finish()
   125  }
   126  
   127  func (m *DeviceManager) cleanupRemodel(t *state.Task, _ *tomb.Tomb) error {
   128  	st := t.State()
   129  	st.Lock()
   130  	defer st.Unlock()
   131  	// cleanup the cached remodel context
   132  	cleanupRemodelCtx(t.Change())
   133  	return nil
   134  }
   135  
   136  func (m *DeviceManager) doPrepareRemodeling(t *state.Task, tmb *tomb.Tomb) error {
   137  	st := t.State()
   138  	st.Lock()
   139  	defer st.Unlock()
   140  
   141  	remodCtx, err := remodelCtxFromTask(t)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	current, err := findModel(st)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	sto := remodCtx.Store()
   151  	if sto == nil {
   152  		return fmt.Errorf("internal error: re-registration remodeling should have built a store")
   153  	}
   154  	// ensure a new session accounting for the new brand/model
   155  	st.Unlock()
   156  	_, err = sto.EnsureDeviceSession()
   157  	st.Lock()
   158  	if err != nil {
   159  		return fmt.Errorf("cannot get a store session based on the new model assertion: %v", err)
   160  	}
   161  
   162  	chgID := t.Change().ID()
   163  
   164  	tss, err := remodelTasks(tmb.Context(nil), st, current, remodCtx.Model(), remodCtx, chgID)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	allTs := state.NewTaskSet()
   170  	for _, ts := range tss {
   171  		allTs.AddAll(ts)
   172  	}
   173  	snapstate.InjectTasks(t, allTs)
   174  
   175  	st.EnsureBefore(0)
   176  	t.SetStatus(state.DoneStatus)
   177  
   178  	return nil
   179  }
   180  
   181  var (
   182  	gadgetIsCompatible = gadget.IsCompatible
   183  )
   184  
   185  func checkGadgetRemodelCompatible(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error {
   186  	if release.OnClassic {
   187  		return nil
   188  	}
   189  	if snapInfo.Type() != snap.TypeGadget {
   190  		// We are only interested in gadget snaps.
   191  		return nil
   192  	}
   193  	if deviceCtx == nil || !deviceCtx.ForRemodeling() {
   194  		// We are only interesting in a remodeling scenario.
   195  		return nil
   196  	}
   197  
   198  	if curInfo == nil {
   199  		// snap isn't installed yet, we are likely remodeling to a new
   200  		// gadget, identify the old gadget
   201  		curInfo, _ = snapstate.GadgetInfo(st, deviceCtx.GroundContext())
   202  	}
   203  	if curInfo == nil {
   204  		return fmt.Errorf("cannot identify the current gadget snap")
   205  	}
   206  
   207  	pendingInfo, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model())
   208  	if err != nil {
   209  		return fmt.Errorf("cannot read new gadget metadata: %v", err)
   210  	}
   211  
   212  	currentData, err := gadgetDataFromInfo(curInfo, deviceCtx.GroundContext().Model())
   213  	if err != nil {
   214  		return fmt.Errorf("cannot read current gadget metadata: %v", err)
   215  	}
   216  
   217  	if err := gadgetIsCompatible(currentData.Info, pendingInfo); err != nil {
   218  		return fmt.Errorf("cannot remodel to an incompatible gadget: %v", err)
   219  	}
   220  	return nil
   221  }