github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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  			// for dangerous models, allow all devmode snaps
   231  			// XXX: eventually we may need to allow specific snaps to be devmode for
   232  			// non-dangerous models, we can do that here since that information will
   233  			// probably be in the model assertion which we have here
   234  			ApplySnapDevMode: model.Grade() == asserts.ModelDangerous,
   235  		}
   236  
   237  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  		if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget {
   242  			configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults)
   243  			// wait for the previous configTss
   244  			configTss = chainTs(configTss, configTs)
   245  		}
   246  		infos = append(infos, info)
   247  		infoToTs[info] = ts
   248  	}
   249  	// now add/chain the tasksets in the right order based on essential
   250  	// snap types
   251  	chainSorted(infos, infoToTs)
   252  
   253  	// chain together configuring core, kernel, and gadget after
   254  	// installing them so that defaults are availabble from gadget
   255  	if len(configTss) > 0 {
   256  		if preseed {
   257  			configTss[0].WaitFor(preseedDoneTask)
   258  		}
   259  		configTss[0].WaitAll(tsAll[len(tsAll)-1])
   260  		tsAll = append(tsAll, configTss...)
   261  	}
   262  
   263  	// ensure we install in the right order
   264  	infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps))
   265  
   266  	for _, seedSnap := range seedSnaps {
   267  		flags := snapstate.Flags{
   268  			// for dangerous models, allow all devmode snaps
   269  			// XXX: eventually we may need to allow specific snaps to be devmode for
   270  			// non-dangerous models, we can do that here since that information will
   271  			// probably be in the model assertion which we have here
   272  			ApplySnapDevMode: model.Grade() == asserts.ModelDangerous,
   273  		}
   274  
   275  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   276  		if err != nil {
   277  			return nil, err
   278  		}
   279  		infos = append(infos, info)
   280  		infoToTs[info] = ts
   281  	}
   282  
   283  	// validate that all snaps have bases
   284  	errs := snap.ValidateBasesAndProviders(infos)
   285  	if errs != nil {
   286  		// only report the first error encountered
   287  		return nil, errs[0]
   288  	}
   289  
   290  	// now add/chain the tasksets in the right order, note that we
   291  	// only have tasksets that we did not already seeded
   292  	chainSorted(infos[len(essentialSeedSnaps):], infoToTs)
   293  
   294  	if len(tsAll) == 0 {
   295  		return nil, fmt.Errorf("cannot proceed, no snaps to seed")
   296  	}
   297  
   298  	// ts is the taskset of the last snap
   299  	ts := tsAll[len(tsAll)-1]
   300  	endTs := state.NewTaskSet()
   301  
   302  	markSeeded := markSeededTask(st)
   303  	if preseed {
   304  		endTs.AddTask(preseedDoneTask)
   305  		markSeeded.WaitFor(preseedDoneTask)
   306  	}
   307  	whatSeeds := &seededSystem{
   308  		System:    sysLabel,
   309  		Model:     model.Model(),
   310  		BrandID:   model.BrandID(),
   311  		Revision:  model.Revision(),
   312  		Timestamp: model.Timestamp(),
   313  	}
   314  	markSeeded.Set("seed-system", whatSeeds)
   315  
   316  	// mark-seeded waits for the taskset of last snap
   317  	markSeeded.WaitAll(ts)
   318  	endTs.AddTask(markSeeded)
   319  	tsAll = append(tsAll, endTs)
   320  
   321  	return tsAll, nil
   322  }
   323  
   324  func importAssertionsFromSeed(st *state.State, sysLabel string) (seed.Seed, error) {
   325  	// TODO: use some kind of context fo Device/SetDevice?
   326  	device, err := internal.Device(st)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	// collect and
   332  	// set device,model from the model assertion
   333  	deviceSeed, err := loadDeviceSeed(st, sysLabel)
   334  	if err == seed.ErrNoAssertions && release.OnClassic {
   335  		// on classic seeding is optional
   336  		// set the fallback model
   337  		err := setClassicFallbackModel(st, device)
   338  		if err != nil {
   339  			return nil, err
   340  		}
   341  		return nil, errNothingToDo
   342  	}
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  	modelAssertion := deviceSeed.Model()
   347  
   348  	classicModel := modelAssertion.Classic()
   349  	if release.OnClassic != classicModel {
   350  		var msg string
   351  		if classicModel {
   352  			msg = "cannot seed an all-snaps system with a classic model"
   353  		} else {
   354  			msg = "cannot seed a classic system with an all-snaps model"
   355  		}
   356  		return nil, fmt.Errorf(msg)
   357  	}
   358  
   359  	// set device,model from the model assertion
   360  	if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil {
   361  		return nil, err
   362  	}
   363  
   364  	return deviceSeed, nil
   365  }
   366  
   367  // loadDeviceSeed loads and caches the device seed based on sysLabel,
   368  // it is meant to be used before and during seeding.
   369  // It is an error to call it with different sysLabel values once one
   370  // seed has been loaded and cached.
   371  func loadDeviceSeed(st *state.State, sysLabel string) (deviceSeed seed.Seed, err error) {
   372  	cached := st.Cached(loadedDeviceSeedKey{})
   373  	if cached != nil {
   374  		loaded := cached.(*loadedDeviceSeed)
   375  		if loaded.sysLabel != sysLabel {
   376  			return nil, fmt.Errorf("internal error: requested inconsistent device seed: %s (was %s)", sysLabel, loaded.sysLabel)
   377  		}
   378  		return loaded.seed, loaded.err
   379  	}
   380  
   381  	// cache the outcome, both success and errors
   382  	defer func() {
   383  		st.Cache(loadedDeviceSeedKey{}, &loadedDeviceSeed{
   384  			sysLabel: sysLabel,
   385  			seed:     deviceSeed,
   386  			err:      err,
   387  		})
   388  	}()
   389  
   390  	deviceSeed, err = seed.Open(dirs.SnapSeedDir, sysLabel)
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  
   395  	// collect and
   396  	// set device,model from the model assertion
   397  	commitTo := func(batch *asserts.Batch) error {
   398  		return assertstate.AddBatch(st, batch, nil)
   399  	}
   400  
   401  	if err := deviceSeed.LoadAssertions(assertstate.DB(st), commitTo); err != nil {
   402  		return nil, err
   403  	}
   404  
   405  	return deviceSeed, nil
   406  }
   407  
   408  // unloadDeviceSeed forgets the cached outcomes of loadDeviceSeed.
   409  // Its main reason is to avoid using memory past the point where the deviceSeed
   410  // isn't needed anymore.
   411  func unloadDeviceSeed(st *state.State) {
   412  	st.Cache(loadedDeviceSeedKey{}, nil)
   413  }
   414  
   415  type loadedDeviceSeedKey struct{}
   416  type loadedDeviceSeed struct {
   417  	sysLabel string
   418  	seed     seed.Seed
   419  	err      error
   420  }