github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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/boot"
    29  	"github.com/snapcore/snapd/dirs"
    30  	"github.com/snapcore/snapd/gadget"
    31  	"github.com/snapcore/snapd/logger"
    32  	"github.com/snapcore/snapd/overlord/snapstate"
    33  	"github.com/snapcore/snapd/overlord/state"
    34  	"github.com/snapcore/snapd/release"
    35  	"github.com/snapcore/snapd/snap"
    36  )
    37  
    38  func makeRollbackDir(name string) (string, error) {
    39  	rollbackDir := filepath.Join(dirs.SnapRollbackDir, name)
    40  
    41  	if err := os.MkdirAll(rollbackDir, 0750); err != nil {
    42  		return "", err
    43  	}
    44  
    45  	return rollbackDir, nil
    46  }
    47  
    48  func currentGadgetInfo(st *state.State, curDeviceCtx snapstate.DeviceContext) (*gadget.GadgetData, error) {
    49  	currentInfo, err := snapstate.GadgetInfo(st, curDeviceCtx)
    50  	if err != nil && err != state.ErrNoState {
    51  		return nil, err
    52  	}
    53  	if currentInfo == nil {
    54  		// no current yet
    55  		return nil, nil
    56  	}
    57  
    58  	ci, err := gadgetDataFromInfo(currentInfo, curDeviceCtx.Model())
    59  	if err != nil {
    60  		return nil, fmt.Errorf("cannot read current gadget snap details: %v", err)
    61  	}
    62  	return ci, nil
    63  }
    64  
    65  func pendingGadgetInfo(snapsup *snapstate.SnapSetup, pendingDeviceCtx snapstate.DeviceContext) (*gadget.GadgetData, error) {
    66  	info, err := snap.ReadInfo(snapsup.InstanceName(), snapsup.SideInfo)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("cannot read candidate gadget snap details: %v", err)
    69  	}
    70  
    71  	gi, err := gadgetDataFromInfo(info, pendingDeviceCtx.Model())
    72  	if err != nil {
    73  		return nil, fmt.Errorf("cannot read candidate snap gadget metadata: %v", err)
    74  	}
    75  	return gi, nil
    76  }
    77  
    78  var (
    79  	gadgetUpdate = gadget.Update
    80  )
    81  
    82  func (m *DeviceManager) doUpdateGadgetAssets(t *state.Task, _ *tomb.Tomb) error {
    83  	if release.OnClassic {
    84  		return fmt.Errorf("cannot run update gadget assets task on a classic system")
    85  	}
    86  
    87  	st := t.State()
    88  	st.Lock()
    89  	defer st.Unlock()
    90  
    91  	snapsup, err := snapstate.TaskSnapSetup(t)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	remodelCtx, err := DeviceCtx(st, t, nil)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	isRemodel := remodelCtx.ForRemodeling()
   101  	groundDeviceCtx := remodelCtx.GroundContext()
   102  
   103  	model := groundDeviceCtx.Model()
   104  	if isRemodel {
   105  		model = remodelCtx.Model()
   106  	}
   107  	// be extra paranoid when checking we are installing the right gadget
   108  	var updateData *gadget.GadgetData
   109  	switch snapsup.Type {
   110  	case snap.TypeGadget:
   111  		expectedGadgetSnap := model.Gadget()
   112  		if snapsup.InstanceName() != expectedGadgetSnap {
   113  			return fmt.Errorf("cannot apply gadget assets update from non-model gadget snap %q, expected %q snap",
   114  				snapsup.InstanceName(), expectedGadgetSnap)
   115  		}
   116  
   117  		updateData, err = pendingGadgetInfo(snapsup, remodelCtx)
   118  		if err != nil {
   119  			return err
   120  		}
   121  	case snap.TypeKernel:
   122  		expectedKernelSnap := model.Kernel()
   123  		if snapsup.InstanceName() != expectedKernelSnap {
   124  			return fmt.Errorf("cannot apply kernel assets update from non-model kernel snap %q, expected %q snap",
   125  				snapsup.InstanceName(), expectedKernelSnap)
   126  		}
   127  
   128  		// now calculate the "update" data, it's the same gadget but
   129  		// argumented from a different kernel
   130  		updateData, err = currentGadgetInfo(t.State(), groundDeviceCtx)
   131  		if err != nil {
   132  			return err
   133  		}
   134  	default:
   135  		return fmt.Errorf("internal errror: doUpdateGadgetAssets called with snap type %v", snapsup.Type)
   136  	}
   137  
   138  	currentData, err := currentGadgetInfo(t.State(), groundDeviceCtx)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	if currentData == nil {
   143  		// no updates during first boot & seeding
   144  		return nil
   145  	}
   146  
   147  	// add kernel directories
   148  	currentKernelInfo, err := snapstate.CurrentInfo(st, groundDeviceCtx.Model().Kernel())
   149  	// XXX: switch to the normal `if err != nil { return err }` pattern
   150  	// here once all tests are updated and have a kernel
   151  	if err == nil {
   152  		currentData.KernelRootDir = currentKernelInfo.MountDir()
   153  		updateData.KernelRootDir = currentKernelInfo.MountDir()
   154  	}
   155  	// if this is a gadget update triggered by an updated kernel we
   156  	// need to ensure "updateData.KernelRootDir" points to the new kernel
   157  	if snapsup.Type == snap.TypeKernel {
   158  		updateKernelInfo, err := snap.ReadInfo(snapsup.InstanceName(), snapsup.SideInfo)
   159  		if err != nil {
   160  			return fmt.Errorf("cannot read candidate kernel snap details: %v", err)
   161  		}
   162  		updateData.KernelRootDir = updateKernelInfo.MountDir()
   163  	}
   164  
   165  	snapRollbackDir, err := makeRollbackDir(fmt.Sprintf("%v_%v", snapsup.InstanceName(), snapsup.SideInfo.Revision))
   166  	if err != nil {
   167  		return fmt.Errorf("cannot prepare update rollback directory: %v", err)
   168  	}
   169  
   170  	var updatePolicy gadget.UpdatePolicyFunc = nil
   171  
   172  	// Even with a remodel a kernel refresh only updates the kernel assets
   173  	if snapsup.Type == snap.TypeKernel {
   174  		updatePolicy = gadget.KernelUpdatePolicy
   175  	} else if isRemodel {
   176  		// use the remodel policy which triggers an update of all
   177  		// structures
   178  		updatePolicy = gadget.RemodelUpdatePolicy
   179  	}
   180  
   181  	var updateObserver gadget.ContentUpdateObserver
   182  	observeTrustedBootAssets, err := boot.TrustedAssetsUpdateObserverForModel(model, updateData.RootDir)
   183  	if err != nil && err != boot.ErrObserverNotApplicable {
   184  		return fmt.Errorf("cannot setup asset update observer: %v", err)
   185  	}
   186  	if err == nil {
   187  		updateObserver = observeTrustedBootAssets
   188  	}
   189  	// do not release the state lock, the update observer may attempt to
   190  	// modify modeenv inside, which implicitly is guarded by the state lock;
   191  	// on top of that we do not expect the update to be moving large amounts
   192  	// of data
   193  	err = gadgetUpdate(*currentData, *updateData, snapRollbackDir, updatePolicy, updateObserver)
   194  	if err != nil {
   195  		if err == gadget.ErrNoUpdate {
   196  			// no update needed
   197  			t.Logf("No gadget assets update needed")
   198  			return nil
   199  		}
   200  		return err
   201  	}
   202  
   203  	t.SetStatus(state.DoneStatus)
   204  
   205  	if err := os.RemoveAll(snapRollbackDir); err != nil && !os.IsNotExist(err) {
   206  		logger.Noticef("failed to remove gadget update rollback directory %q: %v", snapRollbackDir, err)
   207  	}
   208  
   209  	// TODO: consider having the option to do this early via recovery in
   210  	// core20, have fallback code as well there
   211  	st.RequestRestart(state.RestartSystem)
   212  
   213  	return nil
   214  }