gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/firstboot.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package devicestate
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"sort"
    26  
    27  	"github.com/snapcore/snapd/asserts"
    28  	"github.com/snapcore/snapd/dirs"
    29  	"github.com/snapcore/snapd/i18n"
    30  	"github.com/snapcore/snapd/overlord/assertstate"
    31  	"github.com/snapcore/snapd/overlord/devicestate/internal"
    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/seed"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/timings"
    38  )
    39  
    40  var errNothingToDo = errors.New("nothing to do")
    41  
    42  func installSeedSnap(st *state.State, sn *seed.Snap, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
    43  	if sn.Required {
    44  		flags.Required = true
    45  	}
    46  	if sn.Classic {
    47  		flags.Classic = true
    48  	}
    49  	if sn.DevMode {
    50  		flags.DevMode = true
    51  	}
    52  
    53  	return snapstate.InstallPath(st, sn.SideInfo, sn.Path, "", sn.Channel, flags)
    54  }
    55  
    56  func criticalTaskEdges(ts *state.TaskSet) (beginEdge, beforeHooksEdge, hooksEdge *state.Task, err error) {
    57  	// we expect all three edges, or none (the latter is the case with config tasksets).
    58  	beginEdge, err = ts.Edge(snapstate.BeginEdge)
    59  	if err != nil {
    60  		return nil, nil, nil, nil
    61  	}
    62  	beforeHooksEdge, err = ts.Edge(snapstate.BeforeHooksEdge)
    63  	if err != nil {
    64  		return nil, nil, nil, err
    65  	}
    66  	hooksEdge, err = ts.Edge(snapstate.HooksEdge)
    67  	if err != nil {
    68  		return nil, nil, nil, err
    69  	}
    70  
    71  	return beginEdge, beforeHooksEdge, hooksEdge, nil
    72  }
    73  
    74  func markSeededTask(st *state.State) *state.Task {
    75  	return st.NewTask("mark-seeded", i18n.G("Mark system seeded"))
    76  }
    77  
    78  func trivialSeeding(st *state.State) []*state.TaskSet {
    79  	// give the internal core config a chance to run (even if core is
    80  	// not used at all we put system configuration there)
    81  	configTs := snapstate.ConfigureSnap(st, "core", 0)
    82  	markSeeded := markSeededTask(st)
    83  	markSeeded.WaitAll(configTs)
    84  	return []*state.TaskSet{configTs, state.NewTaskSet(markSeeded)}
    85  }
    86  
    87  type populateStateFromSeedOptions struct {
    88  	Label   string
    89  	Mode    string
    90  	Preseed bool
    91  }
    92  
    93  func populateStateFromSeedImpl(st *state.State, opts *populateStateFromSeedOptions, tm timings.Measurer) ([]*state.TaskSet, error) {
    94  	mode := "run"
    95  	sysLabel := ""
    96  	preseed := false
    97  	if opts != nil {
    98  		if opts.Mode != "" {
    99  			mode = opts.Mode
   100  		}
   101  		sysLabel = opts.Label
   102  		preseed = opts.Preseed
   103  	}
   104  
   105  	// check that the state is empty
   106  	var seeded bool
   107  	err := st.Get("seeded", &seeded)
   108  	if err != nil && err != state.ErrNoState {
   109  		return nil, err
   110  	}
   111  	if seeded {
   112  		return nil, fmt.Errorf("cannot populate state: already seeded")
   113  	}
   114  
   115  	var deviceSeed seed.Seed
   116  	// ack all initial assertions
   117  	timings.Run(tm, "import-assertions[finish]", "finish importing assertions from seed", func(nested timings.Measurer) {
   118  		deviceSeed, err = importAssertionsFromSeed(st, sysLabel)
   119  	})
   120  	if err != nil && err != errNothingToDo {
   121  		return nil, err
   122  	}
   123  
   124  	if err == errNothingToDo {
   125  		return trivialSeeding(st), nil
   126  	}
   127  
   128  	timings.Run(tm, "load-verified-snap-metadata", "load verified snap metadata from seed", func(nested timings.Measurer) {
   129  		err = deviceSeed.LoadMeta(nested)
   130  	})
   131  	if release.OnClassic && err == seed.ErrNoMeta {
   132  		if preseed {
   133  			return nil, fmt.Errorf("no snaps to preseed")
   134  		}
   135  		// on classic it is ok to not seed any snaps
   136  		return trivialSeeding(st), nil
   137  	}
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	model := deviceSeed.Model()
   143  
   144  	essentialSeedSnaps := deviceSeed.EssentialSnaps()
   145  	seedSnaps, err := deviceSeed.ModeSnaps(mode)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	// optimistically forget the deviceSeed here
   151  	unloadDeviceSeed(st)
   152  
   153  	tsAll := []*state.TaskSet{}
   154  	configTss := []*state.TaskSet{}
   155  
   156  	var lastBeforeHooksTask *state.Task
   157  	var chainTs func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet
   158  
   159  	var preseedDoneTask *state.Task
   160  	if preseed {
   161  		preseedDoneTask = st.NewTask("mark-preseeded", i18n.G("Mark system pre-seeded"))
   162  	}
   163  
   164  	chainTsPreseeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet {
   165  		// mark-preseeded task needs to be inserted between preliminary setup and hook tasks
   166  		beginTask, beforeHooksTask, hooksTask, err := criticalTaskEdges(ts)
   167  		if err != nil {
   168  			// XXX: internal error?
   169  			panic(err)
   170  		}
   171  		// we either have all edges or none
   172  		if beginTask != nil {
   173  			// hooks must wait for mark-preseeded
   174  			hooksTask.WaitFor(preseedDoneTask)
   175  			if n := len(all); n > 0 {
   176  				// the first hook of the snap waits for all tasks of previous snap
   177  				hooksTask.WaitAll(all[n-1])
   178  			}
   179  			if lastBeforeHooksTask != nil {
   180  				beginTask.WaitFor(lastBeforeHooksTask)
   181  			}
   182  			preseedDoneTask.WaitFor(beforeHooksTask)
   183  			lastBeforeHooksTask = beforeHooksTask
   184  		} else {
   185  			n := len(all)
   186  			// no edges: it is a configure snap taskset for core/gadget/kernel
   187  			if n != 0 {
   188  				ts.WaitAll(all[n-1])
   189  			}
   190  		}
   191  		return append(all, ts)
   192  	}
   193  
   194  	chainTsFullSeeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet {
   195  		n := len(all)
   196  		if n != 0 {
   197  			ts.WaitAll(all[n-1])
   198  		}
   199  		return append(all, ts)
   200  	}
   201  
   202  	if preseed {
   203  		chainTs = chainTsPreseeding
   204  	} else {
   205  		chainTs = chainTsFullSeeding
   206  	}
   207  
   208  	chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) {
   209  		sort.Stable(snap.ByType(infos))
   210  		for _, info := range infos {
   211  			ts := infoToTs[info]
   212  			tsAll = chainTs(tsAll, ts)
   213  		}
   214  	}
   215  
   216  	// collected snap infos
   217  	infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps))
   218  
   219  	infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps))
   220  
   221  	if len(essentialSeedSnaps) != 0 {
   222  		// we *always* configure "core" here even if bases are used
   223  		// for booting. "core" is where the system config lives.
   224  		configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults))
   225  	}
   226  
   227  	for _, seedSnap := range essentialSeedSnaps {
   228  		flags := snapstate.Flags{
   229  			SkipConfigure: true,
   230  			// The kernel is already there either from ubuntu-image or from "install"
   231  			// mode so skip extract.
   232  			SkipKernelExtraction: true,
   233  			// for dangerous models, allow all devmode snaps
   234  			// XXX: eventually we may need to allow specific snaps to be devmode for
   235  			// non-dangerous models, we can do that here since that information will
   236  			// probably be in the model assertion which we have here
   237  			ApplySnapDevMode: model.Grade() == asserts.ModelDangerous,
   238  		}
   239  
   240  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  		if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget {
   245  			configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults)
   246  			// wait for the previous configTss
   247  			configTss = chainTs(configTss, configTs)
   248  		}
   249  		infos = append(infos, info)
   250  		infoToTs[info] = ts
   251  	}
   252  	// now add/chain the tasksets in the right order based on essential
   253  	// snap types
   254  	chainSorted(infos, infoToTs)
   255  
   256  	// chain together configuring core, kernel, and gadget after
   257  	// installing them so that defaults are availabble from gadget
   258  	if len(configTss) > 0 {
   259  		if preseed {
   260  			configTss[0].WaitFor(preseedDoneTask)
   261  		}
   262  		configTss[0].WaitAll(tsAll[len(tsAll)-1])
   263  		tsAll = append(tsAll, configTss...)
   264  	}
   265  
   266  	// ensure we install in the right order
   267  	infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps))
   268  
   269  	for _, seedSnap := range seedSnaps {
   270  		flags := snapstate.Flags{
   271  			// for dangerous models, allow all devmode snaps
   272  			// XXX: eventually we may need to allow specific snaps to be devmode for
   273  			// non-dangerous models, we can do that here since that information will
   274  			// probably be in the model assertion which we have here
   275  			ApplySnapDevMode: model.Grade() == asserts.ModelDangerous,
   276  		}
   277  
   278  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   279  		if err != nil {
   280  			return nil, err
   281  		}
   282  		infos = append(infos, info)
   283  		infoToTs[info] = ts
   284  	}
   285  
   286  	// validate that all snaps have bases
   287  	errs := snap.ValidateBasesAndProviders(infos)
   288  	if errs != nil {
   289  		// only report the first error encountered
   290  		return nil, errs[0]
   291  	}
   292  
   293  	// now add/chain the tasksets in the right order, note that we
   294  	// only have tasksets that we did not already seeded
   295  	chainSorted(infos[len(essentialSeedSnaps):], infoToTs)
   296  
   297  	if len(tsAll) == 0 {
   298  		return nil, fmt.Errorf("cannot proceed, no snaps to seed")
   299  	}
   300  
   301  	// ts is the taskset of the last snap
   302  	ts := tsAll[len(tsAll)-1]
   303  	endTs := state.NewTaskSet()
   304  
   305  	markSeeded := markSeededTask(st)
   306  	if preseed {
   307  		endTs.AddTask(preseedDoneTask)
   308  		markSeeded.WaitFor(preseedDoneTask)
   309  	}
   310  	whatSeeds := &seededSystem{
   311  		System:    sysLabel,
   312  		Model:     model.Model(),
   313  		BrandID:   model.BrandID(),
   314  		Revision:  model.Revision(),
   315  		Timestamp: model.Timestamp(),
   316  	}
   317  	markSeeded.Set("seed-system", whatSeeds)
   318  
   319  	// mark-seeded waits for the taskset of last snap
   320  	markSeeded.WaitAll(ts)
   321  	endTs.AddTask(markSeeded)
   322  	tsAll = append(tsAll, endTs)
   323  
   324  	return tsAll, nil
   325  }
   326  
   327  func importAssertionsFromSeed(st *state.State, sysLabel string) (seed.Seed, error) {
   328  	// TODO: use some kind of context fo Device/SetDevice?
   329  	device, err := internal.Device(st)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	// collect and
   335  	// set device,model from the model assertion
   336  	deviceSeed, err := loadDeviceSeed(st, sysLabel)
   337  	if err == seed.ErrNoAssertions && release.OnClassic {
   338  		// on classic seeding is optional
   339  		// set the fallback model
   340  		err := setClassicFallbackModel(st, device)
   341  		if err != nil {
   342  			return nil, err
   343  		}
   344  		return nil, errNothingToDo
   345  	}
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  	modelAssertion := deviceSeed.Model()
   350  
   351  	classicModel := modelAssertion.Classic()
   352  	if release.OnClassic != classicModel {
   353  		var msg string
   354  		if classicModel {
   355  			msg = "cannot seed an all-snaps system with a classic model"
   356  		} else {
   357  			msg = "cannot seed a classic system with an all-snaps model"
   358  		}
   359  		return nil, fmt.Errorf(msg)
   360  	}
   361  
   362  	// set device,model from the model assertion
   363  	if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil {
   364  		return nil, err
   365  	}
   366  
   367  	return deviceSeed, nil
   368  }
   369  
   370  // loadDeviceSeed loads and caches the device seed based on sysLabel,
   371  // it is meant to be used before and during seeding.
   372  // It is an error to call it with different sysLabel values once one
   373  // seed has been loaded and cached.
   374  func loadDeviceSeed(st *state.State, sysLabel string) (deviceSeed seed.Seed, err error) {
   375  	cached := st.Cached(loadedDeviceSeedKey{})
   376  	if cached != nil {
   377  		loaded := cached.(*loadedDeviceSeed)
   378  		if loaded.sysLabel != sysLabel {
   379  			return nil, fmt.Errorf("internal error: requested inconsistent device seed: %s (was %s)", sysLabel, loaded.sysLabel)
   380  		}
   381  		return loaded.seed, loaded.err
   382  	}
   383  
   384  	// cache the outcome, both success and errors
   385  	defer func() {
   386  		st.Cache(loadedDeviceSeedKey{}, &loadedDeviceSeed{
   387  			sysLabel: sysLabel,
   388  			seed:     deviceSeed,
   389  			err:      err,
   390  		})
   391  	}()
   392  
   393  	deviceSeed, err = seed.Open(dirs.SnapSeedDir, sysLabel)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  
   398  	// collect and
   399  	// set device,model from the model assertion
   400  	commitTo := func(batch *asserts.Batch) error {
   401  		return assertstate.AddBatch(st, batch, nil)
   402  	}
   403  
   404  	if err := deviceSeed.LoadAssertions(assertstate.DB(st), commitTo); err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	return deviceSeed, nil
   409  }
   410  
   411  // unloadDeviceSeed forgets the cached outcomes of loadDeviceSeed.
   412  // Its main reason is to avoid using memory past the point where the deviceSeed
   413  // isn't needed anymore.
   414  func unloadDeviceSeed(st *state.State) {
   415  	st.Cache(loadedDeviceSeedKey{}, nil)
   416  }
   417  
   418  type loadedDeviceSeedKey struct{}
   419  type loadedDeviceSeed struct {
   420  	sysLabel string
   421  	seed     seed.Seed
   422  	err      error
   423  }