gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/handlers_gadget.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  	"os"
    24  	"path/filepath"
    25  
    26  	"gopkg.in/tomb.v2"
    27  
    28  	"github.com/snapcore/snapd/asserts"
    29  	"github.com/snapcore/snapd/boot"
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/gadget"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/overlord/snapstate"
    34  	"github.com/snapcore/snapd/overlord/state"
    35  	"github.com/snapcore/snapd/release"
    36  	"github.com/snapcore/snapd/snap"
    37  )
    38  
    39  func makeRollbackDir(name string) (string, error) {
    40  	rollbackDir := filepath.Join(dirs.SnapRollbackDir, name)
    41  
    42  	if err := os.MkdirAll(rollbackDir, 0750); err != nil {
    43  		return "", err
    44  	}
    45  
    46  	return rollbackDir, nil
    47  }
    48  
    49  func currentGadgetInfo(st *state.State, curDeviceCtx snapstate.DeviceContext) (*gadget.GadgetData, error) {
    50  	currentInfo, err := snapstate.GadgetInfo(st, curDeviceCtx)
    51  	if err != nil && err != state.ErrNoState {
    52  		return nil, err
    53  	}
    54  	if currentInfo == nil {
    55  		// no current yet
    56  		return nil, nil
    57  	}
    58  
    59  	ci, err := gadgetDataFromInfo(currentInfo, curDeviceCtx.Model())
    60  	if err != nil {
    61  		return nil, fmt.Errorf("cannot read current gadget snap details: %v", err)
    62  	}
    63  	return ci, nil
    64  }
    65  
    66  func pendingGadgetInfo(snapsup *snapstate.SnapSetup, pendingDeviceCtx snapstate.DeviceContext) (*gadget.GadgetData, error) {
    67  	info, err := snap.ReadInfo(snapsup.InstanceName(), snapsup.SideInfo)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("cannot read candidate gadget snap details: %v", err)
    70  	}
    71  
    72  	gi, err := gadgetDataFromInfo(info, pendingDeviceCtx.Model())
    73  	if err != nil {
    74  		return nil, fmt.Errorf("cannot read candidate snap gadget metadata: %v", err)
    75  	}
    76  	return gi, nil
    77  }
    78  
    79  var (
    80  	gadgetUpdate = gadget.Update
    81  )
    82  
    83  func (m *DeviceManager) doUpdateGadgetAssets(t *state.Task, _ *tomb.Tomb) error {
    84  	if release.OnClassic {
    85  		return fmt.Errorf("cannot run update gadget assets task on a classic system")
    86  	}
    87  
    88  	st := t.State()
    89  	st.Lock()
    90  	defer st.Unlock()
    91  
    92  	snapsup, err := snapstate.TaskSnapSetup(t)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	remodelCtx, err := DeviceCtx(st, t, nil)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	isRemodel := remodelCtx.ForRemodeling()
   102  	groundDeviceCtx := remodelCtx.GroundContext()
   103  
   104  	model := groundDeviceCtx.Model()
   105  	if isRemodel {
   106  		model = remodelCtx.Model()
   107  	}
   108  	// be extra paranoid when checking we are installing the right gadget
   109  	var updateData *gadget.GadgetData
   110  	switch snapsup.Type {
   111  	case snap.TypeGadget:
   112  		expectedGadgetSnap := model.Gadget()
   113  		if snapsup.InstanceName() != expectedGadgetSnap {
   114  			return fmt.Errorf("cannot apply gadget assets update from non-model gadget snap %q, expected %q snap",
   115  				snapsup.InstanceName(), expectedGadgetSnap)
   116  		}
   117  
   118  		updateData, err = pendingGadgetInfo(snapsup, remodelCtx)
   119  		if err != nil {
   120  			return err
   121  		}
   122  	case snap.TypeKernel:
   123  		expectedKernelSnap := model.Kernel()
   124  		if snapsup.InstanceName() != expectedKernelSnap {
   125  			return fmt.Errorf("cannot apply kernel assets update from non-model kernel snap %q, expected %q snap",
   126  				snapsup.InstanceName(), expectedKernelSnap)
   127  		}
   128  
   129  		// now calculate the "update" data, it's the same gadget but
   130  		// argumented from a different kernel
   131  		updateData, err = currentGadgetInfo(t.State(), groundDeviceCtx)
   132  		if err != nil {
   133  			return err
   134  		}
   135  	default:
   136  		return fmt.Errorf("internal errror: doUpdateGadgetAssets called with snap type %v", snapsup.Type)
   137  	}
   138  
   139  	currentData, err := currentGadgetInfo(t.State(), groundDeviceCtx)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if currentData == nil {
   144  		// no updates during first boot & seeding
   145  		return nil
   146  	}
   147  
   148  	// add kernel directories
   149  	currentKernelInfo, err := snapstate.CurrentInfo(st, groundDeviceCtx.Model().Kernel())
   150  	// XXX: switch to the normal `if err != nil { return err }` pattern
   151  	// here once all tests are updated and have a kernel
   152  	if err == nil {
   153  		currentData.KernelRootDir = currentKernelInfo.MountDir()
   154  		updateData.KernelRootDir = currentKernelInfo.MountDir()
   155  	}
   156  	// if this is a gadget update triggered by an updated kernel we
   157  	// need to ensure "updateData.KernelRootDir" points to the new kernel
   158  	if snapsup.Type == snap.TypeKernel {
   159  		updateKernelInfo, err := snap.ReadInfo(snapsup.InstanceName(), snapsup.SideInfo)
   160  		if err != nil {
   161  			return fmt.Errorf("cannot read candidate kernel snap details: %v", err)
   162  		}
   163  		updateData.KernelRootDir = updateKernelInfo.MountDir()
   164  	}
   165  
   166  	snapRollbackDir, err := makeRollbackDir(fmt.Sprintf("%v_%v", snapsup.InstanceName(), snapsup.SideInfo.Revision))
   167  	if err != nil {
   168  		return fmt.Errorf("cannot prepare update rollback directory: %v", err)
   169  	}
   170  
   171  	var updatePolicy gadget.UpdatePolicyFunc = nil
   172  
   173  	// Even with a remodel a kernel refresh only updates the kernel assets
   174  	if snapsup.Type == snap.TypeKernel {
   175  		updatePolicy = gadget.KernelUpdatePolicy
   176  	} else if isRemodel {
   177  		// use the remodel policy which triggers an update of all
   178  		// structures
   179  		updatePolicy = gadget.RemodelUpdatePolicy
   180  	}
   181  
   182  	var updateObserver gadget.ContentUpdateObserver
   183  	observeTrustedBootAssets, err := boot.TrustedAssetsUpdateObserverForModel(model, updateData.RootDir)
   184  	if err != nil && err != boot.ErrObserverNotApplicable {
   185  		return fmt.Errorf("cannot setup asset update observer: %v", err)
   186  	}
   187  	if err == nil {
   188  		updateObserver = observeTrustedBootAssets
   189  	}
   190  	// do not release the state lock, the update observer may attempt to
   191  	// modify modeenv inside, which implicitly is guarded by the state lock;
   192  	// on top of that we do not expect the update to be moving large amounts
   193  	// of data
   194  	err = gadgetUpdate(*currentData, *updateData, snapRollbackDir, updatePolicy, updateObserver)
   195  	if err != nil {
   196  		if err == gadget.ErrNoUpdate {
   197  			// no update needed
   198  			t.Logf("No gadget assets update needed")
   199  			return nil
   200  		}
   201  		return err
   202  	}
   203  
   204  	t.SetStatus(state.DoneStatus)
   205  
   206  	if err := os.RemoveAll(snapRollbackDir); err != nil && !os.IsNotExist(err) {
   207  		logger.Noticef("failed to remove gadget update rollback directory %q: %v", snapRollbackDir, err)
   208  	}
   209  
   210  	// TODO: consider having the option to do this early via recovery in
   211  	// core20, have fallback code as well there
   212  	st.RequestRestart(state.RestartSystem)
   213  
   214  	return nil
   215  }
   216  
   217  func (m *DeviceManager) updateGadgetCommandLine(t *state.Task, st *state.State, isUndo bool) (updated bool, err error) {
   218  	snapsup, err := snapstate.TaskSnapSetup(t)
   219  	if err != nil {
   220  		return false, err
   221  	}
   222  	devCtx, err := DeviceCtx(st, t, nil)
   223  	if err != nil {
   224  		return false, err
   225  	}
   226  	if devCtx.Model().Grade() == asserts.ModelGradeUnset {
   227  		// pre UC20 system, do nothing
   228  		return false, nil
   229  	}
   230  	var gadgetData *gadget.GadgetData
   231  	if !isUndo {
   232  		// when updating, command line comes from the new gadget
   233  		gadgetData, err = pendingGadgetInfo(snapsup, devCtx)
   234  	} else {
   235  		// but when undoing, we use the current gadget which should have
   236  		// been restored
   237  		currentGadgetData, err := currentGadgetInfo(st, devCtx)
   238  		if err != nil {
   239  			return false, err
   240  		}
   241  		gadgetData = currentGadgetData
   242  	}
   243  	updated, err = boot.UpdateCommandLineForGadgetComponent(devCtx, gadgetData.RootDir)
   244  	if err != nil {
   245  		return false, fmt.Errorf("cannot update kernel command line from gadget: %v", err)
   246  	}
   247  	return updated, nil
   248  }
   249  
   250  func (m *DeviceManager) doUpdateGadgetCommandLine(t *state.Task, _ *tomb.Tomb) error {
   251  	if release.OnClassic {
   252  		return fmt.Errorf("internal error: cannot run update gadget kernel command line task on a classic system")
   253  	}
   254  
   255  	st := t.State()
   256  	st.Lock()
   257  	defer st.Unlock()
   258  
   259  	var seeded bool
   260  	err := st.Get("seeded", &seeded)
   261  	if err != nil && err != state.ErrNoState {
   262  		return err
   263  	}
   264  	if !seeded {
   265  		// do nothing during first boot & seeding
   266  		return nil
   267  	}
   268  
   269  	const isUndo = false
   270  	updated, err := m.updateGadgetCommandLine(t, st, isUndo)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	if !updated {
   275  		logger.Debugf("no kernel command line update from gadget")
   276  		return nil
   277  	}
   278  	t.Logf("Updated kernel command line")
   279  
   280  	t.SetStatus(state.DoneStatus)
   281  
   282  	// TODO: consider optimization to avoid double reboot when the gadget
   283  	// snap carries an update to the gadget assets and a change in the
   284  	// kernel command line
   285  
   286  	// kernel command line was updated, request a reboot to make it effective
   287  	st.RequestRestart(state.RestartSystem)
   288  	return nil
   289  }
   290  
   291  func (m *DeviceManager) undoUpdateGadgetCommandLine(t *state.Task, _ *tomb.Tomb) error {
   292  	if release.OnClassic {
   293  		return fmt.Errorf("internal error: cannot run update gadget kernel command line task on a classic system")
   294  	}
   295  
   296  	st := t.State()
   297  	st.Lock()
   298  	defer st.Unlock()
   299  
   300  	var seeded bool
   301  	err := st.Get("seeded", &seeded)
   302  	if err != nil && err != state.ErrNoState {
   303  		return err
   304  	}
   305  	if !seeded {
   306  		// do nothing during first boot & seeding
   307  		return nil
   308  	}
   309  
   310  	const isUndo = true
   311  	updated, err := m.updateGadgetCommandLine(t, st, isUndo)
   312  	if err != nil {
   313  		return err
   314  	}
   315  	if !updated {
   316  		logger.Debugf("no kernel command line update to undo")
   317  		return nil
   318  	}
   319  	t.Logf("Reverted kernel command line change")
   320  
   321  	t.SetStatus(state.UndoneStatus)
   322  
   323  	// kernel command line was updated, request a reboot to make it effective
   324  	st.RequestRestart(state.RestartSystem)
   325  	return nil
   326  }