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