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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 seed
    21  
    22  /* ATTN this should *not* use:
    23  
    24  * dirs package: it is passed an explicit directory to work on
    25  
    26  * release.OnClassic: it assumes classic based on the model classic
    27    option; consistency between system and model can/must be enforced
    28    elsewhere
    29  
    30  */
    31  
    32  import (
    33  	"encoding/json"
    34  	"errors"
    35  	"fmt"
    36  	"os"
    37  	"path/filepath"
    38  
    39  	"github.com/snapcore/snapd/asserts"
    40  	"github.com/snapcore/snapd/asserts/snapasserts"
    41  	"github.com/snapcore/snapd/osutil"
    42  	"github.com/snapcore/snapd/seed/internal"
    43  	"github.com/snapcore/snapd/snap"
    44  	"github.com/snapcore/snapd/snap/naming"
    45  	"github.com/snapcore/snapd/strutil"
    46  	"github.com/snapcore/snapd/timings"
    47  )
    48  
    49  type seed20 struct {
    50  	systemDir string
    51  
    52  	db asserts.RODatabase
    53  
    54  	model *asserts.Model
    55  
    56  	snapDeclsByID   map[string]*asserts.SnapDeclaration
    57  	snapDeclsByName map[string]*asserts.SnapDeclaration
    58  
    59  	snapRevsByID map[string]*asserts.SnapRevision
    60  
    61  	optSnaps    []*internal.Snap20
    62  	optSnapsIdx int
    63  
    64  	auxInfos map[string]*internal.AuxInfo20
    65  
    66  	snaps []*Snap
    67  	// modes holds a matching applicable modes set for each snap in snaps
    68  	modes             [][]string
    69  	essentialSnapsNum int
    70  }
    71  
    72  func (s *seed20) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
    73  	if db == nil {
    74  		// a db was not provided, create an internal temporary one
    75  		var err error
    76  		db, commitTo, err = newMemAssertionsDB()
    77  		if err != nil {
    78  			return err
    79  		}
    80  	}
    81  
    82  	assertsDir := filepath.Join(s.systemDir, "assertions")
    83  	// collect assertions that are not the model
    84  	var declRefs []*asserts.Ref
    85  	var revRefs []*asserts.Ref
    86  	checkAssertion := func(ref *asserts.Ref) error {
    87  		switch ref.Type {
    88  		case asserts.ModelType:
    89  			return fmt.Errorf("system cannot have any model assertion but the one in the system model assertion file")
    90  		case asserts.SnapDeclarationType:
    91  			declRefs = append(declRefs, ref)
    92  		case asserts.SnapRevisionType:
    93  			revRefs = append(revRefs, ref)
    94  		}
    95  		return nil
    96  	}
    97  
    98  	batch, err := loadAssertions(assertsDir, checkAssertion)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	refs, err := readAsserts(batch, filepath.Join(s.systemDir, "model"))
   104  	if err != nil {
   105  		return fmt.Errorf("cannot read model assertion: %v", err)
   106  	}
   107  	if len(refs) != 1 || refs[0].Type != asserts.ModelType {
   108  		return fmt.Errorf("system model assertion file must contain exactly the model assertion")
   109  	}
   110  	modelRef := refs[0]
   111  
   112  	if len(declRefs) != len(revRefs) {
   113  		return fmt.Errorf("system unexpectedly holds a different number of snap-declaration than snap-revision assertions")
   114  	}
   115  
   116  	// this also verifies the consistency of all of them
   117  	if err := commitTo(batch); err != nil {
   118  		return err
   119  	}
   120  
   121  	find := func(ref *asserts.Ref) (asserts.Assertion, error) {
   122  		a, err := ref.Resolve(db.Find)
   123  		if err != nil {
   124  			return nil, fmt.Errorf("internal error: cannot find just accepted assertion %v: %v", ref, err)
   125  		}
   126  		return a, nil
   127  	}
   128  
   129  	a, err := find(modelRef)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	modelAssertion := a.(*asserts.Model)
   134  
   135  	snapDeclsByName := make(map[string]*asserts.SnapDeclaration, len(declRefs))
   136  	snapDeclsByID := make(map[string]*asserts.SnapDeclaration, len(declRefs))
   137  
   138  	for _, declRef := range declRefs {
   139  		a, err := find(declRef)
   140  		if err != nil {
   141  			return err
   142  		}
   143  		snapDecl := a.(*asserts.SnapDeclaration)
   144  		snapDeclsByID[snapDecl.SnapID()] = snapDecl
   145  		if snapDecl1 := snapDeclsByName[snapDecl.SnapName()]; snapDecl1 != nil {
   146  			return fmt.Errorf("cannot have multiple snap-declarations for the same snap-name: %s", snapDecl.SnapName())
   147  		}
   148  		snapDeclsByName[snapDecl.SnapName()] = snapDecl
   149  	}
   150  
   151  	snapRevsByID := make(map[string]*asserts.SnapRevision, len(revRefs))
   152  
   153  	for _, revRef := range revRefs {
   154  		a, err := find(revRef)
   155  		if err != nil {
   156  			return err
   157  		}
   158  		snapRevision := a.(*asserts.SnapRevision)
   159  		snapRevision1 := snapRevsByID[snapRevision.SnapID()]
   160  		if snapRevision1 != nil {
   161  			if snapRevision1.SnapRevision() != snapRevision.SnapRevision() {
   162  				return fmt.Errorf("cannot have multiple snap-revisions for the same snap-id: %s", snapRevision1.SnapID())
   163  			}
   164  		} else {
   165  			snapRevsByID[snapRevision.SnapID()] = snapRevision
   166  		}
   167  	}
   168  
   169  	// remember db for later use
   170  	s.db = db
   171  	// remember
   172  	s.model = modelAssertion
   173  	s.snapDeclsByID = snapDeclsByID
   174  	s.snapDeclsByName = snapDeclsByName
   175  	s.snapRevsByID = snapRevsByID
   176  
   177  	return nil
   178  }
   179  
   180  func (s *seed20) Model() *asserts.Model {
   181  	if s.model == nil {
   182  		panic("internal error: model assertion unset (LoadAssertions not called)")
   183  	}
   184  	return s.model
   185  }
   186  
   187  func (s *seed20) Brand() (*asserts.Account, error) {
   188  	return findBrand(s, s.db)
   189  }
   190  
   191  func (s *seed20) UsesSnapdSnap() bool {
   192  	return true
   193  }
   194  
   195  func (s *seed20) loadOptions() error {
   196  	if s.model.Grade() != asserts.ModelDangerous {
   197  		// options.yaml is not supported for grade > dangerous
   198  		return nil
   199  	}
   200  	optionsFn := filepath.Join(s.systemDir, "options.yaml")
   201  	if !osutil.FileExists(optionsFn) {
   202  		// missing
   203  		return nil
   204  	}
   205  	options20, err := internal.ReadOptions20(optionsFn)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	s.optSnaps = options20.Snaps
   210  	return nil
   211  }
   212  
   213  func (s *seed20) nextOptSnap(modSnap *asserts.ModelSnap) (optSnap *internal.Snap20, done bool) {
   214  	// we can merge model snaps and options snaps because
   215  	// both seed20.go and writer.go follow the order:
   216  	// system snap, model.EssentialSnaps(), model.SnapsWithoutEssential()
   217  	if s.optSnapsIdx == len(s.optSnaps) {
   218  		return nil, true
   219  	}
   220  	next := s.optSnaps[s.optSnapsIdx]
   221  	if modSnap == nil || naming.SameSnap(next, modSnap) {
   222  		s.optSnapsIdx++
   223  		return next, false
   224  	}
   225  	return nil, false
   226  }
   227  
   228  func (s *seed20) loadAuxInfos() error {
   229  	auxInfoFn := filepath.Join(s.systemDir, "snaps", "aux-info.json")
   230  	if !osutil.FileExists(auxInfoFn) {
   231  		// missing
   232  		return nil
   233  	}
   234  
   235  	f, err := os.Open(auxInfoFn)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	defer f.Close()
   240  	dec := json.NewDecoder(f)
   241  	if err := dec.Decode(&s.auxInfos); err != nil {
   242  		return fmt.Errorf("cannot decode aux-info.json: %v", err)
   243  	}
   244  	return nil
   245  }
   246  
   247  type noSnapDeclarationError struct {
   248  	snapRef naming.SnapRef
   249  }
   250  
   251  func (e *noSnapDeclarationError) Error() string {
   252  	snapID := e.snapRef.ID()
   253  	if snapID != "" {
   254  		return fmt.Sprintf("cannot find snap-declaration for snap-id: %s", snapID)
   255  	}
   256  	return fmt.Sprintf("cannot find snap-declaration for snap name: %s", e.snapRef.SnapName())
   257  }
   258  
   259  func (s *seed20) lookupVerifiedRevision(snapRef naming.SnapRef, snapsDir string) (snapPath string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, err error) {
   260  	snapID := snapRef.ID()
   261  	if snapID != "" {
   262  		snapDecl = s.snapDeclsByID[snapID]
   263  		if snapDecl == nil {
   264  			return "", nil, nil, &noSnapDeclarationError{snapRef}
   265  		}
   266  	} else {
   267  		if s.model.Grade() != asserts.ModelDangerous {
   268  			return "", nil, nil, fmt.Errorf("all system snaps must be identified by snap-id, missing for %q", snapRef.SnapName())
   269  		}
   270  		snapName := snapRef.SnapName()
   271  		snapDecl = s.snapDeclsByName[snapName]
   272  		if snapDecl == nil {
   273  			return "", nil, nil, &noSnapDeclarationError{snapRef}
   274  		}
   275  		snapID = snapDecl.SnapID()
   276  	}
   277  
   278  	snapRev = s.snapRevsByID[snapID]
   279  	if snapRev == nil {
   280  		return "", nil, nil, fmt.Errorf("internal error: cannot find snap-revision for snap-id: %s", snapID)
   281  	}
   282  
   283  	snapName := snapDecl.SnapName()
   284  	snapPath = filepath.Join(s.systemDir, snapsDir, fmt.Sprintf("%s_%d.snap", snapName, snapRev.SnapRevision()))
   285  
   286  	fi, err := os.Stat(snapPath)
   287  	if err != nil {
   288  		return "", nil, nil, fmt.Errorf("cannot stat snap: %v", err)
   289  	}
   290  
   291  	if fi.Size() != int64(snapRev.SnapSize()) {
   292  		return "", nil, nil, fmt.Errorf("cannot validate %q for snap %q (snap-id %q), wrong size", snapPath, snapName, snapID)
   293  	}
   294  
   295  	snapSHA3_384, _, err := asserts.SnapFileSHA3_384(snapPath)
   296  	if err != nil {
   297  		return "", nil, nil, err
   298  	}
   299  
   300  	if snapSHA3_384 != snapRev.SnapSHA3_384() {
   301  		return "", nil, nil, fmt.Errorf("cannot validate %q for snap %q (snap-id %q), hash mismatch with snap-revision", snapPath, snapName, snapID)
   302  
   303  	}
   304  
   305  	return snapPath, snapRev, snapDecl, nil
   306  }
   307  
   308  func (s *seed20) addSnap(snapRef naming.SnapRef, optSnap *internal.Snap20, modes []string, channel string, snapsDir string, tm timings.Measurer) (*Snap, error) {
   309  	if optSnap != nil && optSnap.Channel != "" {
   310  		channel = optSnap.Channel
   311  	}
   312  
   313  	var path string
   314  	var sideInfo *snap.SideInfo
   315  	if optSnap != nil && optSnap.Unasserted != "" {
   316  		path = filepath.Join(s.systemDir, "snaps", optSnap.Unasserted)
   317  		info, err := readInfo(path, nil)
   318  		if err != nil {
   319  			return nil, fmt.Errorf("cannot read unasserted snap: %v", err)
   320  		}
   321  		sideInfo = &snap.SideInfo{RealName: info.SnapName()}
   322  		// suppress channel
   323  		channel = ""
   324  	} else {
   325  		var err error
   326  		timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", snapRef.SnapName()), func(nested timings.Measurer) {
   327  			var snapRev *asserts.SnapRevision
   328  			var snapDecl *asserts.SnapDeclaration
   329  			path, snapRev, snapDecl, err = s.lookupVerifiedRevision(snapRef, snapsDir)
   330  			if err == nil {
   331  				sideInfo = snapasserts.SideInfoFromSnapAssertions(snapDecl, snapRev)
   332  			}
   333  		})
   334  		if err != nil {
   335  			return nil, err
   336  		}
   337  	}
   338  
   339  	// complement with aux-info.json information
   340  	auxInfo := s.auxInfos[sideInfo.SnapID]
   341  	if auxInfo != nil {
   342  		sideInfo.Private = auxInfo.Private
   343  		sideInfo.Contact = auxInfo.Contact
   344  	}
   345  
   346  	seedSnap := &Snap{
   347  		Path: path,
   348  
   349  		SideInfo: sideInfo,
   350  
   351  		Channel: channel,
   352  	}
   353  
   354  	s.snaps = append(s.snaps, seedSnap)
   355  	s.modes = append(s.modes, modes)
   356  
   357  	return seedSnap, nil
   358  }
   359  
   360  var errFiltered = errors.New("filtered out")
   361  
   362  func (s *seed20) addModelSnap(modelSnap *asserts.ModelSnap, essential bool, filter func(*asserts.ModelSnap) bool, tm timings.Measurer) (*Snap, error) {
   363  	optSnap, _ := s.nextOptSnap(modelSnap)
   364  	if filter != nil && !filter(modelSnap) {
   365  		return nil, errFiltered
   366  	}
   367  	seedSnap, err := s.addSnap(modelSnap, optSnap, modelSnap.Modes, modelSnap.DefaultChannel, "../../snaps", tm)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  
   372  	seedSnap.Essential = essential
   373  	seedSnap.Required = essential || modelSnap.Presence == "required"
   374  	if essential {
   375  		seedSnap.EssentialType = snapTypeFromModel(modelSnap)
   376  		s.essentialSnapsNum++
   377  	}
   378  
   379  	return seedSnap, nil
   380  }
   381  
   382  func (s *seed20) LoadMeta(tm timings.Measurer) error {
   383  	if err := s.loadEssentialMeta(nil, tm); err != nil {
   384  		return err
   385  	}
   386  	if err := s.loadModelRestMeta(tm); err != nil {
   387  		return err
   388  	}
   389  
   390  	// extra snaps
   391  	runMode := []string{"run"}
   392  	for {
   393  		optSnap, done := s.nextOptSnap(nil)
   394  		if done {
   395  			break
   396  		}
   397  
   398  		_, err := s.addSnap(optSnap, optSnap, runMode, "latest/stable", "snaps", tm)
   399  		if err != nil {
   400  			return err
   401  		}
   402  	}
   403  
   404  	return nil
   405  }
   406  
   407  func (s *seed20) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
   408  	filterEssential := essentialSnapTypesToModelFilter(essentialTypes)
   409  
   410  	if err := s.loadEssentialMeta(filterEssential, tm); err != nil {
   411  		return err
   412  	}
   413  
   414  	if s.essentialSnapsNum != len(essentialTypes) {
   415  		// did not find all the explicitly asked essential types
   416  		return fmt.Errorf("model does not specify all the requested essential snaps: %v", essentialTypes)
   417  	}
   418  
   419  	return nil
   420  }
   421  
   422  func (s *seed20) loadEssentialMeta(filterEssential func(*asserts.ModelSnap) bool, tm timings.Measurer) error {
   423  	model := s.Model()
   424  
   425  	if err := s.loadOptions(); err != nil {
   426  		return err
   427  	}
   428  
   429  	if err := s.loadAuxInfos(); err != nil {
   430  		return err
   431  	}
   432  
   433  	essSnaps := model.EssentialSnaps()
   434  	const essential = true
   435  
   436  	// an explicit snapd is the first of all of snaps
   437  	if essSnaps[0].SnapType != "snapd" {
   438  		snapdSnap := internal.MakeSystemSnap("snapd", "latest/stable", []string{"run", "ephemeral"})
   439  		if _, err := s.addModelSnap(snapdSnap, essential, filterEssential, tm); err != nil && err != errFiltered {
   440  			return err
   441  		}
   442  	}
   443  
   444  	for _, modelSnap := range essSnaps {
   445  		seedSnap, err := s.addModelSnap(modelSnap, essential, filterEssential, tm)
   446  		if err != nil {
   447  			if err == errFiltered {
   448  				continue
   449  			}
   450  			return err
   451  		}
   452  		if modelSnap.SnapType == "gadget" {
   453  			// sanity
   454  			info, err := readInfo(seedSnap.Path, seedSnap.SideInfo)
   455  			if err != nil {
   456  				return err
   457  			}
   458  			if info.Base != model.Base() {
   459  				return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", info.Base, model.Base())
   460  			}
   461  			// TODO: when we allow extend models for classic
   462  			// we need to add the gadget base here
   463  		}
   464  	}
   465  
   466  	return nil
   467  }
   468  
   469  func (s *seed20) loadModelRestMeta(tm timings.Measurer) error {
   470  	model := s.Model()
   471  
   472  	const notEssential = false
   473  	for _, modelSnap := range model.SnapsWithoutEssential() {
   474  		_, err := s.addModelSnap(modelSnap, notEssential, nil, tm)
   475  		if err != nil {
   476  			if _, ok := err.(*noSnapDeclarationError); ok && modelSnap.Presence == "optional" {
   477  				// skipped optional snap is ok
   478  				continue
   479  			}
   480  			return err
   481  		}
   482  	}
   483  
   484  	return nil
   485  }
   486  
   487  func (s *seed20) EssentialSnaps() []*Snap {
   488  	return s.snaps[:s.essentialSnapsNum]
   489  }
   490  
   491  func (s *seed20) ModeSnaps(mode string) ([]*Snap, error) {
   492  	snaps := s.snaps[s.essentialSnapsNum:]
   493  	modes := s.modes[s.essentialSnapsNum:]
   494  	nGuess := len(snaps)
   495  	ephemeral := mode != "run"
   496  	if ephemeral {
   497  		nGuess /= 2
   498  	}
   499  	res := make([]*Snap, 0, nGuess)
   500  	for i, snap := range snaps {
   501  		if !strutil.ListContains(modes[i], mode) {
   502  			if !ephemeral || !strutil.ListContains(modes[i], "ephemeral") {
   503  				continue
   504  			}
   505  		}
   506  		res = append(res, snap)
   507  	}
   508  	return res, nil
   509  }