gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/seed/seedwriter/writer.go (about)

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