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