github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/devicestate/firstboot.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2019 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 trivialSeeding(st *state.State, markSeeded *state.Task) []*state.TaskSet {
    57  	// give the internal core config a chance to run (even if core is
    58  	// not used at all we put system configuration there)
    59  	configTs := snapstate.ConfigureSnap(st, "core", 0)
    60  	markSeeded.WaitAll(configTs)
    61  	return []*state.TaskSet{configTs, state.NewTaskSet(markSeeded)}
    62  }
    63  
    64  func populateStateFromSeedImpl(st *state.State, tm timings.Measurer) ([]*state.TaskSet, error) {
    65  	// check that the state is empty
    66  	var seeded bool
    67  	err := st.Get("seeded", &seeded)
    68  	if err != nil && err != state.ErrNoState {
    69  		return nil, err
    70  	}
    71  	if seeded {
    72  		return nil, fmt.Errorf("cannot populate state: already seeded")
    73  	}
    74  
    75  	markSeeded := st.NewTask("mark-seeded", i18n.G("Mark system seeded"))
    76  
    77  	deviceSeed, err := seed.Open(dirs.SnapSeedDir)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	// ack all initial assertions
    83  	var model *asserts.Model
    84  	timings.Run(tm, "import-assertions", "import assertions from seed", func(nested timings.Measurer) {
    85  		model, err = importAssertionsFromSeed(st, deviceSeed)
    86  	})
    87  	if err == errNothingToDo {
    88  		return trivialSeeding(st, markSeeded), nil
    89  	}
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	err = deviceSeed.LoadMeta(tm)
    95  	if release.OnClassic && err == seed.ErrNoMeta {
    96  		// on classic it is ok to not seed any snaps
    97  		return trivialSeeding(st, markSeeded), nil
    98  	}
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	essentialSeedSnaps := deviceSeed.EssentialSnaps()
   104  	seedSnaps, err := deviceSeed.ModeSnaps("run") // XXX mode should be passed in
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	// allSnapInfos are collected for cross-check validation of bases
   110  	allSnapInfos := make(map[string]*snap.Info, len(essentialSeedSnaps)+len(seedSnaps))
   111  
   112  	tsAll := []*state.TaskSet{}
   113  	configTss := []*state.TaskSet{}
   114  	chainTs := func(all []*state.TaskSet, ts *state.TaskSet) []*state.TaskSet {
   115  		n := len(all)
   116  		if n != 0 {
   117  			ts.WaitAll(all[n-1])
   118  		}
   119  		return append(all, ts)
   120  	}
   121  	chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) {
   122  		sort.Stable(snap.ByType(infos))
   123  		for _, info := range infos {
   124  			ts := infoToTs[info]
   125  			tsAll = chainTs(tsAll, ts)
   126  		}
   127  	}
   128  
   129  	essInfoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps))
   130  	essInfos := make([]*snap.Info, 0, len(essentialSeedSnaps))
   131  
   132  	if len(essentialSeedSnaps) != 0 {
   133  		// we *always* configure "core" here even if bases are used
   134  		// for booting. "core" if where the system config lives.
   135  		configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults))
   136  	}
   137  
   138  	for _, seedSnap := range essentialSeedSnaps {
   139  		ts, info, err := installSeedSnap(st, seedSnap, snapstate.Flags{SkipConfigure: true})
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  		if info.GetType() == snap.TypeKernel || info.GetType() == snap.TypeGadget {
   144  			configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults)
   145  			// wait for the previous configTss
   146  			configTss = chainTs(configTss, configTs)
   147  		}
   148  		essInfos = append(essInfos, info)
   149  		essInfoToTs[info] = ts
   150  		allSnapInfos[info.SnapName()] = info
   151  	}
   152  	// now add/chain the tasksets in the right order based on essential
   153  	// snap types
   154  	chainSorted(essInfos, essInfoToTs)
   155  
   156  	// chain together configuring core, kernel, and gadget after
   157  	// installing them so that defaults are availabble from gadget
   158  	if len(configTss) > 0 {
   159  		configTss[0].WaitAll(tsAll[len(tsAll)-1])
   160  		tsAll = append(tsAll, configTss...)
   161  	}
   162  
   163  	// ensure we install in the right order
   164  	infoToTs := make(map[*snap.Info]*state.TaskSet, len(seedSnaps))
   165  	infos := make([]*snap.Info, 0, len(seedSnaps))
   166  
   167  	for _, seedSnap := range seedSnaps {
   168  		var flags snapstate.Flags
   169  		ts, info, err := installSeedSnap(st, seedSnap, flags)
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  		infos = append(infos, info)
   174  		infoToTs[info] = ts
   175  		allSnapInfos[info.SnapName()] = info
   176  	}
   177  
   178  	// validate that all snaps have bases
   179  	errs := snap.ValidateBasesAndProviders(allSnapInfos)
   180  	if errs != nil {
   181  		// only report the first error encountered
   182  		return nil, errs[0]
   183  	}
   184  
   185  	// now add/chain the tasksets in the right order, note that we
   186  	// only have tasksets that we did not already seeded
   187  	chainSorted(infos, infoToTs)
   188  
   189  	if len(tsAll) == 0 {
   190  		return nil, fmt.Errorf("cannot proceed, no snaps to seed")
   191  	}
   192  
   193  	ts := tsAll[len(tsAll)-1]
   194  	endTs := state.NewTaskSet()
   195  	if model.Gadget() != "" {
   196  		// we have a gadget that could have interface
   197  		// connection instructions
   198  		gadgetConnect := st.NewTask("gadget-connect", "Connect plugs and slots as instructed by the gadget")
   199  		gadgetConnect.WaitAll(ts)
   200  		endTs.AddTask(gadgetConnect)
   201  		ts = endTs
   202  	}
   203  	markSeeded.WaitAll(ts)
   204  	endTs.AddTask(markSeeded)
   205  	tsAll = append(tsAll, endTs)
   206  
   207  	return tsAll, nil
   208  }
   209  
   210  func importAssertionsFromSeed(st *state.State, deviceSeed seed.Seed) (*asserts.Model, error) {
   211  	// TODO: use some kind of context fo Device/SetDevice?
   212  	device, err := internal.Device(st)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	// collect and
   218  	// set device,model from the model assertion
   219  	commitTo := func(batch *asserts.Batch) error {
   220  		return assertstate.AddBatch(st, batch, nil)
   221  	}
   222  
   223  	err = deviceSeed.LoadAssertions(assertstate.DB(st), commitTo)
   224  	if err == seed.ErrNoAssertions && release.OnClassic {
   225  		// on classic seeding is optional
   226  		// set the fallback model
   227  		err := setClassicFallbackModel(st, device)
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  		return nil, errNothingToDo
   232  	}
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	modelAssertion, err := deviceSeed.Model()
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	classicModel := modelAssertion.Classic()
   243  	if release.OnClassic != classicModel {
   244  		var msg string
   245  		if classicModel {
   246  			msg = "cannot seed an all-snaps system with a classic model"
   247  		} else {
   248  			msg = "cannot seed a classic system with an all-snaps model"
   249  		}
   250  		return nil, fmt.Errorf(msg)
   251  	}
   252  
   253  	// set device,model from the model assertion
   254  	if err := setDeviceFromModelAssertion(st, device, modelAssertion); err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	return modelAssertion, nil
   259  }