github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	expectedGadgetSnap := model.Gadget()
   109  	if snapsup.InstanceName() != expectedGadgetSnap {
   110  		return fmt.Errorf("cannot apply gadget assets update from non-model gadget snap %q, expected %q snap",
   111  			snapsup.InstanceName(), expectedGadgetSnap)
   112  	}
   113  
   114  	updateData, err := pendingGadgetInfo(snapsup, remodelCtx)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	currentData, err := currentGadgetInfo(t.State(), groundDeviceCtx)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	if currentData == nil {
   124  		// no updates during first boot & seeding
   125  		return nil
   126  	}
   127  
   128  	snapRollbackDir, err := makeRollbackDir(fmt.Sprintf("%v_%v", snapsup.InstanceName(), snapsup.SideInfo.Revision))
   129  	if err != nil {
   130  		return fmt.Errorf("cannot prepare update rollback directory: %v", err)
   131  	}
   132  
   133  	var updatePolicy gadget.UpdatePolicyFunc = nil
   134  
   135  	if isRemodel {
   136  		// use the remodel policy which triggers an update of all
   137  		// structures
   138  		updatePolicy = gadget.RemodelUpdatePolicy
   139  	}
   140  
   141  	var updateObserver gadget.ContentUpdateObserver
   142  	observeTrustedBootAssets, err := boot.TrustedAssetsUpdateObserverForModel(model)
   143  	if err != nil && err != boot.ErrObserverNotApplicable {
   144  		return fmt.Errorf("cannot setup asset update observer: %v", err)
   145  	}
   146  	if err == nil {
   147  		updateObserver = observeTrustedBootAssets
   148  	}
   149  	// do not release the state lock, the update observer may attempt to
   150  	// modify modeenv inside, which implicitly is guarded by the state lock;
   151  	// on top of that we do not expect the update to be moving large amounts
   152  	// of data
   153  	err = gadgetUpdate(*currentData, *updateData, snapRollbackDir, updatePolicy, updateObserver)
   154  	if err != nil {
   155  		if err == gadget.ErrNoUpdate {
   156  			// no update needed
   157  			t.Logf("No gadget assets update needed")
   158  			return nil
   159  		}
   160  		return err
   161  	}
   162  
   163  	t.SetStatus(state.DoneStatus)
   164  
   165  	if err := os.RemoveAll(snapRollbackDir); err != nil && !os.IsNotExist(err) {
   166  		logger.Noticef("failed to remove gadget update rollback directory %q: %v", snapRollbackDir, err)
   167  	}
   168  
   169  	// TODO: consider having the option to do this early via recovery in
   170  	// core20, have fallback code as well there
   171  	st.RequestRestart(state.RestartSystem)
   172  
   173  	return nil
   174  }