github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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  	deviceSeed, err := seed.Open(dirs.SnapSeedDir, sysLabel)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var model *asserts.Model
   121  	// ack all initial assertions
   122  	timings.Run(tm, "import-assertions", "import assertions from seed", func(nested timings.Measurer) {
   123  		model, err = importAssertionsFromSeed(st, deviceSeed)
   124  	})
   125  	if err != nil && err != errNothingToDo {
   126  		return nil, err
   127  	}
   128  
   129  	if err == errNothingToDo {
   130  		return trivialSeeding(st), nil
   131  	}
   132  
   133  	err = deviceSeed.LoadMeta(tm)
   134  	if release.OnClassic && err == seed.ErrNoMeta {
   135  		if preseed {
   136  			return nil, fmt.Errorf("no snaps to preseed")
   137  		}
   138  		// on classic it is ok to not seed any snaps
   139  		return trivialSeeding(st), nil
   140  	}
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	essentialSeedSnaps := deviceSeed.EssentialSnaps()
   146  	seedSnaps, err := deviceSeed.ModeSnaps(mode)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	tsAll := []*state.TaskSet{}
   152  	configTss := []*state.TaskSet{}
   153  
   154  	var lastBeforeHooksTask *state.Task
   155  	var chainTs func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet
   156  
   157  	var preseedDoneTask *state.Task
   158  	if preseed {
   159  		preseedDoneTask = st.NewTask("mark-preseeded", i18n.G("Mark system pre-seeded"))
   160  	}
   161  
   162  	chainTsPreseeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet {
   163  		// mark-preseeded task needs to be inserted between preliminary setup and hook tasks
   164  		beginTask, beforeHooksTask, hooksTask, err := criticalTaskEdges(ts)
   165  		if err != nil {
   166  			// XXX: internal error?
   167  			panic(err)
   168  		}
   169  		// we either have all edges or none
   170  		if beginTask != nil {
   171  			// hooks must wait for mark-preseeded
   172  			hooksTask.WaitFor(preseedDoneTask)
   173  			if n := len(all); n > 0 {
   174  				// the first hook of the snap waits for all tasks of previous snap
   175  				hooksTask.WaitAll(all[n-1])
   176  			}
   177  			if lastBeforeHooksTask != nil {
   178  				beginTask.WaitFor(lastBeforeHooksTask)
   179  			}
   180  			preseedDoneTask.WaitFor(beforeHooksTask)
   181  			lastBeforeHooksTask = beforeHooksTask
   182  		} else {
   183  			n := len(all)
   184  			// no edges: it is a configure snap taskset for core/gadget/kernel
   185  			if n != 0 {
   186  				ts.WaitAll(all[n-1])
   187  			}
   188  		}
   189  		return append(all, ts)
   190  	}
   191  
   192  	chainTsFullSeeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet {
   193  		n := len(all)
   194  		if n != 0 {
   195  			ts.WaitAll(all[n-1])
   196  		}
   197  		return append(all, ts)
   198  	}
   199  
   200  	if preseed {
   201  		chainTs = chainTsPreseeding
   202  	} else {
   203  		chainTs = chainTsFullSeeding
   204  	}
   205  
   206  	chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) {
   207  		sort.Stable(snap.ByType(infos))
   208  		for _, info := range infos {
   209  			ts := infoToTs[info]
   210  			tsAll = chainTs(tsAll, ts)
   211  		}
   212  	}
   213  
   214  	// collected snap infos
   215  	infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps))
   216  
   217  	infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps))
   218  
   219  	if len(essentialSeedSnaps) != 0 {
   220  		// we *always* configure "core" here even if bases are used
   221  		// for booting. "core" is where the system config lives.
   222  		configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults))
   223  	}
   224  
   225  	for _, seedSnap := range essentialSeedSnaps {
   226  		flags := snapstate.Flags{
   227  			SkipConfigure: true,
   228  			// for dangerous models, allow all devmode snaps
   229  			// XXX: eventually we may need to allow specific snaps to be devmode for
   230  			// non-dangerous models, we can do that here since that information will
   231  			// probably be in the model assertion which we have here
   232  			ApplySnapDevMode: model.Grade() == asserts.ModelDangerous,
   233  		}
   234  
   235  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   236  		if err != nil {
   237  			return nil, err
   238  		}
   239  		if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget {
   240  			configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults)
   241  			// wait for the previous configTss
   242  			configTss = chainTs(configTss, configTs)
   243  		}
   244  		infos = append(infos, info)
   245  		infoToTs[info] = ts
   246  	}
   247  	// now add/chain the tasksets in the right order based on essential
   248  	// snap types
   249  	chainSorted(infos, infoToTs)
   250  
   251  	// chain together configuring core, kernel, and gadget after
   252  	// installing them so that defaults are availabble from gadget
   253  	if len(configTss) > 0 {
   254  		if preseed {
   255  			configTss[0].WaitFor(preseedDoneTask)
   256  		}
   257  		configTss[0].WaitAll(tsAll[len(tsAll)-1])
   258  		tsAll = append(tsAll, configTss...)
   259  	}
   260  
   261  	// ensure we install in the right order
   262  	infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps))
   263  
   264  	for _, seedSnap := range seedSnaps {
   265  		flags := snapstate.Flags{
   266  			// for dangerous models, allow all devmode snaps
   267  			// XXX: eventually we may need to allow specific snaps to be devmode for
   268  			// non-dangerous models, we can do that here since that information will
   269  			// probably be in the model assertion which we have here
   270  			ApplySnapDevMode: model.Grade() == asserts.ModelDangerous,
   271  		}
   272  
   273  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  		infos = append(infos, info)
   278  		infoToTs[info] = ts
   279  	}
   280  
   281  	// validate that all snaps have bases
   282  	errs := snap.ValidateBasesAndProviders(infos)
   283  	if errs != nil {
   284  		// only report the first error encountered
   285  		return nil, errs[0]
   286  	}
   287  
   288  	// now add/chain the tasksets in the right order, note that we
   289  	// only have tasksets that we did not already seeded
   290  	chainSorted(infos[len(essentialSeedSnaps):], infoToTs)
   291  
   292  	if len(tsAll) == 0 {
   293  		return nil, fmt.Errorf("cannot proceed, no snaps to seed")
   294  	}
   295  
   296  	// ts is the taskset of the last snap
   297  	ts := tsAll[len(tsAll)-1]
   298  	endTs := state.NewTaskSet()
   299  
   300  	markSeeded := markSeededTask(st)
   301  	if preseed {
   302  		endTs.AddTask(preseedDoneTask)
   303  		markSeeded.WaitFor(preseedDoneTask)
   304  	}
   305  	whatSeeds := &seededSystem{
   306  		System:    sysLabel,
   307  		Model:     model.Model(),
   308  		BrandID:   model.BrandID(),
   309  		Revision:  model.Revision(),
   310  		Timestamp: model.Timestamp(),
   311  	}
   312  	markSeeded.Set("seed-system", whatSeeds)
   313  
   314  	// mark-seeded waits for the taskset of last snap
   315  	markSeeded.WaitAll(ts)
   316  	endTs.AddTask(markSeeded)
   317  	tsAll = append(tsAll, endTs)
   318  
   319  	return tsAll, nil
   320  }
   321  
   322  func importAssertionsFromSeed(st *state.State, deviceSeed seed.Seed) (*asserts.Model, error) {
   323  	// TODO: use some kind of context fo Device/SetDevice?
   324  	device, err := internal.Device(st)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	// collect and
   330  	// set device,model from the model assertion
   331  	commitTo := func(batch *asserts.Batch) error {
   332  		return assertstate.AddBatch(st, batch, nil)
   333  	}
   334  
   335  	err = deviceSeed.LoadAssertions(assertstate.DB(st), commitTo)
   336  	if err == seed.ErrNoAssertions && release.OnClassic {
   337  		// on classic seeding is optional
   338  		// set the fallback model
   339  		err := setClassicFallbackModel(st, device)
   340  		if err != nil {
   341  			return nil, err
   342  		}
   343  		return nil, errNothingToDo
   344  	}
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	modelAssertion := deviceSeed.Model()
   349  
   350  	classicModel := modelAssertion.Classic()
   351  	if release.OnClassic != classicModel {
   352  		var msg string
   353  		if classicModel {
   354  			msg = "cannot seed an all-snaps system with a classic model"
   355  		} else {
   356  			msg = "cannot seed a classic system with an all-snaps model"
   357  		}
   358  		return nil, fmt.Errorf(msg)
   359  	}
   360  
   361  	// set device,model from the model assertion
   362  	if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	return modelAssertion, nil
   367  }