github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 lastBeforeHooksTask != nil {
   174  				beginTask.WaitFor(lastBeforeHooksTask)
   175  			}
   176  			preseedDoneTask.WaitFor(beforeHooksTask)
   177  			lastBeforeHooksTask = beforeHooksTask
   178  		} else {
   179  			n := len(all)
   180  			// no edges: it is a configure snap taskset for core/gadget/kernel
   181  			if n != 0 {
   182  				ts.WaitAll(all[n-1])
   183  			}
   184  		}
   185  		return append(all, ts)
   186  	}
   187  
   188  	chainTsFullSeeding := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet {
   189  		n := len(all)
   190  		if n != 0 {
   191  			ts.WaitAll(all[n-1])
   192  		}
   193  		return append(all, ts)
   194  	}
   195  
   196  	if preseed {
   197  		chainTs = chainTsPreseeding
   198  	} else {
   199  		chainTs = chainTsFullSeeding
   200  	}
   201  
   202  	chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) {
   203  		sort.Stable(snap.ByType(infos))
   204  		for _, info := range infos {
   205  			ts := infoToTs[info]
   206  			tsAll = chainTs(tsAll, ts)
   207  		}
   208  	}
   209  
   210  	// collected snap infos
   211  	infos := make([]*snap.Info, 0, len(essentialSeedSnaps)+len(seedSnaps))
   212  
   213  	infoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps))
   214  
   215  	if len(essentialSeedSnaps) != 0 {
   216  		// we *always* configure "core" here even if bases are used
   217  		// for booting. "core" is where the system config lives.
   218  		configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults))
   219  	}
   220  
   221  	for _, seedSnap := range essentialSeedSnaps {
   222  		ts, info, err := installSeedSnap(st, seedSnap, snapstate.Flags{SkipConfigure: true})
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  		if info.Type() == snap.TypeKernel || info.Type() == snap.TypeGadget {
   227  			configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults)
   228  			// wait for the previous configTss
   229  			configTss = chainTs(configTss, configTs)
   230  		}
   231  		infos = append(infos, info)
   232  		infoToTs[info] = ts
   233  	}
   234  	// now add/chain the tasksets in the right order based on essential
   235  	// snap types
   236  	chainSorted(infos, infoToTs)
   237  
   238  	// chain together configuring core, kernel, and gadget after
   239  	// installing them so that defaults are availabble from gadget
   240  	if len(configTss) > 0 {
   241  		if preseed {
   242  			configTss[0].WaitFor(preseedDoneTask)
   243  		}
   244  		configTss[0].WaitAll(tsAll[len(tsAll)-1])
   245  		tsAll = append(tsAll, configTss...)
   246  	}
   247  
   248  	// ensure we install in the right order
   249  	infoToTs = make(map[*snap.Info]*state.TaskSet, len(seedSnaps))
   250  
   251  	for _, seedSnap := range seedSnaps {
   252  		var flags snapstate.Flags
   253  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   254  		if err != nil {
   255  			return nil, err
   256  		}
   257  		infos = append(infos, info)
   258  		infoToTs[info] = ts
   259  	}
   260  
   261  	// validate that all snaps have bases
   262  	errs := snap.ValidateBasesAndProviders(infos)
   263  	if errs != nil {
   264  		// only report the first error encountered
   265  		return nil, errs[0]
   266  	}
   267  
   268  	// now add/chain the tasksets in the right order, note that we
   269  	// only have tasksets that we did not already seeded
   270  	chainSorted(infos[len(essentialSeedSnaps):], infoToTs)
   271  
   272  	if len(tsAll) == 0 {
   273  		return nil, fmt.Errorf("cannot proceed, no snaps to seed")
   274  	}
   275  
   276  	ts := tsAll[len(tsAll)-1]
   277  	endTs := state.NewTaskSet()
   278  
   279  	markSeeded := markSeededTask(st)
   280  	if preseed {
   281  		endTs.AddTask(preseedDoneTask)
   282  		markSeeded.WaitFor(preseedDoneTask)
   283  	}
   284  	whatSeeds := &seededSystem{
   285  		System:    sysLabel,
   286  		Model:     model.Model(),
   287  		BrandID:   model.BrandID(),
   288  		Revision:  model.Revision(),
   289  		Timestamp: model.Timestamp(),
   290  	}
   291  	markSeeded.Set("seed-system", whatSeeds)
   292  
   293  	markSeeded.WaitAll(ts)
   294  	endTs.AddTask(markSeeded)
   295  	tsAll = append(tsAll, endTs)
   296  
   297  	return tsAll, nil
   298  }
   299  
   300  func importAssertionsFromSeed(st *state.State, deviceSeed seed.Seed) (*asserts.Model, error) {
   301  	// TODO: use some kind of context fo Device/SetDevice?
   302  	device, err := internal.Device(st)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	// collect and
   308  	// set device,model from the model assertion
   309  	commitTo := func(batch *asserts.Batch) error {
   310  		return assertstate.AddBatch(st, batch, nil)
   311  	}
   312  
   313  	err = deviceSeed.LoadAssertions(assertstate.DB(st), commitTo)
   314  	if err == seed.ErrNoAssertions && release.OnClassic {
   315  		// on classic seeding is optional
   316  		// set the fallback model
   317  		err := setClassicFallbackModel(st, device)
   318  		if err != nil {
   319  			return nil, err
   320  		}
   321  		return nil, errNothingToDo
   322  	}
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  	modelAssertion := deviceSeed.Model()
   327  
   328  	classicModel := modelAssertion.Classic()
   329  	if release.OnClassic != classicModel {
   330  		var msg string
   331  		if classicModel {
   332  			msg = "cannot seed an all-snaps system with a classic model"
   333  		} else {
   334  			msg = "cannot seed a classic system with an all-snaps model"
   335  		}
   336  		return nil, fmt.Errorf(msg)
   337  	}
   338  
   339  	// set device,model from the model assertion
   340  	if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	return modelAssertion, nil
   345  }