github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/seed/seedwriter/writer.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-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 seedwrite implements writing image seeds.
    21  package seedwriter
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"strings"
    27  
    28  	"github.com/snapcore/snapd/asserts"
    29  	"github.com/snapcore/snapd/asserts/snapasserts"
    30  	"github.com/snapcore/snapd/osutil"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/snap/channel"
    33  	"github.com/snapcore/snapd/snap/naming"
    34  	"github.com/snapcore/snapd/strutil"
    35  )
    36  
    37  // Options holds the options for a Writer.
    38  type Options struct {
    39  	SeedDir string
    40  
    41  	DefaultChannel string
    42  
    43  	// The label for the recovery system for Core20 models
    44  	Label string
    45  
    46  	// TestSkipCopyUnverifiedModel is set to support naive tests
    47  	// using an unverified model, the resulting image is broken
    48  	TestSkipCopyUnverifiedModel bool
    49  }
    50  
    51  // OptionsSnap represents an options-referred snap with its option values.
    52  // E.g. a snap passed to ubuntu-image via --snap.
    53  // If Name is set the snap is from the store. If Path is set the snap
    54  // is local at Path location.
    55  type OptionsSnap struct {
    56  	Name    string
    57  	SnapID  string
    58  	Path    string
    59  	Channel string
    60  }
    61  
    62  func (s *OptionsSnap) SnapName() string {
    63  	return s.Name
    64  }
    65  
    66  func (s *OptionsSnap) ID() string {
    67  	return s.SnapID
    68  }
    69  
    70  var _ naming.SnapRef = (*OptionsSnap)(nil)
    71  
    72  // SeedSnap holds details of a snap being added to a seed.
    73  type SeedSnap struct {
    74  	naming.SnapRef
    75  	Channel string
    76  	Path    string
    77  
    78  	// Info is the *snap.Info for the seed snap, filling this is
    79  	// delegated to the Writer using code, via Writer.SetInfo.
    80  	Info *snap.Info
    81  	// ARefs are references to the snap assertions if applicable,
    82  	// filling these is delegated to the Writer using code, the
    83  	// assumption is that the corresponding assertions can be
    84  	// found in the database passed to Writer.Start.
    85  	ARefs []*asserts.Ref
    86  
    87  	local      bool
    88  	modelSnap  *asserts.ModelSnap
    89  	optionSnap *OptionsSnap
    90  }
    91  
    92  var _ naming.SnapRef = (*SeedSnap)(nil)
    93  
    94  /* Writer writes Core 16/18 and Core 20 seeds.
    95  
    96  Its methods need to be called in sequences that match prescribed
    97  flows.
    98  
    99  Some methods can be skipped given some conditions.
   100  
   101  SnapsToDownload and Downloaded needs to be called in a loop where the
   102  SeedSnaps returned by SnapsToDownload get SetInfo called with
   103  *snap.Info retrieved from the store and then the snaps can be
   104  downloaded at SeedSnap.Path, after which Downloaded must be invoked
   105  and the flow breaks out of the loop only when it returns complete =
   106  true. In the loop as well assertions for the snaps can be fetched and
   107  SeedSnap.ARefs set.
   108  
   109  Optionally a similar but simpler mechanism covers local snaps, where
   110  LocalSnaps returns SeedSnaps that can be filled with information
   111  derived from the snap at SeedSnap.Path, then InfoDerived is called.
   112  
   113                        V-------->\
   114                        |         |
   115                 SetOptionsSnaps  |
   116                        |         v
   117                        | ________/
   118                        v
   119           /          Start       \
   120           |            |         |
   121           |            v         |
   122     no    |   /    LocalSnaps    | no option
   123     local |   |        |         | snaps
   124     snaps |   |        v         |
   125           |   |    InfoDerived   |
   126           |   |        |         |
   127           |   \        |         /
   128            >   > SnapsToDownload<
   129                        |     ^
   130                        |     |
   131                        |     | complete = false
   132                        v     /
   133                    Downloaded
   134                        |
   135                        | complete = true
   136                        |
   137                        v
   138                    SeedSnaps (copy files)
   139                        |
   140                        v
   141                    WriteMeta
   142  
   143  */
   144  type Writer struct {
   145  	model  *asserts.Model
   146  	opts   *Options
   147  	policy policy
   148  	tree   tree
   149  
   150  	// warnings keep a list of warnings produced during the
   151  	// process, no more warnings should be produced after
   152  	// Downloaded signaled complete
   153  	warnings []string
   154  
   155  	db asserts.RODatabase
   156  
   157  	expectedStep writerStep
   158  
   159  	modelRefs []*asserts.Ref
   160  
   161  	optionsSnaps []*OptionsSnap
   162  	// consumedOptSnapNum counts which options snaps have been consumed
   163  	// by either cross matching or matching with a model snap
   164  	consumedOptSnapNum int
   165  	// extraSnapsGuessNum is essentially #(optionsSnaps) -
   166  	// consumedOptSnapNum
   167  	extraSnapsGuessNum int
   168  
   169  	byNameOptSnaps *naming.SnapSet
   170  
   171  	localSnaps      map[*OptionsSnap]*SeedSnap
   172  	byRefLocalSnaps *naming.SnapSet
   173  
   174  	availableSnaps *naming.SnapSet
   175  
   176  	// toDownload tracks which set of snaps SnapsToDownload should compute
   177  	// next
   178  	toDownload              snapsToDownloadSet
   179  	toDownloadConsideredNum int
   180  
   181  	snapsFromModel []*SeedSnap
   182  	extraSnaps     []*SeedSnap
   183  }
   184  
   185  type policy interface {
   186  	allowsDangerousFeatures() error
   187  
   188  	checkDefaultChannel(channel.Channel) error
   189  	checkSnapChannel(ch channel.Channel, whichSnap string) error
   190  
   191  	systemSnap() *asserts.ModelSnap
   192  
   193  	modelSnapDefaultChannel() string
   194  	extraSnapDefaultChannel() string
   195  
   196  	checkBase(*snap.Info, *naming.SnapSet) error
   197  
   198  	needsImplicitSnaps(*naming.SnapSet) (bool, error)
   199  	implicitSnaps(*naming.SnapSet) []*asserts.ModelSnap
   200  	implicitExtraSnaps(*naming.SnapSet) []*OptionsSnap
   201  }
   202  
   203  type tree interface {
   204  	mkFixedDirs() error
   205  
   206  	snapPath(*SeedSnap) (string, error)
   207  
   208  	localSnapPath(*SeedSnap) (string, error)
   209  
   210  	writeAssertions(db asserts.RODatabase, modelRefs []*asserts.Ref, snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error
   211  
   212  	writeMeta(snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error
   213  }
   214  
   215  // New returns a Writer to write a seed for the given model and using
   216  // the given Options.
   217  func New(model *asserts.Model, opts *Options) (*Writer, error) {
   218  	if opts == nil {
   219  		return nil, fmt.Errorf("internal error: Writer *Options is nil")
   220  	}
   221  	w := &Writer{
   222  		model: model,
   223  		opts:  opts,
   224  
   225  		expectedStep: setOptionsSnapsStep,
   226  
   227  		byNameOptSnaps:  naming.NewSnapSet(nil),
   228  		byRefLocalSnaps: naming.NewSnapSet(nil),
   229  	}
   230  
   231  	var treeImpl tree
   232  	var pol policy
   233  	if model.Grade() != asserts.ModelGradeUnset {
   234  		// Core 20
   235  		if opts.Label == "" {
   236  			return nil, fmt.Errorf("internal error: cannot write Core 20 seed without Options.Label set")
   237  		}
   238  		if err := validateSystemLabel(opts.Label); err != nil {
   239  			return nil, err
   240  		}
   241  		pol = &policy20{model: model, opts: opts, warningf: w.warningf}
   242  		treeImpl = &tree20{opts: opts}
   243  	} else {
   244  		pol = &policy16{model: model, opts: opts, warningf: w.warningf}
   245  		treeImpl = &tree16{opts: opts}
   246  	}
   247  
   248  	if opts.DefaultChannel != "" {
   249  		deflCh, err := channel.ParseVerbatim(opts.DefaultChannel, "_")
   250  		if err != nil {
   251  			return nil, fmt.Errorf("cannot use global default option channel: %v", err)
   252  		}
   253  		if err := pol.checkDefaultChannel(deflCh); err != nil {
   254  			return nil, err
   255  		}
   256  	}
   257  
   258  	w.tree = treeImpl
   259  	w.policy = pol
   260  	return w, nil
   261  }
   262  
   263  type writerStep int
   264  
   265  const (
   266  	setOptionsSnapsStep = iota
   267  	startStep
   268  	localSnapsStep
   269  	infoDerivedStep
   270  	snapsToDownloadStep
   271  	downloadedStep
   272  	seedSnapsStep
   273  	writeMetaStep
   274  )
   275  
   276  var writerStepNames = map[writerStep]string{
   277  	startStep:           "Start",
   278  	setOptionsSnapsStep: "SetOptionsSnaps",
   279  	localSnapsStep:      "LocalSnaps",
   280  	infoDerivedStep:     "InfoDerived",
   281  	snapsToDownloadStep: "SnapsToDownload",
   282  	downloadedStep:      "Downloaded",
   283  	seedSnapsStep:       "SeedSnaps",
   284  	writeMetaStep:       "WriteMeta",
   285  }
   286  
   287  func (ws writerStep) String() string {
   288  	name := writerStepNames[ws]
   289  	if name == "" {
   290  		panic(fmt.Sprintf("unknown writerStep: %d", ws))
   291  	}
   292  	return name
   293  }
   294  
   295  func (w *Writer) checkStep(thisStep writerStep) error {
   296  	if thisStep != w.expectedStep {
   297  		// exceptions
   298  		alright := false
   299  		switch thisStep {
   300  		case startStep:
   301  			if w.expectedStep == setOptionsSnapsStep {
   302  				alright = true
   303  			}
   304  		case snapsToDownloadStep:
   305  			if w.expectedStep == localSnapsStep || w.expectedStep == infoDerivedStep {
   306  				if len(w.localSnaps) != 0 {
   307  					break
   308  				}
   309  				alright = true
   310  			}
   311  		}
   312  		if !alright {
   313  			expected := w.expectedStep.String()
   314  			switch w.expectedStep {
   315  			case setOptionsSnapsStep:
   316  				expected = "Start|SetOptionsSnaps"
   317  			case localSnapsStep:
   318  				if len(w.localSnaps) == 0 {
   319  					expected = "SnapsToDownload|LocalSnaps"
   320  				}
   321  			}
   322  			return fmt.Errorf("internal error: seedwriter.Writer expected %s to be invoked on it at this point, not %v", expected, thisStep)
   323  		}
   324  	}
   325  	w.expectedStep = thisStep + 1
   326  	return nil
   327  }
   328  
   329  // warningf adds a warning that can be later retrieved via Warnings.
   330  func (w *Writer) warningf(format string, a ...interface{}) {
   331  	w.warnings = append(w.warnings, fmt.Sprintf(format, a...))
   332  }
   333  
   334  // SetOptionsSnaps accepts options-referred snaps represented as OptionsSnap.
   335  func (w *Writer) SetOptionsSnaps(optSnaps []*OptionsSnap) error {
   336  	if err := w.checkStep(setOptionsSnapsStep); err != nil {
   337  		return err
   338  	}
   339  
   340  	if len(optSnaps) == 0 {
   341  		return nil
   342  	}
   343  
   344  	for _, sn := range optSnaps {
   345  		var whichSnap string
   346  		local := false
   347  		if sn.Name != "" {
   348  			if sn.Path != "" {
   349  				return fmt.Errorf("cannot specify both name and path for option snap %q", sn.Name)
   350  			}
   351  			snapName := sn.Name
   352  			whichSnap = snapName
   353  			if _, instanceKey := snap.SplitInstanceName(snapName); instanceKey != "" {
   354  				// be specific about this error
   355  				return fmt.Errorf("cannot use snap %q, parallel snap instances are unsupported", snapName)
   356  			}
   357  			if err := naming.ValidateSnap(snapName); err != nil {
   358  				return err
   359  			}
   360  
   361  			if w.byNameOptSnaps.Contains(sn) {
   362  				return fmt.Errorf("snap %q is repeated in options", snapName)
   363  			}
   364  			w.byNameOptSnaps.Add(sn)
   365  		} else {
   366  			if !strings.HasSuffix(sn.Path, ".snap") {
   367  				return fmt.Errorf("local option snap %q does not end in .snap", sn.Path)
   368  			}
   369  			if !osutil.FileExists(sn.Path) {
   370  				return fmt.Errorf("local option snap %q does not exist", sn.Path)
   371  			}
   372  
   373  			whichSnap = sn.Path
   374  			local = true
   375  		}
   376  		if sn.Channel != "" {
   377  			ch, err := channel.ParseVerbatim(sn.Channel, "_")
   378  			if err != nil {
   379  				return fmt.Errorf("cannot use option channel for snap %q: %v", whichSnap, err)
   380  			}
   381  			if err := w.policy.checkSnapChannel(ch, whichSnap); err != nil {
   382  				return err
   383  			}
   384  		}
   385  		if local {
   386  			if w.localSnaps == nil {
   387  				w.localSnaps = make(map[*OptionsSnap]*SeedSnap)
   388  			}
   389  			w.localSnaps[sn] = &SeedSnap{
   390  				SnapRef: nil,
   391  				Path:    sn.Path,
   392  
   393  				local:      true,
   394  				optionSnap: sn,
   395  			}
   396  		}
   397  	}
   398  
   399  	// used later to determine extra snaps
   400  	w.optionsSnaps = optSnaps
   401  
   402  	return nil
   403  }
   404  
   405  // Start starts the seed writing. It creates a RefAssertsFetcher using
   406  // newFetcher and uses it to fetch model related assertions. For
   407  // convenience it returns the fetcher possibly for use to fetch seed
   408  // snap assertions, a task that the writer delegates as well as snap
   409  // downloading. The writer assumes that the snap assertions will end up
   410  // in the given db (writing assertion database).
   411  func (w *Writer) Start(db asserts.RODatabase, newFetcher NewFetcherFunc) (RefAssertsFetcher, error) {
   412  	if err := w.checkStep(startStep); err != nil {
   413  		return nil, err
   414  	}
   415  	if db == nil {
   416  		return nil, fmt.Errorf("internal error: Writer *asserts.RODatabase is nil")
   417  	}
   418  	if newFetcher == nil {
   419  		return nil, fmt.Errorf("internal error: Writer newFetcherFunc is nil")
   420  	}
   421  	w.db = db
   422  
   423  	f := MakeRefAssertsFetcher(newFetcher)
   424  
   425  	if err := f.Save(w.model); err != nil {
   426  		const msg = "cannot fetch and check prerequisites for the model assertion: %v"
   427  		if !w.opts.TestSkipCopyUnverifiedModel {
   428  			return nil, fmt.Errorf(msg, err)
   429  		}
   430  		// Some naive tests including ubuntu-image ones use
   431  		// unverified models
   432  		w.warningf(msg, err)
   433  		f.ResetRefs()
   434  	}
   435  
   436  	// fetch device store assertion (and prereqs) if available
   437  	if w.model.Store() != "" {
   438  		err := snapasserts.FetchStore(f, w.model.Store())
   439  		if err != nil {
   440  			if nfe, ok := err.(*asserts.NotFoundError); !ok || nfe.Type != asserts.StoreType {
   441  				return nil, err
   442  			}
   443  		}
   444  	}
   445  
   446  	w.modelRefs = f.Refs()
   447  
   448  	if err := w.tree.mkFixedDirs(); err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	return f, nil
   453  }
   454  
   455  // LocalSnaps returns a list of seed snaps that are local.  The writer
   456  // delegates to produce *snap.Info for them to then be set via
   457  // SetInfo. If matching snap assertions can be found as well they can
   458  // be passed into SeedSnap ARefs, assuming they were added to the
   459  // writing assertion database.
   460  func (w *Writer) LocalSnaps() ([]*SeedSnap, error) {
   461  	if err := w.checkStep(localSnapsStep); err != nil {
   462  		return nil, err
   463  	}
   464  
   465  	if len(w.localSnaps) == 0 {
   466  		return nil, nil
   467  	}
   468  
   469  	lsnaps := make([]*SeedSnap, 0, len(w.localSnaps))
   470  	for _, optSnap := range w.optionsSnaps {
   471  		if sn := w.localSnaps[optSnap]; sn != nil {
   472  			lsnaps = append(lsnaps, sn)
   473  		}
   474  	}
   475  	return lsnaps, nil
   476  }
   477  
   478  // InfoDerived checks the local snaps metadata provided via setting it
   479  // into the SeedSnaps returned by the previous LocalSnaps.
   480  func (w *Writer) InfoDerived() error {
   481  	if err := w.checkStep(infoDerivedStep); err != nil {
   482  		return err
   483  	}
   484  
   485  	// loop this way to process for consistency in the same order
   486  	// as LocalSnaps result
   487  	for _, optSnap := range w.optionsSnaps {
   488  		sn := w.localSnaps[optSnap]
   489  		if sn == nil {
   490  			continue
   491  		}
   492  		if sn.Info == nil {
   493  			return fmt.Errorf("internal error: before seedwriter.Writer.InfoDerived snap %q Info should have been set", sn.Path)
   494  		}
   495  
   496  		if sn.Info.ID() == "" {
   497  			// this check is here in case we relax the checks in
   498  			// SetOptionsSnaps
   499  			if err := w.policy.allowsDangerousFeatures(); err != nil {
   500  				return err
   501  			}
   502  		}
   503  
   504  		sn.SnapRef = sn.Info
   505  
   506  		// local snap gets local revision
   507  		if sn.Info.Revision.Unset() {
   508  			sn.Info.Revision = snap.R(-1)
   509  		}
   510  
   511  		if w.byRefLocalSnaps.Contains(sn) {
   512  			return fmt.Errorf("local snap %q is repeated in options", sn.SnapName())
   513  		}
   514  
   515  		// in case, merge channel given by name separately
   516  		optSnap, _ := w.byNameOptSnaps.Lookup(sn).(*OptionsSnap)
   517  		if optSnap != nil {
   518  			w.consumedOptSnapNum++
   519  		}
   520  		if optSnap != nil && optSnap.Channel != "" {
   521  			if sn.optionSnap.Channel != "" {
   522  				if sn.optionSnap.Channel != optSnap.Channel {
   523  					return fmt.Errorf("option snap has different channels specified: %q=%q vs %q=%q", sn.Path, sn.optionSnap.Channel, optSnap.Name, optSnap.Channel)
   524  				}
   525  			} else {
   526  				sn.optionSnap.Channel = optSnap.Channel
   527  			}
   528  		}
   529  
   530  		w.byRefLocalSnaps.Add(sn)
   531  	}
   532  
   533  	return nil
   534  }
   535  
   536  // SetInfo sets Info of the SeedSnap and possibly computes its
   537  // destination Path.
   538  func (w *Writer) SetInfo(sn *SeedSnap, info *snap.Info) error {
   539  	sn.Info = info
   540  	if sn.local {
   541  		// nothing more to do
   542  		return nil
   543  	}
   544  
   545  	p, err := w.tree.snapPath(sn)
   546  	if err != nil {
   547  		return err
   548  	}
   549  
   550  	sn.Path = p
   551  	return nil
   552  }
   553  
   554  // SetRedirectChannel sets the redirect channel for the SeedSnap
   555  // for the in case there is a default track for it.
   556  func (w *Writer) SetRedirectChannel(sn *SeedSnap, redirectChannel string) error {
   557  	if sn.local {
   558  		return fmt.Errorf("internal error: cannot set redirect channel for local snap %q", sn.Path)
   559  	}
   560  	if sn.Info == nil {
   561  		return fmt.Errorf("internal error: before using seedwriter.Writer.SetRedirectChannel snap %q Info should have been set", sn.SnapName())
   562  	}
   563  	if redirectChannel == "" {
   564  		// nothing to do
   565  		return nil
   566  	}
   567  	_, err := channel.ParseVerbatim(redirectChannel, "-")
   568  	if err != nil {
   569  		return fmt.Errorf("invalid redirect channel for snap %q: %v", sn.SnapName(), err)
   570  	}
   571  	sn.Channel = redirectChannel
   572  	return nil
   573  
   574  }
   575  
   576  // snapsToDownloadSet indicates which set of snaps SnapsToDownload should compute
   577  type snapsToDownloadSet int
   578  
   579  const (
   580  	toDownloadModel snapsToDownloadSet = iota
   581  	toDownloadImplicit
   582  	toDownloadExtra
   583  	toDownloadExtraImplicit
   584  )
   585  
   586  var errSkipOptional = errors.New("skip")
   587  
   588  func (w *Writer) modelSnapToSeed(modSnap *asserts.ModelSnap) (*SeedSnap, error) {
   589  	sn, _ := w.byRefLocalSnaps.Lookup(modSnap).(*SeedSnap)
   590  	var optSnap *OptionsSnap
   591  	if sn == nil {
   592  		// not local, to download
   593  		optSnap, _ = w.byNameOptSnaps.Lookup(modSnap).(*OptionsSnap)
   594  		if modSnap.Presence == "optional" && optSnap == nil {
   595  			// an optional snap that is not confirmed
   596  			// by an OptionsSnap entry is skipped
   597  			return nil, errSkipOptional
   598  		}
   599  		sn = &SeedSnap{
   600  			SnapRef: modSnap,
   601  
   602  			local:      false,
   603  			optionSnap: optSnap,
   604  		}
   605  	} else {
   606  		optSnap = sn.optionSnap
   607  	}
   608  
   609  	channel, err := w.resolveChannel(modSnap.SnapName(), modSnap, optSnap)
   610  	if err != nil {
   611  		return nil, err
   612  	}
   613  	sn.modelSnap = modSnap
   614  	sn.Channel = channel
   615  	return sn, nil
   616  }
   617  
   618  func (w *Writer) modelSnapsToDownload(modSnaps []*asserts.ModelSnap) (toDownload []*SeedSnap, err error) {
   619  	if w.snapsFromModel == nil {
   620  		w.snapsFromModel = make([]*SeedSnap, 0, len(modSnaps))
   621  	}
   622  	toDownload = make([]*SeedSnap, 0, len(modSnaps))
   623  
   624  	alreadyConsidered := len(w.snapsFromModel)
   625  	for _, modSnap := range modSnaps {
   626  		sn, err := w.modelSnapToSeed(modSnap)
   627  		if err == errSkipOptional {
   628  			continue
   629  		}
   630  		if err != nil {
   631  			return nil, err
   632  		}
   633  		if !sn.local {
   634  			toDownload = append(toDownload, sn)
   635  		}
   636  		if sn.optionSnap != nil {
   637  			w.consumedOptSnapNum++
   638  		}
   639  		w.snapsFromModel = append(w.snapsFromModel, sn)
   640  	}
   641  	w.toDownloadConsideredNum = len(w.snapsFromModel) - alreadyConsidered
   642  	w.extraSnapsGuessNum = len(w.optionsSnaps) - w.consumedOptSnapNum
   643  
   644  	return toDownload, nil
   645  }
   646  
   647  func (w *Writer) modSnaps() ([]*asserts.ModelSnap, error) {
   648  	// model snaps are accumulated/processed in the order
   649  	//  * system snap if implicit
   650  	//  * essential snaps (in Model.EssentialSnaps order)
   651  	//  * not essential snaps
   652  	modSnaps := append([]*asserts.ModelSnap{}, w.model.EssentialSnaps()...)
   653  	if systemSnap := w.policy.systemSnap(); systemSnap != nil {
   654  		prepend := true
   655  		for _, modSnap := range modSnaps {
   656  			if naming.SameSnap(modSnap, systemSnap) {
   657  				prepend = false
   658  				modes := modSnap.Modes
   659  				expectedModes := systemSnap.Modes
   660  				if len(modes) != len(expectedModes) {
   661  					return nil, fmt.Errorf("internal error: system snap %q explicitly listed in model carries wrong modes: %q", systemSnap.SnapName(), modes)
   662  				}
   663  				for _, mod := range expectedModes {
   664  					if !strutil.ListContains(modes, mod) {
   665  						return nil, fmt.Errorf("internal error: system snap %q explicitly listed in model carries wrong modes: %q", systemSnap.SnapName(), modes)
   666  					}
   667  				}
   668  				break
   669  			}
   670  		}
   671  		if prepend {
   672  			modSnaps = append([]*asserts.ModelSnap{systemSnap}, modSnaps...)
   673  		}
   674  	}
   675  	modSnaps = append(modSnaps, w.model.SnapsWithoutEssential()...)
   676  	return modSnaps, nil
   677  }
   678  
   679  func (w *Writer) optExtraSnaps() []*OptionsSnap {
   680  	extra := make([]*OptionsSnap, 0, w.extraSnapsGuessNum)
   681  	for _, optSnap := range w.optionsSnaps {
   682  		var snapRef naming.SnapRef = optSnap
   683  		if sn := w.localSnaps[optSnap]; sn != nil {
   684  			snapRef = sn
   685  		}
   686  		if w.availableSnaps.Contains(snapRef) {
   687  			continue
   688  		}
   689  		extra = append(extra, optSnap)
   690  	}
   691  	return extra
   692  }
   693  
   694  func (w *Writer) extraSnapToSeed(optSnap *OptionsSnap) (*SeedSnap, error) {
   695  	sn := w.localSnaps[optSnap]
   696  	if sn == nil {
   697  		// not local, to download
   698  		sn = &SeedSnap{
   699  			SnapRef: optSnap,
   700  
   701  			local:      false,
   702  			optionSnap: optSnap,
   703  		}
   704  	}
   705  	if sn.SnapName() == "" {
   706  		return nil, fmt.Errorf("internal error: option extra snap has no associated name: %#v %#v", optSnap, sn)
   707  	}
   708  
   709  	channel, err := w.resolveChannel(sn.SnapName(), nil, optSnap)
   710  	if err != nil {
   711  		return nil, err
   712  	}
   713  	sn.Channel = channel
   714  	return sn, nil
   715  }
   716  
   717  func (w *Writer) extraSnapsToDownload(extraSnaps []*OptionsSnap) (toDownload []*SeedSnap, err error) {
   718  	if w.extraSnaps == nil {
   719  		w.extraSnaps = make([]*SeedSnap, 0, len(extraSnaps))
   720  	}
   721  	toDownload = make([]*SeedSnap, 0, len(extraSnaps))
   722  
   723  	alreadyConsidered := len(w.extraSnaps)
   724  	for _, optSnap := range extraSnaps {
   725  		sn, err := w.extraSnapToSeed(optSnap)
   726  		if err != nil {
   727  			return nil, err
   728  		}
   729  		if !sn.local {
   730  			toDownload = append(toDownload, sn)
   731  		}
   732  		w.extraSnaps = append(w.extraSnaps, sn)
   733  	}
   734  	w.toDownloadConsideredNum = len(w.extraSnaps) - alreadyConsidered
   735  
   736  	return toDownload, nil
   737  }
   738  
   739  // SnapsToDownload returns a list of seed snaps to download. Once that
   740  // is done and their SeedSnaps Info with SetInfo and ARefs fields are
   741  // set, Downloaded should be called next.
   742  func (w *Writer) SnapsToDownload() (snaps []*SeedSnap, err error) {
   743  	if err := w.checkStep(snapsToDownloadStep); err != nil {
   744  		return nil, err
   745  	}
   746  
   747  	switch w.toDownload {
   748  	case toDownloadModel:
   749  		modSnaps, err := w.modSnaps()
   750  		if err != nil {
   751  			return nil, err
   752  		}
   753  		toDownload, err := w.modelSnapsToDownload(modSnaps)
   754  		if err != nil {
   755  			return nil, err
   756  		}
   757  		if w.extraSnapsGuessNum > 0 {
   758  			// this check is here in case we relax the checks in
   759  			// SetOptionsSnaps
   760  			if err := w.policy.allowsDangerousFeatures(); err != nil {
   761  				return nil, err
   762  			}
   763  		}
   764  		return toDownload, nil
   765  	case toDownloadImplicit:
   766  		return w.modelSnapsToDownload(w.policy.implicitSnaps(w.availableSnaps))
   767  	case toDownloadExtra:
   768  		return w.extraSnapsToDownload(w.optExtraSnaps())
   769  	case toDownloadExtraImplicit:
   770  		return w.extraSnapsToDownload(w.policy.implicitExtraSnaps(w.availableSnaps))
   771  	default:
   772  		panic(fmt.Sprintf("unknown to-download set: %d", w.toDownload))
   773  	}
   774  }
   775  
   776  func (w *Writer) resolveChannel(whichSnap string, modSnap *asserts.ModelSnap, optSnap *OptionsSnap) (string, error) {
   777  	var optChannel string
   778  	if optSnap != nil {
   779  		optChannel = optSnap.Channel
   780  	}
   781  	if optChannel == "" {
   782  		optChannel = w.opts.DefaultChannel
   783  	}
   784  
   785  	if modSnap != nil && modSnap.PinnedTrack != "" {
   786  		resChannel, err := channel.ResolvePinned(modSnap.PinnedTrack, optChannel)
   787  		if err == channel.ErrPinnedTrackSwitch {
   788  			return "", fmt.Errorf("option channel %q for %s has a track incompatible with the pinned track from model assertion: %s", optChannel, whichModelSnap(modSnap, w.model), modSnap.PinnedTrack)
   789  		}
   790  		if err != nil {
   791  			// shouldn't happen given that we check that
   792  			// the inputs parse before
   793  			return "", fmt.Errorf("internal error: cannot resolve pinned track %q and option channel %q for snap %q", modSnap.PinnedTrack, optChannel, whichSnap)
   794  		}
   795  		return resChannel, nil
   796  	}
   797  
   798  	var defaultChannel string
   799  	if modSnap != nil {
   800  		defaultChannel = modSnap.DefaultChannel
   801  		if defaultChannel == "" {
   802  			defaultChannel = w.policy.modelSnapDefaultChannel()
   803  		}
   804  	} else {
   805  		defaultChannel = w.policy.extraSnapDefaultChannel()
   806  	}
   807  
   808  	resChannel, err := channel.Resolve(defaultChannel, optChannel)
   809  	if err != nil {
   810  		// shouldn't happen given that we check that
   811  		// the inputs parse before
   812  		return "", fmt.Errorf("internal error: cannot resolve model default channel %q and option channel %q for snap %q", defaultChannel, optChannel, whichSnap)
   813  	}
   814  	return resChannel, nil
   815  }
   816  
   817  func (w *Writer) checkBase(info *snap.Info) error {
   818  	// Sanity check, note that we could support this case
   819  	// if we have a use-case but it requires changes in the
   820  	// devicestate/firstboot.go ordering code.
   821  	if info.Type() == snap.TypeGadget && !w.model.Classic() && info.Base != w.model.Base() {
   822  		return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", info.Base, w.model.Base())
   823  	}
   824  
   825  	// snap explicitly listed as not needing a base snap (e.g. a content-only snap)
   826  	if info.Base == "none" {
   827  		return nil
   828  	}
   829  
   830  	return w.policy.checkBase(info, w.availableSnaps)
   831  }
   832  
   833  func (w *Writer) downloaded(seedSnaps []*SeedSnap) error {
   834  	if w.availableSnaps == nil {
   835  		w.availableSnaps = naming.NewSnapSet(nil)
   836  	}
   837  
   838  	for _, sn := range seedSnaps {
   839  		if sn.Info == nil {
   840  			return fmt.Errorf("internal error: before seedwriter.Writer.Downloaded snap %q Info should have been set", sn.SnapName())
   841  		}
   842  		w.availableSnaps.Add(sn)
   843  	}
   844  
   845  	for _, sn := range seedSnaps {
   846  		info := sn.Info
   847  		if !sn.local {
   848  			if info.ID() == "" {
   849  				return fmt.Errorf("internal error: before seedwriter.Writer.Downloaded snap %q snap-id should have been set", sn.SnapName())
   850  			}
   851  		}
   852  		if info.ID() != "" {
   853  			if sn.ARefs == nil {
   854  				return fmt.Errorf("internal error: before seedwriter.Writer.Downloaded snap %q ARefs should have been set", sn.SnapName())
   855  			}
   856  		}
   857  
   858  		// TODO: optionally check that model snap name and
   859  		// info snap name match
   860  
   861  		if err := checkType(sn, w.model); err != nil {
   862  			return err
   863  		}
   864  
   865  		needsClassic := info.NeedsClassic()
   866  		if needsClassic && !w.model.Classic() {
   867  			return fmt.Errorf("cannot use classic snap %q in a core system", info.SnapName())
   868  		}
   869  
   870  		if err := w.checkBase(info); err != nil {
   871  			return err
   872  		}
   873  		// error about missing default providers
   874  		for dp := range snap.NeededDefaultProviders(info) {
   875  			if !w.availableSnaps.Contains(naming.Snap(dp)) {
   876  				// TODO: have a way to ignore this issue on a snap by snap basis?
   877  				return fmt.Errorf("cannot use snap %q without its default content provider %q being added explicitly", info.SnapName(), dp)
   878  			}
   879  		}
   880  
   881  		if err := w.checkPublisher(sn); err != nil {
   882  			return err
   883  		}
   884  	}
   885  
   886  	return nil
   887  }
   888  
   889  // Downloaded checks the downloaded snaps metadata provided via
   890  // setting it into the SeedSnaps returned by the previous
   891  // SnapsToDownload. It also returns whether the seed snap set is
   892  // complete or SnapsToDownload should be called again.
   893  func (w *Writer) Downloaded() (complete bool, err error) {
   894  	if err := w.checkStep(downloadedStep); err != nil {
   895  		return false, err
   896  	}
   897  
   898  	// TODO: w.policy.resetChecks()
   899  
   900  	var considered []*SeedSnap
   901  	switch w.toDownload {
   902  	default:
   903  		panic(fmt.Sprintf("unknown to-download set: %d", w.toDownload))
   904  	case toDownloadImplicit:
   905  		fallthrough
   906  	case toDownloadModel:
   907  		considered = w.snapsFromModel
   908  	case toDownloadExtraImplicit:
   909  		fallthrough
   910  	case toDownloadExtra:
   911  		considered = w.extraSnaps
   912  	}
   913  
   914  	considered = considered[len(considered)-w.toDownloadConsideredNum:]
   915  	err = w.downloaded(considered)
   916  	if err != nil {
   917  		return false, err
   918  	}
   919  
   920  	switch w.toDownload {
   921  	case toDownloadModel:
   922  		implicitNeeded, err := w.policy.needsImplicitSnaps(w.availableSnaps)
   923  		if err != nil {
   924  			return false, err
   925  		}
   926  		if implicitNeeded {
   927  			w.toDownload = toDownloadImplicit
   928  			w.expectedStep = snapsToDownloadStep
   929  			return false, nil
   930  		}
   931  		fallthrough
   932  	case toDownloadImplicit:
   933  		if w.extraSnapsGuessNum > 0 {
   934  			w.toDownload = toDownloadExtra
   935  			w.expectedStep = snapsToDownloadStep
   936  			return false, nil
   937  		}
   938  	case toDownloadExtra:
   939  		implicitNeeded, err := w.policy.needsImplicitSnaps(w.availableSnaps)
   940  		if err != nil {
   941  			return false, err
   942  		}
   943  		if implicitNeeded {
   944  			w.toDownload = toDownloadExtraImplicit
   945  			w.expectedStep = snapsToDownloadStep
   946  			return false, nil
   947  		}
   948  	case toDownloadExtraImplicit:
   949  		// nothing to do
   950  		// TODO: consider generalizing the logic and optionally asking
   951  		// the policy again
   952  	default:
   953  		panic(fmt.Sprintf("unknown to-download set: %d", w.toDownload))
   954  	}
   955  
   956  	return true, nil
   957  }
   958  
   959  func (w *Writer) checkPublisher(sn *SeedSnap) error {
   960  	if sn.local && sn.ARefs == nil {
   961  		// nothing to do
   962  		return nil
   963  	}
   964  	info := sn.Info
   965  	var kind string
   966  	switch info.Type() {
   967  	case snap.TypeKernel:
   968  		kind = "kernel"
   969  	case snap.TypeGadget:
   970  		kind = "gadget"
   971  	default:
   972  		return nil
   973  	}
   974  	// TODO: share helpers with devicestate if the policy becomes much more complicated
   975  	snapDecl, err := w.snapDecl(sn)
   976  	if err != nil {
   977  		return err
   978  	}
   979  	publisher := snapDecl.PublisherID()
   980  	if publisher != w.model.BrandID() && publisher != "canonical" {
   981  		return fmt.Errorf("cannot use %s %q published by %q for model by %q", kind, info.SnapName(), publisher, w.model.BrandID())
   982  	}
   983  	return nil
   984  }
   985  
   986  func (w *Writer) snapDecl(sn *SeedSnap) (*asserts.SnapDeclaration, error) {
   987  	for _, ref := range sn.ARefs {
   988  		if ref.Type == asserts.SnapDeclarationType {
   989  			a, err := ref.Resolve(w.db.Find)
   990  			if err != nil {
   991  				return nil, fmt.Errorf("internal error: lost saved assertion")
   992  			}
   993  			return a.(*asserts.SnapDeclaration), nil
   994  		}
   995  	}
   996  	return nil, fmt.Errorf("internal error: snap %q has no snap-declaration set", sn.SnapName())
   997  }
   998  
   999  // Warnings returns the warning messages produced so far. No warnings
  1000  // should be generated after Downloaded signaled complete.
  1001  func (w *Writer) Warnings() []string {
  1002  	return w.warnings
  1003  }
  1004  
  1005  // SeedSnaps checks seed snaps and copies local snaps into the seed using copySnap.
  1006  func (w *Writer) SeedSnaps(copySnap func(name, src, dst string) error) error {
  1007  	if err := w.checkStep(seedSnapsStep); err != nil {
  1008  		return err
  1009  	}
  1010  
  1011  	seedSnaps := func(snaps []*SeedSnap) error {
  1012  		for _, sn := range snaps {
  1013  			info := sn.Info
  1014  			if !sn.local {
  1015  				expectedPath, err := w.tree.snapPath(sn)
  1016  				if err != nil {
  1017  					return err
  1018  				}
  1019  				if sn.Path != expectedPath {
  1020  					return fmt.Errorf("internal error: before seedwriter.Writer.SeedSnaps snap %q Path should have been set to %q", sn.SnapName(), expectedPath)
  1021  				}
  1022  				if !osutil.FileExists(expectedPath) {
  1023  					return fmt.Errorf("internal error: before seedwriter.Writer.SeedSnaps snap file %q should exist", expectedPath)
  1024  				}
  1025  			} else {
  1026  				var snapPath func(*SeedSnap) (string, error)
  1027  				if sn.Info.ID() != "" {
  1028  					// actually asserted
  1029  					snapPath = w.tree.snapPath
  1030  				} else {
  1031  					// purely local
  1032  					snapPath = w.tree.localSnapPath
  1033  				}
  1034  				dst, err := snapPath(sn)
  1035  				if err != nil {
  1036  					return err
  1037  				}
  1038  				if err := copySnap(info.SnapName(), sn.Path, dst); err != nil {
  1039  					return err
  1040  				}
  1041  				// record final destination path
  1042  				sn.Path = dst
  1043  			}
  1044  		}
  1045  		return nil
  1046  	}
  1047  
  1048  	if err := seedSnaps(w.snapsFromModel); err != nil {
  1049  		return err
  1050  	}
  1051  	if err := seedSnaps(w.extraSnaps); err != nil {
  1052  		return err
  1053  	}
  1054  
  1055  	return nil
  1056  }
  1057  
  1058  // WriteMeta writes seed metadata and assertions into the seed.
  1059  func (w *Writer) WriteMeta() error {
  1060  	if err := w.checkStep(writeMetaStep); err != nil {
  1061  		return err
  1062  	}
  1063  
  1064  	snapsFromModel := w.snapsFromModel
  1065  	extraSnaps := w.extraSnaps
  1066  
  1067  	if err := w.tree.writeAssertions(w.db, w.modelRefs, snapsFromModel, extraSnaps); err != nil {
  1068  		return err
  1069  	}
  1070  
  1071  	return w.tree.writeMeta(snapsFromModel, extraSnaps)
  1072  }
  1073  
  1074  // query accessors
  1075  
  1076  func (w *Writer) checkSnapsAccessor() error {
  1077  	if w.expectedStep < seedSnapsStep {
  1078  		return fmt.Errorf("internal error: seedwriter.Writer cannot query seed snaps before Downloaded signaled complete")
  1079  	}
  1080  	return nil
  1081  }
  1082  
  1083  // BootSnaps returns the seed snaps involved in the boot process.
  1084  // It can be invoked only after Downloaded returns complete ==
  1085  // true. It returns an error for classic models as for those no snaps
  1086  // participate in boot before user space.
  1087  func (w *Writer) BootSnaps() ([]*SeedSnap, error) {
  1088  	if err := w.checkSnapsAccessor(); err != nil {
  1089  		return nil, err
  1090  	}
  1091  	if w.model.Classic() {
  1092  		return nil, fmt.Errorf("no snaps participating in boot on classic")
  1093  	}
  1094  	var bootSnaps []*SeedSnap
  1095  	for _, sn := range w.snapsFromModel {
  1096  		bootSnaps = append(bootSnaps, sn)
  1097  		if sn.Info.Type() == snap.TypeGadget {
  1098  			break
  1099  
  1100  		}
  1101  	}
  1102  	return bootSnaps, nil
  1103  }
  1104  
  1105  // UnassertedSnaps returns references for all unasserted snaps in the seed.
  1106  // It can be invoked only after Downloaded returns complete ==
  1107  // true.
  1108  func (w *Writer) UnassertedSnaps() ([]naming.SnapRef, error) {
  1109  	if err := w.checkSnapsAccessor(); err != nil {
  1110  		return nil, err
  1111  	}
  1112  	var res []naming.SnapRef
  1113  	for _, sn := range w.snapsFromModel {
  1114  		if sn.Info.ID() != "" {
  1115  			continue
  1116  		}
  1117  		res = append(res, sn.SnapRef)
  1118  	}
  1119  
  1120  	for _, sn := range w.extraSnaps {
  1121  		if sn.Info.ID() != "" {
  1122  			continue
  1123  		}
  1124  		res = append(res, sn.SnapRef)
  1125  	}
  1126  	return res, nil
  1127  }