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