gitee.com/mysnapcore/mysnapd@v0.1.0/snap/info.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2022 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 snap
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net/url"
    27  	"os"
    28  	"path/filepath"
    29  	"sort"
    30  	"strings"
    31  	"time"
    32  
    33  	"gitee.com/mysnapcore/mysnapd/dirs"
    34  	"gitee.com/mysnapcore/mysnapd/metautil"
    35  	"gitee.com/mysnapcore/mysnapd/osutil"
    36  	"gitee.com/mysnapcore/mysnapd/osutil/sys"
    37  	"gitee.com/mysnapcore/mysnapd/snap/naming"
    38  	"gitee.com/mysnapcore/mysnapd/strutil"
    39  	"gitee.com/mysnapcore/mysnapd/timeout"
    40  )
    41  
    42  // PlaceInfo offers all the information about where a snap and its data are
    43  // located and exposed in the filesystem.
    44  type PlaceInfo interface {
    45  	// InstanceName returns the name of the snap decorated with instance
    46  	// key, if any.
    47  	InstanceName() string
    48  
    49  	// SnapName returns the name of the snap.
    50  	SnapName() string
    51  
    52  	// SnapRevision returns the revision of the snap.
    53  	SnapRevision() Revision
    54  
    55  	// Filename returns the name of the snap with the revision
    56  	// number, as used on the filesystem.
    57  	Filename() string
    58  
    59  	// MountDir returns the base directory of the snap.
    60  	MountDir() string
    61  
    62  	// MountFile returns the path where the snap file that is mounted is
    63  	// installed.
    64  	MountFile() string
    65  
    66  	// HooksDir returns the directory containing the snap's hooks.
    67  	HooksDir() string
    68  
    69  	// DataDir returns the data directory of the snap.
    70  	DataDir() string
    71  
    72  	// UserDataDir returns the per user data directory of the snap.
    73  	UserDataDir(home string, opts *dirs.SnapDirOptions) string
    74  
    75  	// CommonDataDir returns the data directory common across revisions of the
    76  	// snap.
    77  	CommonDataDir() string
    78  
    79  	// CommonDataSaveDir returns the save data directory common across revisions
    80  	// of the snap.
    81  	CommonDataSaveDir() string
    82  
    83  	// UserCommonDataDir returns the per user data directory common across
    84  	// revisions of the snap.
    85  	UserCommonDataDir(home string, opts *dirs.SnapDirOptions) string
    86  
    87  	// UserXdgRuntimeDir returns the per user XDG_RUNTIME_DIR directory
    88  	UserXdgRuntimeDir(userID sys.UserID) string
    89  
    90  	// DataHomeDir returns a glob that matches all per user data directories
    91  	// of a snap.
    92  	DataHomeDir(opts *dirs.SnapDirOptions) string
    93  
    94  	// CommonDataHomeDir returns a glob that matches all per user data
    95  	// directories common across revisions of the snap.
    96  	CommonDataHomeDir(opts *dirs.SnapDirOptions) string
    97  
    98  	// XdgRuntimeDirs returns a glob that matches all XDG_RUNTIME_DIR
    99  	// directories for all users of the snap.
   100  	XdgRuntimeDirs() string
   101  
   102  	// UserExposedHomeDir returns the snap's new home directory under ~/Snap.
   103  	UserExposedHomeDir(home string) string
   104  }
   105  
   106  // MinimalPlaceInfo returns a PlaceInfo with just the location information for a
   107  // snap of the given name and revision.
   108  func MinimalPlaceInfo(name string, revision Revision) PlaceInfo {
   109  	storeName, instanceKey := SplitInstanceName(name)
   110  	return &Info{SideInfo: SideInfo{RealName: storeName, Revision: revision}, InstanceKey: instanceKey}
   111  }
   112  
   113  // ParsePlaceInfoFromSnapFileName returns a PlaceInfo with just the location
   114  // information for a snap of file name, failing if the snap file name is invalid
   115  // This explicitly does not support filenames with instance names in them
   116  func ParsePlaceInfoFromSnapFileName(sn string) (PlaceInfo, error) {
   117  	if sn == "" {
   118  		return nil, fmt.Errorf("empty snap file name")
   119  	}
   120  	if strings.Count(sn, "_") > 1 {
   121  		// too many "_", probably has an instance key in the filename like in
   122  		// snap-name_key_23.snap
   123  		return nil, fmt.Errorf("too many '_' in snap file name")
   124  	}
   125  	idx := strings.IndexByte(sn, '_')
   126  	switch {
   127  	case idx < 0:
   128  		return nil, fmt.Errorf("snap file name %q has invalid format (missing '_')", sn)
   129  	case idx == 0:
   130  		return nil, fmt.Errorf("snap file name %q has invalid format (no snap name before '_')", sn)
   131  	}
   132  	// ensure that _ is not the last element
   133  	name := sn[:idx]
   134  	revnoNSuffix := sn[idx+1:]
   135  	rev, err := ParseRevision(strings.TrimSuffix(revnoNSuffix, ".snap"))
   136  	if err != nil {
   137  		return nil, fmt.Errorf("cannot parse revision in snap file name %q: %v", sn, err)
   138  	}
   139  	return &Info{SideInfo: SideInfo{RealName: name, Revision: rev}}, nil
   140  }
   141  
   142  // BaseDir returns the system level directory of given snap.
   143  func BaseDir(name string) string {
   144  	return filepath.Join(dirs.SnapMountDir, name)
   145  }
   146  
   147  // MountDir returns the base directory where it gets mounted of the snap with
   148  // the given name and revision.
   149  func MountDir(name string, revision Revision) string {
   150  	return filepath.Join(BaseDir(name), revision.String())
   151  }
   152  
   153  // MountFile returns the path where the snap file that is mounted is installed.
   154  func MountFile(name string, revision Revision) string {
   155  	return filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_%s.snap", name, revision))
   156  }
   157  
   158  // ScopedSecurityTag returns the snap-specific, scope specific, security tag.
   159  func ScopedSecurityTag(snapName, scopeName, suffix string) string {
   160  	return fmt.Sprintf("snap.%s.%s.%s", snapName, scopeName, suffix)
   161  }
   162  
   163  // SecurityTag returns the snap-specific security tag.
   164  func SecurityTag(snapName string) string {
   165  	return fmt.Sprintf("snap.%s", snapName)
   166  }
   167  
   168  // AppSecurityTag returns the application-specific security tag.
   169  func AppSecurityTag(snapName, appName string) string {
   170  	return fmt.Sprintf("%s.%s", SecurityTag(snapName), appName)
   171  }
   172  
   173  // HookSecurityTag returns the hook-specific security tag.
   174  func HookSecurityTag(snapName, hookName string) string {
   175  	return ScopedSecurityTag(snapName, "hook", hookName)
   176  }
   177  
   178  // NoneSecurityTag returns the security tag for interfaces that
   179  // are not associated to an app or hook in the snap.
   180  func NoneSecurityTag(snapName, uniqueName string) string {
   181  	return ScopedSecurityTag(snapName, "none", uniqueName)
   182  }
   183  
   184  // BaseDataDir returns the base directory for snap data locations.
   185  func BaseDataDir(name string) string {
   186  	return filepath.Join(dirs.SnapDataDir, name)
   187  }
   188  
   189  // DataDir returns the data directory for given snap name and revision. The name
   190  // can be
   191  // either a snap name or snap instance name.
   192  func DataDir(name string, revision Revision) string {
   193  	return filepath.Join(BaseDataDir(name), revision.String())
   194  }
   195  
   196  // CommonDataSaveDir returns a core-specific save directory meant to provide access
   197  // to a per-snap storage that is preserved across factory reset.
   198  func CommonDataSaveDir(name string) string {
   199  	return filepath.Join(dirs.SnapDataSaveDir, name)
   200  }
   201  
   202  // CommonDataDir returns the common data directory for given snap name. The name
   203  // can be either a snap name or snap instance name.
   204  func CommonDataDir(name string) string {
   205  	return filepath.Join(dirs.SnapDataDir, name, "common")
   206  }
   207  
   208  // HooksDir returns the directory containing the snap's hooks for given snap
   209  // name. The name can be either a snap name or snap instance name.
   210  func HooksDir(name string, revision Revision) string {
   211  	return filepath.Join(MountDir(name, revision), "meta", "hooks")
   212  }
   213  
   214  func snapDataDir(opts *dirs.SnapDirOptions) string {
   215  	if opts == nil {
   216  		opts = &dirs.SnapDirOptions{}
   217  	}
   218  
   219  	if opts.HiddenSnapDataDir {
   220  		return dirs.HiddenSnapDataHomeDir
   221  	}
   222  
   223  	return dirs.UserHomeSnapDir
   224  }
   225  
   226  // UserDataDir returns the user-specific data directory for given snap name. The
   227  // name can be either a snap name or snap instance name.
   228  func UserDataDir(home string, name string, revision Revision, opts *dirs.SnapDirOptions) string {
   229  	return filepath.Join(home, snapDataDir(opts), name, revision.String())
   230  }
   231  
   232  // UserCommonDataDir returns the user-specific common data directory for given
   233  // snap name. The name can be either a snap name or snap instance name.
   234  func UserCommonDataDir(home string, name string, opts *dirs.SnapDirOptions) string {
   235  	return filepath.Join(home, snapDataDir(opts), name, "common")
   236  }
   237  
   238  // UserSnapDir returns the user-specific directory for given
   239  // snap name. The name can be either a snap name or snap instance name.
   240  func UserSnapDir(home string, name string, opts *dirs.SnapDirOptions) string {
   241  	return filepath.Join(home, snapDataDir(opts), name)
   242  }
   243  
   244  // UserExposedHomeDir returns the snap's directory in the exposed home dir.
   245  func UserExposedHomeDir(home string, snapName string) string {
   246  	return filepath.Join(home, dirs.ExposedSnapHomeDir, snapName)
   247  }
   248  
   249  // UserXdgRuntimeDir returns the user-specific XDG_RUNTIME_DIR directory for
   250  // given snap name. The name can be either a snap name or snap instance name.
   251  func UserXdgRuntimeDir(euid sys.UserID, name string) string {
   252  	return filepath.Join(dirs.XdgRuntimeDirBase, fmt.Sprintf("%d/snap.%s", euid, name))
   253  }
   254  
   255  // SnapDir returns the user-specific snap directory.
   256  func SnapDir(home string, opts *dirs.SnapDirOptions) string {
   257  	return filepath.Join(home, snapDataDir(opts))
   258  }
   259  
   260  // SideInfo holds snap metadata that is crucial for the tracking of
   261  // snaps and for the working of the system offline and which is not
   262  // included in snap.yaml or for which the store is the canonical
   263  // source overriding snap.yaml content.
   264  //
   265  // It can be marshalled and will be stored in the system state for
   266  // each currently installed snap revision so it needs to be evolved
   267  // carefully.
   268  //
   269  // Information that can be taken directly from snap.yaml or that comes
   270  // from the store but is not required for working offline should not
   271  // end up in SideInfo.
   272  type SideInfo struct {
   273  	RealName    string              `yaml:"name,omitempty" json:"name,omitempty"`
   274  	SnapID      string              `yaml:"snap-id" json:"snap-id"`
   275  	Revision    Revision            `yaml:"revision" json:"revision"`
   276  	Channel     string              `yaml:"channel,omitempty" json:"channel,omitempty"`
   277  	EditedLinks map[string][]string `yaml:"links,omitempty" json:"links,omitempty"`
   278  	// subsumed by EditedLinks, by need to set for if we revert
   279  	// to old snapd
   280  	LegacyEditedContact string `yaml:"contact,omitempty" json:"contact,omitempty"`
   281  	EditedTitle         string `yaml:"title,omitempty" json:"title,omitempty"`
   282  	EditedSummary       string `yaml:"summary,omitempty" json:"summary,omitempty"`
   283  	EditedDescription   string `yaml:"description,omitempty" json:"description,omitempty"`
   284  	Private             bool   `yaml:"private,omitempty" json:"private,omitempty"`
   285  	Paid                bool   `yaml:"paid,omitempty" json:"paid,omitempty"`
   286  }
   287  
   288  // Info provides information about snaps.
   289  type Info struct {
   290  	SuggestedName string
   291  	InstanceKey   string
   292  	Version       string
   293  	SnapType      Type
   294  	Architectures []string
   295  	Assumes       []string
   296  
   297  	OriginalTitle       string
   298  	OriginalSummary     string
   299  	OriginalDescription string
   300  
   301  	SnapProvenance string
   302  
   303  	Environment strutil.OrderedMap
   304  
   305  	LicenseAgreement string
   306  	LicenseVersion   string
   307  	License          string
   308  	Epoch            Epoch
   309  	Base             string
   310  	Confinement      ConfinementType
   311  	Apps             map[string]*AppInfo
   312  	LegacyAliases    map[string]*AppInfo // FIXME: eventually drop this
   313  	Hooks            map[string]*HookInfo
   314  	Plugs            map[string]*PlugInfo
   315  	Slots            map[string]*SlotInfo
   316  
   317  	// Plugs or slots with issues (they are not included in Plugs or Slots)
   318  	BadInterfaces map[string]string // slot or plug => message
   319  
   320  	// The information in all the remaining fields is not sourced from the snap
   321  	// blob itself.
   322  	SideInfo
   323  
   324  	// Broken marks whether the snap is broken and the reason.
   325  	Broken string
   326  
   327  	// The information in these fields is ephemeral, available only from the
   328  	// store or when read from a snap file.
   329  	DownloadInfo
   330  
   331  	Prices  map[string]float64
   332  	MustBuy bool
   333  
   334  	Publisher StoreAccount
   335  
   336  	Media MediaInfos
   337  
   338  	// subsumed by EditedLinks but needed to handle information
   339  	// stored by old snapd
   340  	LegacyWebsite string
   341  
   342  	StoreURL string
   343  
   344  	// The flattended channel map with $track/$risk
   345  	Channels map[string]*ChannelSnapInfo
   346  
   347  	// The ordered list of tracks that contain channels
   348  	Tracks []string
   349  
   350  	Layout map[string]*Layout
   351  
   352  	// The list of common-ids from all apps of the snap
   353  	CommonIDs []string
   354  
   355  	// List of system users (usernames) this snap may use. The group of the same
   356  	// name must also exist.
   357  	SystemUsernames map[string]*SystemUsernameInfo
   358  
   359  	// OriginalLinks is a map links keys to link lists
   360  	OriginalLinks map[string][]string
   361  }
   362  
   363  // StoreAccount holds information about a store account, for example of snap
   364  // publisher.
   365  type StoreAccount struct {
   366  	ID          string `json:"id"`
   367  	Username    string `json:"username"`
   368  	DisplayName string `json:"display-name"`
   369  	Validation  string `json:"validation,omitempty"`
   370  }
   371  
   372  // Layout describes a single element of the layout section.
   373  type Layout struct {
   374  	Snap *Info
   375  
   376  	Path     string      `json:"path"`
   377  	Bind     string      `json:"bind,omitempty"`
   378  	BindFile string      `json:"bind-file,omitempty"`
   379  	Type     string      `json:"type,omitempty"`
   380  	User     string      `json:"user,omitempty"`
   381  	Group    string      `json:"group,omitempty"`
   382  	Mode     os.FileMode `json:"mode,omitempty"`
   383  	Symlink  string      `json:"symlink,omitempty"`
   384  }
   385  
   386  // String returns a simple textual representation of a layout.
   387  func (l *Layout) String() string {
   388  	var buf bytes.Buffer
   389  	fmt.Fprintf(&buf, "%s: ", l.Path)
   390  	switch {
   391  	case l.Bind != "":
   392  		fmt.Fprintf(&buf, "bind %s", l.Bind)
   393  	case l.BindFile != "":
   394  		fmt.Fprintf(&buf, "bind-file %s", l.BindFile)
   395  	case l.Symlink != "":
   396  		fmt.Fprintf(&buf, "symlink %s", l.Symlink)
   397  	case l.Type != "":
   398  		fmt.Fprintf(&buf, "type %s", l.Type)
   399  	default:
   400  		fmt.Fprintf(&buf, "???")
   401  	}
   402  	if l.User != "root" && l.User != "" {
   403  		fmt.Fprintf(&buf, ", user: %s", l.User)
   404  	}
   405  	if l.Group != "root" && l.Group != "" {
   406  		fmt.Fprintf(&buf, ", group: %s", l.Group)
   407  	}
   408  	if l.Mode != 0755 {
   409  		fmt.Fprintf(&buf, ", mode: %#o", l.Mode)
   410  	}
   411  	return buf.String()
   412  }
   413  
   414  // ChannelSnapInfo is the minimum information that can be used to clearly
   415  // distinguish different revisions of the same snap.
   416  type ChannelSnapInfo struct {
   417  	Revision    Revision        `json:"revision"`
   418  	Confinement ConfinementType `json:"confinement"`
   419  	Version     string          `json:"version"`
   420  	Channel     string          `json:"channel"`
   421  	Epoch       Epoch           `json:"epoch"`
   422  	Size        int64           `json:"size"`
   423  	ReleasedAt  time.Time       `json:"released-at"`
   424  }
   425  
   426  // Provenance returns the provenance of the snap, this is a label set
   427  // e.g to distinguish snaps that are not expected to be processed by the global
   428  // store. Constraints on this value are used to allow for delegated
   429  // snap-revision signing.
   430  // This returns naming.DefaultProvenance if no value is set explicitly
   431  // in the snap metadata.
   432  func (s *Info) Provenance() string {
   433  	if s.SnapProvenance == "" {
   434  		return naming.DefaultProvenance
   435  	}
   436  	return s.SnapProvenance
   437  }
   438  
   439  // InstanceName returns the blessed name of the snap decorated with instance
   440  // key, if any.
   441  func (s *Info) InstanceName() string {
   442  	return InstanceName(s.SnapName(), s.InstanceKey)
   443  }
   444  
   445  // SnapName returns the global blessed name of the snap.
   446  func (s *Info) SnapName() string {
   447  	if s.RealName != "" {
   448  		return s.RealName
   449  	}
   450  	return s.SuggestedName
   451  }
   452  
   453  // Filename returns the name of the snap with the revision number,
   454  // as used on the filesystem. This is the equivalent of
   455  // filepath.Base(s.MountFile()).
   456  func (s *Info) Filename() string {
   457  	return filepath.Base(s.MountFile())
   458  }
   459  
   460  // SnapRevision returns the revision of the snap.
   461  func (s *Info) SnapRevision() Revision {
   462  	return s.Revision
   463  }
   464  
   465  // ID implements naming.SnapRef.
   466  func (s *Info) ID() string {
   467  	return s.SnapID
   468  }
   469  
   470  var _ naming.SnapRef = (*Info)(nil)
   471  
   472  // Title returns the blessed title for the snap.
   473  func (s *Info) Title() string {
   474  	if s.EditedTitle != "" {
   475  		return s.EditedTitle
   476  	}
   477  	return s.OriginalTitle
   478  }
   479  
   480  // Summary returns the blessed summary for the snap.
   481  func (s *Info) Summary() string {
   482  	if s.EditedSummary != "" {
   483  		return s.EditedSummary
   484  	}
   485  	return s.OriginalSummary
   486  }
   487  
   488  // Description returns the blessed description for the snap.
   489  func (s *Info) Description() string {
   490  	if s.EditedDescription != "" {
   491  		return s.EditedDescription
   492  	}
   493  	return s.OriginalDescription
   494  }
   495  
   496  // Links returns the blessed set of snap-related links.
   497  func (s *Info) Links() map[string][]string {
   498  	if s.EditedLinks != nil {
   499  		// coming from thes store, assumed normalized
   500  		return s.EditedLinks
   501  	}
   502  	return s.normalizedOriginalLinks()
   503  }
   504  
   505  func (s *Info) normalizedOriginalLinks() map[string][]string {
   506  	res := make(map[string][]string, len(s.OriginalLinks))
   507  	addLink := func(k, v string) {
   508  		if v == "" {
   509  			return
   510  		}
   511  		u, err := url.Parse(v)
   512  		if err != nil {
   513  			// shouldn't happen if Validate succeeded but be robust
   514  			return
   515  		}
   516  		// assume email if no scheme
   517  		// Validate enforces the presence of @
   518  		if u.Scheme == "" {
   519  			v = "mailto:" + v
   520  		}
   521  		if strutil.ListContains(res[k], v) {
   522  			return
   523  		}
   524  		res[k] = append(res[k], v)
   525  	}
   526  	addLink("contact", s.LegacyEditedContact)
   527  	addLink("website", s.LegacyWebsite)
   528  	for k, links := range s.OriginalLinks {
   529  		for _, v := range links {
   530  			addLink(k, v)
   531  		}
   532  	}
   533  	if len(res) == 0 {
   534  		return nil
   535  	}
   536  	return res
   537  }
   538  
   539  // Contact returns the blessed contact information for the snap.
   540  func (s *Info) Contact() string {
   541  	contacts := s.Links()["contact"]
   542  	if len(contacts) > 0 {
   543  		return contacts[0]
   544  	}
   545  	return ""
   546  }
   547  
   548  // Website returns the blessed website information for the snap.
   549  func (s *Info) Website() string {
   550  	websites := s.Links()["website"]
   551  	if len(websites) > 0 {
   552  		return websites[0]
   553  	}
   554  	return ""
   555  }
   556  
   557  // Type returns the type of the snap, including additional snap ID check
   558  // for the legacy snapd snap definitions.
   559  func (s *Info) Type() Type {
   560  	if s.SnapType == TypeApp && IsSnapd(s.SnapID) {
   561  		return TypeSnapd
   562  	}
   563  	return s.SnapType
   564  }
   565  
   566  // MountDir returns the base directory of the snap where it gets mounted.
   567  func (s *Info) MountDir() string {
   568  	return MountDir(s.InstanceName(), s.Revision)
   569  }
   570  
   571  // MountFile returns the path where the snap file that is mounted is installed.
   572  func (s *Info) MountFile() string {
   573  	return MountFile(s.InstanceName(), s.Revision)
   574  }
   575  
   576  // HooksDir returns the directory containing the snap's hooks.
   577  func (s *Info) HooksDir() string {
   578  	return HooksDir(s.InstanceName(), s.Revision)
   579  }
   580  
   581  // DataDir returns the data directory of the snap.
   582  func (s *Info) DataDir() string {
   583  	return DataDir(s.InstanceName(), s.Revision)
   584  }
   585  
   586  // UserDataDir returns the user-specific data directory of the snap.
   587  func (s *Info) UserDataDir(home string, opts *dirs.SnapDirOptions) string {
   588  	return UserDataDir(home, s.InstanceName(), s.Revision, opts)
   589  }
   590  
   591  // UserCommonDataDir returns the user-specific data directory common across
   592  // revision of the snap.
   593  func (s *Info) UserCommonDataDir(home string, opts *dirs.SnapDirOptions) string {
   594  	return UserCommonDataDir(home, s.InstanceName(), opts)
   595  }
   596  
   597  // UserExposedHomeDir returns the new upper-case snap directory in the user home.
   598  func (s *Info) UserExposedHomeDir(home string) string {
   599  	return filepath.Join(home, dirs.ExposedSnapHomeDir, s.InstanceName())
   600  }
   601  
   602  // CommonDataDir returns the data directory common across revisions of the snap.
   603  func (s *Info) CommonDataDir() string {
   604  	return CommonDataDir(s.InstanceName())
   605  }
   606  
   607  // CommonDataSaveDir returns the save data directory common across revisions of the snap.
   608  func (s *Info) CommonDataSaveDir() string {
   609  	return CommonDataSaveDir(s.InstanceName())
   610  }
   611  
   612  // DataHomeGlob returns the globbing expression for the snap directories in use
   613  func DataHomeGlob(opts *dirs.SnapDirOptions) string {
   614  	if opts == nil {
   615  		opts = &dirs.SnapDirOptions{}
   616  	}
   617  
   618  	if opts.HiddenSnapDataDir {
   619  		return dirs.HiddenSnapDataHomeGlob
   620  	}
   621  
   622  	return dirs.SnapDataHomeGlob
   623  }
   624  
   625  // DataHomeDir returns the per user data directory of the snap.
   626  func (s *Info) DataHomeDir(opts *dirs.SnapDirOptions) string {
   627  	return filepath.Join(DataHomeGlob(opts), s.InstanceName(), s.Revision.String())
   628  }
   629  
   630  // CommonDataHomeDir returns the per user data directory common across revisions
   631  // of the snap.
   632  func (s *Info) CommonDataHomeDir(opts *dirs.SnapDirOptions) string {
   633  	return filepath.Join(DataHomeGlob(opts), s.InstanceName(), "common")
   634  }
   635  
   636  // UserXdgRuntimeDir returns the XDG_RUNTIME_DIR directory of the snap for a
   637  // particular user.
   638  func (s *Info) UserXdgRuntimeDir(euid sys.UserID) string {
   639  	return UserXdgRuntimeDir(euid, s.InstanceName())
   640  }
   641  
   642  // XdgRuntimeDirs returns the XDG_RUNTIME_DIR directories for all users of the
   643  // snap.
   644  func (s *Info) XdgRuntimeDirs() string {
   645  	return filepath.Join(dirs.XdgRuntimeDirGlob, fmt.Sprintf("snap.%s", s.InstanceName()))
   646  }
   647  
   648  // NeedsDevMode returns whether the snap needs devmode.
   649  func (s *Info) NeedsDevMode() bool {
   650  	return s.Confinement == DevModeConfinement
   651  }
   652  
   653  // NeedsClassic  returns whether the snap needs classic confinement consent.
   654  func (s *Info) NeedsClassic() bool {
   655  	return s.Confinement == ClassicConfinement
   656  }
   657  
   658  // Services returns a list of the apps that have "daemon" set.
   659  func (s *Info) Services() []*AppInfo {
   660  	svcs := make([]*AppInfo, 0, len(s.Apps))
   661  	for _, app := range s.Apps {
   662  		if !app.IsService() {
   663  			continue
   664  		}
   665  		svcs = append(svcs, app)
   666  	}
   667  
   668  	return svcs
   669  }
   670  
   671  // ExpandSnapVariables resolves $SNAP, $SNAP_DATA and $SNAP_COMMON inside the
   672  // snap's mount namespace.
   673  func (s *Info) ExpandSnapVariables(path string) string {
   674  	return os.Expand(path, func(v string) string {
   675  		switch v {
   676  		case "SNAP":
   677  			// NOTE: We use dirs.CoreSnapMountDir here as the path used will be
   678  			// always inside the mount namespace snap-confine creates and there
   679  			// we will always have a /snap directory available regardless if the
   680  			// system we're running on supports this or not.
   681  			return filepath.Join(dirs.CoreSnapMountDir, s.SnapName(), s.Revision.String())
   682  		case "SNAP_DATA":
   683  			return DataDir(s.SnapName(), s.Revision)
   684  		case "SNAP_COMMON":
   685  			return CommonDataDir(s.SnapName())
   686  		}
   687  		return ""
   688  	})
   689  }
   690  
   691  // InstallDate returns the "install date" of the snap.
   692  //
   693  // If the snap is not active, it'll return a zero time; otherwise
   694  // it'll return the modtime of the "current" symlink. Sneaky.
   695  func (s *Info) InstallDate() time.Time {
   696  	dir, rev := filepath.Split(s.MountDir())
   697  	cur := filepath.Join(dir, "current")
   698  	tag, err := os.Readlink(cur)
   699  	if err == nil && tag == rev {
   700  		if st, err := os.Lstat(cur); err == nil {
   701  			return st.ModTime()
   702  		}
   703  	}
   704  	return time.Time{}
   705  }
   706  
   707  // IsActive returns whether this snap revision is active.
   708  func (s *Info) IsActive() bool {
   709  	dir, rev := filepath.Split(s.MountDir())
   710  	cur := filepath.Join(dir, "current")
   711  	tag, err := os.Readlink(cur)
   712  	return err == nil && tag == rev
   713  }
   714  
   715  // BadInterfacesSummary returns a summary of the problems of bad plugs
   716  // and slots in the snap.
   717  func BadInterfacesSummary(snapInfo *Info) string {
   718  	inverted := make(map[string][]string)
   719  	for name, reason := range snapInfo.BadInterfaces {
   720  		inverted[reason] = append(inverted[reason], name)
   721  	}
   722  	var buf bytes.Buffer
   723  	fmt.Fprintf(&buf, "snap %q has bad plugs or slots: ", snapInfo.InstanceName())
   724  	reasons := make([]string, 0, len(inverted))
   725  	for reason := range inverted {
   726  		reasons = append(reasons, reason)
   727  	}
   728  	sort.Strings(reasons)
   729  	for _, reason := range reasons {
   730  		names := inverted[reason]
   731  		sort.Strings(names)
   732  		for i, name := range names {
   733  			if i > 0 {
   734  				buf.WriteString(", ")
   735  			}
   736  			buf.WriteString(name)
   737  		}
   738  		fmt.Fprintf(&buf, " (%s); ", reason)
   739  	}
   740  	return strings.TrimSuffix(buf.String(), "; ")
   741  }
   742  
   743  // DesktopPrefix returns the prefix string for the desktop files that
   744  // belongs to the given snapInstance. We need to do something custom
   745  // here because a) we need to be compatible with the world before we had
   746  // parallel installs b) we can't just use the usual "_" parallel installs
   747  // separator because that is already used as the separator between snap
   748  // and desktop filename.
   749  func (s *Info) DesktopPrefix() string {
   750  	if s.InstanceKey == "" {
   751  		return s.SnapName()
   752  	}
   753  	// we cannot use the usual "_" separator because that is also used
   754  	// to separate "$snap_$desktopfile"
   755  	return fmt.Sprintf("%s+%s", s.SnapName(), s.InstanceKey)
   756  }
   757  
   758  // DownloadInfo contains the information to download a snap.
   759  // It can be marshalled.
   760  type DownloadInfo struct {
   761  	DownloadURL string `json:"download-url,omitempty"`
   762  
   763  	Size     int64  `json:"size,omitempty"`
   764  	Sha3_384 string `json:"sha3-384,omitempty"`
   765  
   766  	// The server can include information about available deltas for a given
   767  	// snap at a specific revision during refresh. Currently during refresh the
   768  	// server will provide single matching deltas only, from the clients
   769  	// revision to the target revision when available, per requested format.
   770  	Deltas []DeltaInfo `json:"deltas,omitempty"`
   771  }
   772  
   773  // DeltaInfo contains the information to download a delta
   774  // from one revision to another.
   775  type DeltaInfo struct {
   776  	FromRevision int    `json:"from-revision,omitempty"`
   777  	ToRevision   int    `json:"to-revision,omitempty"`
   778  	Format       string `json:"format,omitempty"`
   779  	DownloadURL  string `json:"download-url,omitempty"`
   780  	Size         int64  `json:"size,omitempty"`
   781  	Sha3_384     string `json:"sha3-384,omitempty"`
   782  }
   783  
   784  // check that Info is a PlaceInfo
   785  var _ PlaceInfo = (*Info)(nil)
   786  
   787  type AttributeNotFoundError struct{ Err error }
   788  
   789  func (e AttributeNotFoundError) Error() string {
   790  	return e.Err.Error()
   791  }
   792  
   793  func (e AttributeNotFoundError) Is(target error) bool {
   794  	_, ok := target.(AttributeNotFoundError)
   795  	return ok
   796  }
   797  
   798  // PlugInfo provides information about a plug.
   799  type PlugInfo struct {
   800  	Snap *Info
   801  
   802  	Name      string
   803  	Interface string
   804  	Attrs     map[string]interface{}
   805  	Label     string
   806  	Apps      map[string]*AppInfo
   807  	Hooks     map[string]*HookInfo
   808  }
   809  
   810  func lookupAttr(attrs map[string]interface{}, path string) (interface{}, bool) {
   811  	var v interface{}
   812  	comps := strings.FieldsFunc(path, func(r rune) bool { return r == '.' })
   813  	if len(comps) == 0 {
   814  		return nil, false
   815  	}
   816  	v = attrs
   817  	for _, comp := range comps {
   818  		m, ok := v.(map[string]interface{})
   819  		if !ok {
   820  			return nil, false
   821  		}
   822  		v, ok = m[comp]
   823  		if !ok {
   824  			return nil, false
   825  		}
   826  	}
   827  
   828  	return v, true
   829  }
   830  
   831  func getAttribute(snapName string, ifaceName string, attrs map[string]interface{}, key string, val interface{}) error {
   832  	v, ok := lookupAttr(attrs, key)
   833  	if !ok {
   834  		return AttributeNotFoundError{fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, key, ifaceName)}
   835  	}
   836  
   837  	return metautil.SetValueFromAttribute(snapName, ifaceName, key, v, val)
   838  }
   839  
   840  func (plug *PlugInfo) Attr(key string, val interface{}) error {
   841  	return getAttribute(plug.Snap.InstanceName(), plug.Interface, plug.Attrs, key, val)
   842  }
   843  
   844  func (plug *PlugInfo) Lookup(key string) (interface{}, bool) {
   845  	return lookupAttr(plug.Attrs, key)
   846  }
   847  
   848  // SecurityTags returns security tags associated with a given plug.
   849  func (plug *PlugInfo) SecurityTags() []string {
   850  	tags := make([]string, 0, len(plug.Apps)+len(plug.Hooks))
   851  	for _, app := range plug.Apps {
   852  		tags = append(tags, app.SecurityTag())
   853  	}
   854  	for _, hook := range plug.Hooks {
   855  		tags = append(tags, hook.SecurityTag())
   856  	}
   857  	sort.Strings(tags)
   858  	return tags
   859  }
   860  
   861  // String returns the representation of the plug as snap:plug string.
   862  func (plug *PlugInfo) String() string {
   863  	return fmt.Sprintf("%s:%s", plug.Snap.InstanceName(), plug.Name)
   864  }
   865  
   866  func (slot *SlotInfo) Attr(key string, val interface{}) error {
   867  	return getAttribute(slot.Snap.InstanceName(), slot.Interface, slot.Attrs, key, val)
   868  }
   869  
   870  func (slot *SlotInfo) Lookup(key string) (interface{}, bool) {
   871  	return lookupAttr(slot.Attrs, key)
   872  }
   873  
   874  // SecurityTags returns security tags associated with a given slot.
   875  func (slot *SlotInfo) SecurityTags() []string {
   876  	tags := make([]string, 0, len(slot.Apps))
   877  	for _, app := range slot.Apps {
   878  		tags = append(tags, app.SecurityTag())
   879  	}
   880  	for _, hook := range slot.Hooks {
   881  		tags = append(tags, hook.SecurityTag())
   882  	}
   883  	sort.Strings(tags)
   884  	return tags
   885  }
   886  
   887  // String returns the representation of the slot as snap:slot string.
   888  func (slot *SlotInfo) String() string {
   889  	return fmt.Sprintf("%s:%s", slot.Snap.InstanceName(), slot.Name)
   890  }
   891  
   892  func gatherDefaultContentProvider(providerSnapsToContentTag map[string][]string, plug *PlugInfo) {
   893  	if plug.Interface == "content" {
   894  		var dprovider string
   895  		if err := plug.Attr("default-provider", &dprovider); err == nil && dprovider != "" {
   896  			// usage can be "snap:slot" but slot
   897  			// is ignored/unused
   898  			name := strings.Split(dprovider, ":")[0]
   899  			var contentTag string
   900  			plug.Attr("content", &contentTag)
   901  			tags := providerSnapsToContentTag[name]
   902  			if tags == nil {
   903  				tags = []string{contentTag}
   904  			} else {
   905  				if !strutil.SortedListContains(tags, contentTag) {
   906  					tags = append(tags, contentTag)
   907  					sort.Strings(tags)
   908  				}
   909  			}
   910  			providerSnapsToContentTag[name] = tags
   911  		}
   912  	}
   913  }
   914  
   915  // DefaultContentProviders returns the set of default provider snaps
   916  // requested by the given plugs, mapped to their content tags.
   917  func DefaultContentProviders(plugs []*PlugInfo) (providerSnapsToContentTag map[string][]string) {
   918  	providerSnapsToContentTag = make(map[string][]string)
   919  	for _, plug := range plugs {
   920  		gatherDefaultContentProvider(providerSnapsToContentTag, plug)
   921  	}
   922  	return providerSnapsToContentTag
   923  }
   924  
   925  // SlotInfo provides information about a slot.
   926  type SlotInfo struct {
   927  	Snap *Info
   928  
   929  	Name      string
   930  	Interface string
   931  	Attrs     map[string]interface{}
   932  	Label     string
   933  	Apps      map[string]*AppInfo
   934  	Hooks     map[string]*HookInfo
   935  
   936  	// HotplugKey is a unique key built by the slot's interface
   937  	// using properties of a hotplugged device so that the same
   938  	// slot may be made available if the device is reinserted.
   939  	// It's empty for regular slots.
   940  	HotplugKey HotplugKey
   941  }
   942  
   943  // SocketInfo provides information on application sockets.
   944  type SocketInfo struct {
   945  	App *AppInfo
   946  
   947  	Name         string
   948  	ListenStream string
   949  	SocketMode   os.FileMode
   950  }
   951  
   952  // TimerInfo provides information on application timer.
   953  type TimerInfo struct {
   954  	App *AppInfo
   955  
   956  	Timer string
   957  }
   958  
   959  // StopModeType is the type for the "stop-mode:" of a snap app
   960  type StopModeType string
   961  
   962  // KillAll returns if the stop-mode means all processes should be killed
   963  // when the service is stopped or just the main process.
   964  func (st StopModeType) KillAll() bool {
   965  	return string(st) == "" || strings.HasSuffix(string(st), "-all")
   966  }
   967  
   968  // KillSignal returns the signal that should be used to kill the process
   969  // (or an empty string if no signal is needed).
   970  func (st StopModeType) KillSignal() string {
   971  	if st.Validate() != nil || st == "" {
   972  		return ""
   973  	}
   974  	return strings.ToUpper(strings.TrimSuffix(string(st), "-all"))
   975  }
   976  
   977  // Validate ensures that the StopModeType has an valid value.
   978  func (st StopModeType) Validate() error {
   979  	switch st {
   980  	case "", "sigterm", "sigterm-all", "sighup", "sighup-all", "sigusr1", "sigusr1-all", "sigusr2", "sigusr2-all", "sigint", "sigint-all":
   981  		// valid
   982  		return nil
   983  	}
   984  	return fmt.Errorf(`"stop-mode" field contains invalid value %q`, st)
   985  }
   986  
   987  // AppInfo provides information about an app.
   988  type AppInfo struct {
   989  	Snap *Info
   990  
   991  	Name          string
   992  	LegacyAliases []string // FIXME: eventually drop this
   993  	Command       string
   994  	CommandChain  []string
   995  	CommonID      string
   996  
   997  	Daemon          string
   998  	DaemonScope     DaemonScope
   999  	StopTimeout     timeout.Timeout
  1000  	StartTimeout    timeout.Timeout
  1001  	WatchdogTimeout timeout.Timeout
  1002  	StopCommand     string
  1003  	ReloadCommand   string
  1004  	PostStopCommand string
  1005  	RestartCond     RestartCondition
  1006  	RestartDelay    timeout.Timeout
  1007  	Completer       string
  1008  	RefreshMode     string
  1009  	StopMode        StopModeType
  1010  	InstallMode     string
  1011  
  1012  	// TODO: this should go away once we have more plumbing and can change
  1013  	// things vs refactor
  1014  	// https://github.com/snapcore/snapd/pull/794#discussion_r58688496
  1015  	BusName     string
  1016  	ActivatesOn []*SlotInfo
  1017  
  1018  	Plugs   map[string]*PlugInfo
  1019  	Slots   map[string]*SlotInfo
  1020  	Sockets map[string]*SocketInfo
  1021  
  1022  	Environment strutil.OrderedMap
  1023  
  1024  	// list of other service names that this service will start after or
  1025  	// before
  1026  	After  []string
  1027  	Before []string
  1028  
  1029  	Timer *TimerInfo
  1030  
  1031  	Autostart string
  1032  }
  1033  
  1034  // ScreenshotInfo provides information about a screenshot.
  1035  type ScreenshotInfo struct {
  1036  	URL    string `json:"url,omitempty"`
  1037  	Width  int64  `json:"width,omitempty"`
  1038  	Height int64  `json:"height,omitempty"`
  1039  	Note   string `json:"note,omitempty"`
  1040  }
  1041  
  1042  type MediaInfo struct {
  1043  	Type   string `json:"type"`
  1044  	URL    string `json:"url"`
  1045  	Width  int64  `json:"width,omitempty"`
  1046  	Height int64  `json:"height,omitempty"`
  1047  }
  1048  
  1049  type MediaInfos []MediaInfo
  1050  
  1051  func (mis MediaInfos) IconURL() string {
  1052  	for _, mi := range mis {
  1053  		if mi.Type == "icon" {
  1054  			return mi.URL
  1055  		}
  1056  	}
  1057  	return ""
  1058  }
  1059  
  1060  // HookInfo provides information about a hook.
  1061  type HookInfo struct {
  1062  	Snap *Info
  1063  
  1064  	Name  string
  1065  	Plugs map[string]*PlugInfo
  1066  	Slots map[string]*SlotInfo
  1067  
  1068  	Environment  strutil.OrderedMap
  1069  	CommandChain []string
  1070  
  1071  	Explicit bool
  1072  }
  1073  
  1074  // SystemUsernameInfo provides information about a system username (ie, a
  1075  // UNIX user and group with the same name). The scope defines visibility of the
  1076  // username wrt the snap and the system. Defined scopes:
  1077  // - shared    static, snapd-managed user/group shared between host and all
  1078  //             snaps
  1079  // - private   static, snapd-managed user/group private to a particular snap
  1080  //             (currently not implemented)
  1081  // - external  dynamic user/group shared between host and all snaps (currently
  1082  //             not implented)
  1083  type SystemUsernameInfo struct {
  1084  	Name  string
  1085  	Scope string
  1086  	Attrs map[string]interface{}
  1087  }
  1088  
  1089  // File returns the path to the *.socket file
  1090  func (socket *SocketInfo) File() string {
  1091  	return filepath.Join(socket.App.serviceDir(), socket.App.SecurityTag()+"."+socket.Name+".socket")
  1092  }
  1093  
  1094  // File returns the path to the *.timer file
  1095  func (timer *TimerInfo) File() string {
  1096  	return filepath.Join(timer.App.serviceDir(), timer.App.SecurityTag()+".timer")
  1097  }
  1098  
  1099  func (app *AppInfo) String() string {
  1100  	return JoinSnapApp(app.Snap.InstanceName(), app.Name)
  1101  }
  1102  
  1103  // SecurityTag returns application-specific security tag.
  1104  //
  1105  // Security tags are used by various security subsystems as "profile names" and
  1106  // sometimes also as a part of the file name.
  1107  func (app *AppInfo) SecurityTag() string {
  1108  	return AppSecurityTag(app.Snap.InstanceName(), app.Name)
  1109  }
  1110  
  1111  // DesktopFile returns the path to the installed optional desktop file for the
  1112  // application.
  1113  func (app *AppInfo) DesktopFile() string {
  1114  	return filepath.Join(dirs.SnapDesktopFilesDir, fmt.Sprintf("%s_%s.desktop", app.Snap.DesktopPrefix(), app.Name))
  1115  }
  1116  
  1117  // WrapperPath returns the path to wrapper invoking the app binary.
  1118  func (app *AppInfo) WrapperPath() string {
  1119  	return filepath.Join(dirs.SnapBinariesDir, JoinSnapApp(app.Snap.InstanceName(), app.Name))
  1120  }
  1121  
  1122  // CompleterPath returns the path to the completer snippet for the app binary.
  1123  func (app *AppInfo) CompleterPath() string {
  1124  	return filepath.Join(dirs.CompletersDir, JoinSnapApp(app.Snap.InstanceName(), app.Name))
  1125  }
  1126  
  1127  // CompleterPath returns the legacy path to the completer snippet for the app binary.
  1128  func (app *AppInfo) LegacyCompleterPath() string {
  1129  	return filepath.Join(dirs.LegacyCompletersDir, JoinSnapApp(app.Snap.InstanceName(), app.Name))
  1130  }
  1131  
  1132  func (app *AppInfo) launcherCommand(command string) string {
  1133  	if command != "" {
  1134  		command = " " + command
  1135  	}
  1136  	if app.Name == app.Snap.SnapName() {
  1137  		return fmt.Sprintf("/usr/bin/snap run%s %s", command, app.Snap.InstanceName())
  1138  	}
  1139  	return fmt.Sprintf("/usr/bin/snap run%s %s.%s", command, app.Snap.InstanceName(), app.Name)
  1140  }
  1141  
  1142  // LauncherCommand returns the launcher command line to use when invoking the
  1143  // app binary.
  1144  func (app *AppInfo) LauncherCommand() string {
  1145  	if app.Timer != nil {
  1146  		return app.launcherCommand(fmt.Sprintf("--timer=%q", app.Timer.Timer))
  1147  	}
  1148  	return app.launcherCommand("")
  1149  }
  1150  
  1151  // LauncherStopCommand returns the launcher command line to use when invoking
  1152  // the app stop command binary.
  1153  func (app *AppInfo) LauncherStopCommand() string {
  1154  	return app.launcherCommand("--command=stop")
  1155  }
  1156  
  1157  // LauncherReloadCommand returns the launcher command line to use when invoking
  1158  // the app stop command binary.
  1159  func (app *AppInfo) LauncherReloadCommand() string {
  1160  	return app.launcherCommand("--command=reload")
  1161  }
  1162  
  1163  // LauncherPostStopCommand returns the launcher command line to use when
  1164  // invoking the app post-stop command binary.
  1165  func (app *AppInfo) LauncherPostStopCommand() string {
  1166  	return app.launcherCommand("--command=post-stop")
  1167  }
  1168  
  1169  // ServiceName returns the systemd service name for the daemon app.
  1170  func (app *AppInfo) ServiceName() string {
  1171  	return app.SecurityTag() + ".service"
  1172  }
  1173  
  1174  func (app *AppInfo) serviceDir() string {
  1175  	switch app.DaemonScope {
  1176  	case SystemDaemon:
  1177  		return dirs.SnapServicesDir
  1178  	case UserDaemon:
  1179  		return dirs.SnapUserServicesDir
  1180  	default:
  1181  		panic("unknown daemon scope")
  1182  	}
  1183  }
  1184  
  1185  // ServiceFile returns the systemd service file path for the daemon app.
  1186  func (app *AppInfo) ServiceFile() string {
  1187  	return filepath.Join(app.serviceDir(), app.ServiceName())
  1188  }
  1189  
  1190  // IsService returns whether app represents a daemon/service.
  1191  func (app *AppInfo) IsService() bool {
  1192  	return app.Daemon != ""
  1193  }
  1194  
  1195  // EnvChain returns the chain of environment overrides, possibly with
  1196  // expandable $ vars, specific for the app.
  1197  func (app *AppInfo) EnvChain() []osutil.ExpandableEnv {
  1198  	return []osutil.ExpandableEnv{
  1199  		{OrderedMap: &app.Snap.Environment},
  1200  		{OrderedMap: &app.Environment},
  1201  	}
  1202  }
  1203  
  1204  // SecurityTag returns the hook-specific security tag.
  1205  //
  1206  // Security tags are used by various security subsystems as "profile names" and
  1207  // sometimes also as a part of the file name.
  1208  func (hook *HookInfo) SecurityTag() string {
  1209  	return HookSecurityTag(hook.Snap.InstanceName(), hook.Name)
  1210  }
  1211  
  1212  // EnvChain returns the chain of environment overrides, possibly with
  1213  // expandable $ vars, specific for the hook.
  1214  func (hook *HookInfo) EnvChain() []osutil.ExpandableEnv {
  1215  	return []osutil.ExpandableEnv{
  1216  		{OrderedMap: &hook.Snap.Environment},
  1217  		{OrderedMap: &hook.Environment},
  1218  	}
  1219  }
  1220  
  1221  func infoFromSnapYamlWithSideInfo(meta []byte, si *SideInfo, strk *scopedTracker) (*Info, error) {
  1222  	info, err := infoFromSnapYaml(meta, strk)
  1223  	if err != nil {
  1224  		return nil, err
  1225  	}
  1226  
  1227  	if si != nil {
  1228  		info.SideInfo = *si
  1229  	}
  1230  
  1231  	return info, nil
  1232  }
  1233  
  1234  // BrokenSnapError describes an error that refers to a snap that warrants the
  1235  // "broken" note.
  1236  type BrokenSnapError interface {
  1237  	error
  1238  	Broken() string
  1239  }
  1240  
  1241  type NotFoundError struct {
  1242  	Snap     string
  1243  	Revision Revision
  1244  	// Path encodes the path that triggered the not-found error. It may refer to
  1245  	// a file inside the snap or to the snap file itself.
  1246  	Path string
  1247  }
  1248  
  1249  func (e NotFoundError) Error() string {
  1250  	if e.Path != "" {
  1251  		return fmt.Sprintf("cannot find installed snap %q at revision %s: missing file %s", e.Snap, e.Revision, e.Path)
  1252  	}
  1253  	return fmt.Sprintf("cannot find installed snap %q at revision %s", e.Snap, e.Revision)
  1254  }
  1255  
  1256  func (e NotFoundError) Broken() string {
  1257  	return e.Error()
  1258  }
  1259  
  1260  type invalidMetaError struct {
  1261  	Snap     string
  1262  	Revision Revision
  1263  	Msg      string
  1264  }
  1265  
  1266  func (e invalidMetaError) Error() string {
  1267  	return fmt.Sprintf("cannot use installed snap %q at revision %s: %s", e.Snap, e.Revision, e.Msg)
  1268  }
  1269  
  1270  func (e invalidMetaError) Broken() string {
  1271  	return e.Error()
  1272  }
  1273  
  1274  func MockSanitizePlugsSlots(f func(snapInfo *Info)) (restore func()) {
  1275  	old := SanitizePlugsSlots
  1276  	SanitizePlugsSlots = f
  1277  	return func() { SanitizePlugsSlots = old }
  1278  }
  1279  
  1280  var SanitizePlugsSlots = func(snapInfo *Info) {
  1281  	panic("SanitizePlugsSlots function not set")
  1282  }
  1283  
  1284  // ReadInfo reads the snap information for the installed snap with the given
  1285  // name and given side-info.
  1286  func ReadInfo(name string, si *SideInfo) (*Info, error) {
  1287  	snapYamlFn := filepath.Join(MountDir(name, si.Revision), "meta", "snap.yaml")
  1288  	meta, err := ioutil.ReadFile(snapYamlFn)
  1289  	if os.IsNotExist(err) {
  1290  		return nil, &NotFoundError{Snap: name, Revision: si.Revision, Path: snapYamlFn}
  1291  	}
  1292  	if err != nil {
  1293  		return nil, err
  1294  	}
  1295  
  1296  	strk := new(scopedTracker)
  1297  	info, err := infoFromSnapYamlWithSideInfo(meta, si, strk)
  1298  	if err != nil {
  1299  		return nil, &invalidMetaError{Snap: name, Revision: si.Revision, Msg: err.Error()}
  1300  	}
  1301  
  1302  	_, instanceKey := SplitInstanceName(name)
  1303  	info.InstanceKey = instanceKey
  1304  
  1305  	err = addImplicitHooks(info)
  1306  	if err != nil {
  1307  		return nil, &invalidMetaError{Snap: name, Revision: si.Revision, Msg: err.Error()}
  1308  	}
  1309  
  1310  	bindImplicitHooks(info, strk)
  1311  
  1312  	mountFile := MountFile(name, si.Revision)
  1313  	st, err := os.Lstat(mountFile)
  1314  	if os.IsNotExist(err) {
  1315  		// This can happen when "snap try" mode snap is moved around. The mount
  1316  		// is still in place (it's a bind mount, it doesn't care about the
  1317  		// source moving) but the symlink in /var/lib/snapd/snaps is now
  1318  		// dangling.
  1319  		return nil, &NotFoundError{Snap: name, Revision: si.Revision, Path: mountFile}
  1320  	}
  1321  	if err != nil {
  1322  		return nil, err
  1323  	}
  1324  	// If the file is a regular file than it must be a squashfs file that is
  1325  	// used as the backing store for the snap. The size of that file is the
  1326  	// size of the snap.
  1327  	if st.Mode().IsRegular() {
  1328  		info.Size = st.Size()
  1329  	}
  1330  
  1331  	return info, nil
  1332  }
  1333  
  1334  // ReadCurrentInfo reads the snap information from the installed snap in
  1335  // 'current' revision
  1336  func ReadCurrentInfo(snapName string) (*Info, error) {
  1337  	curFn := filepath.Join(dirs.SnapMountDir, snapName, "current")
  1338  	realFn, err := os.Readlink(curFn)
  1339  	if err != nil {
  1340  		return nil, fmt.Errorf("cannot find current revision for snap %s: %s", snapName, err)
  1341  	}
  1342  	rev := filepath.Base(realFn)
  1343  	revision, err := ParseRevision(rev)
  1344  	if err != nil {
  1345  		return nil, fmt.Errorf("cannot read revision %s: %s", rev, err)
  1346  	}
  1347  
  1348  	return ReadInfo(snapName, &SideInfo{Revision: revision})
  1349  }
  1350  
  1351  // ReadInfoFromSnapFile reads the snap information from the given Container and
  1352  // completes it with the given side-info if this is not nil.
  1353  func ReadInfoFromSnapFile(snapf Container, si *SideInfo) (*Info, error) {
  1354  	meta, err := snapf.ReadFile("meta/snap.yaml")
  1355  	if err != nil {
  1356  		return nil, err
  1357  	}
  1358  
  1359  	strk := new(scopedTracker)
  1360  	info, err := infoFromSnapYamlWithSideInfo(meta, si, strk)
  1361  	if err != nil {
  1362  		return nil, err
  1363  	}
  1364  
  1365  	info.Size, err = snapf.Size()
  1366  	if err != nil {
  1367  		return nil, err
  1368  	}
  1369  
  1370  	addImplicitHooksFromContainer(info, snapf)
  1371  
  1372  	bindImplicitHooks(info, strk)
  1373  
  1374  	err = Validate(info)
  1375  	if err != nil {
  1376  		return nil, err
  1377  	}
  1378  
  1379  	// As part of the validation, also read the snapshot manifest file: we
  1380  	// don't care about its contents now, but we need to make sure it's valid.
  1381  	_, err = ReadSnapshotYamlFromSnapFile(snapf)
  1382  	if err != nil {
  1383  		return nil, err
  1384  	}
  1385  
  1386  	return info, nil
  1387  }
  1388  
  1389  // InstallDate returns the "install date" of the snap.
  1390  //
  1391  // If the snap is not active, it'll return a zero time; otherwise it'll return
  1392  // the modtime of the "current" symlink.
  1393  func InstallDate(name string) time.Time {
  1394  	cur := filepath.Join(dirs.SnapMountDir, name, "current")
  1395  	if st, err := os.Lstat(cur); err == nil {
  1396  		return st.ModTime()
  1397  	}
  1398  	return time.Time{}
  1399  }
  1400  
  1401  // SplitSnapApp will split a string of the form `snap.app` into the `snap` and
  1402  // the `app` part. It also deals with the special case of snapName == appName.
  1403  func SplitSnapApp(snapApp string) (snap, app string) {
  1404  	l := strings.SplitN(snapApp, ".", 2)
  1405  	if len(l) < 2 {
  1406  		return l[0], InstanceSnap(l[0])
  1407  	}
  1408  	return l[0], l[1]
  1409  }
  1410  
  1411  // JoinSnapApp produces a full application wrapper name from the `snap` and the
  1412  // `app` part. It also deals with the special case of snapName == appName.
  1413  func JoinSnapApp(snap, app string) string {
  1414  	storeName, instanceKey := SplitInstanceName(snap)
  1415  	if storeName == app {
  1416  		return InstanceName(app, instanceKey)
  1417  	}
  1418  	return fmt.Sprintf("%s.%s", snap, app)
  1419  }
  1420  
  1421  // InstanceSnap splits the instance name and returns the name of the snap.
  1422  func InstanceSnap(instanceName string) string {
  1423  	snapName, _ := SplitInstanceName(instanceName)
  1424  	return snapName
  1425  }
  1426  
  1427  // SplitInstanceName splits the instance name and returns the snap name and the
  1428  // instance key.
  1429  func SplitInstanceName(instanceName string) (snapName, instanceKey string) {
  1430  	split := strings.SplitN(instanceName, "_", 2)
  1431  	snapName = split[0]
  1432  	if len(split) > 1 {
  1433  		instanceKey = split[1]
  1434  	}
  1435  	return snapName, instanceKey
  1436  }
  1437  
  1438  // InstanceName takes the snap name and the instance key and returns an instance
  1439  // name of the snap.
  1440  func InstanceName(snapName, instanceKey string) string {
  1441  	if instanceKey != "" {
  1442  		return fmt.Sprintf("%s_%s", snapName, instanceKey)
  1443  	}
  1444  	return snapName
  1445  }
  1446  
  1447  // ByType supports sorting the given slice of snap info by types. The most
  1448  // important types will come first.
  1449  type ByType []*Info
  1450  
  1451  func (r ByType) Len() int      { return len(r) }
  1452  func (r ByType) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
  1453  func (r ByType) Less(i, j int) bool {
  1454  	return r[i].Type().SortsBefore(r[j].Type())
  1455  }
  1456  
  1457  // SortServices sorts the apps based on their Before and After specs, such that
  1458  // starting the services in the returned ordering will satisfy all specs.
  1459  func SortServices(apps []*AppInfo) (sorted []*AppInfo, err error) {
  1460  	nameToApp := make(map[string]*AppInfo, len(apps))
  1461  	for _, app := range apps {
  1462  		nameToApp[app.Name] = app
  1463  	}
  1464  
  1465  	// list of successors of given app
  1466  	successors := make(map[string][]*AppInfo, len(apps))
  1467  	// count of predecessors (i.e. incoming edges) of given app
  1468  	predecessors := make(map[string]int, len(apps))
  1469  
  1470  	// identify the successors and predecessors of each app, input data set may
  1471  	// be a subset of all apps in the snap (eg. when restarting only few select
  1472  	// apps), thus make sure to look only at those after/before apps that are
  1473  	// listed in the input
  1474  	for _, app := range apps {
  1475  		for _, other := range app.After {
  1476  			if _, ok := nameToApp[other]; ok {
  1477  				predecessors[app.Name]++
  1478  				successors[other] = append(successors[other], app)
  1479  			}
  1480  		}
  1481  		for _, other := range app.Before {
  1482  			if _, ok := nameToApp[other]; ok {
  1483  				predecessors[other]++
  1484  				successors[app.Name] = append(successors[app.Name], nameToApp[other])
  1485  			}
  1486  		}
  1487  	}
  1488  
  1489  	// list of apps without predecessors (no incoming edges)
  1490  	queue := make([]*AppInfo, 0, len(apps))
  1491  	for _, app := range apps {
  1492  		if predecessors[app.Name] == 0 {
  1493  			queue = append(queue, app)
  1494  		}
  1495  	}
  1496  
  1497  	// Kahn:
  1498  	// see https://dl.acm.org/citation.cfm?doid=368996.369025
  1499  	//     https://en.wikipedia.org/wiki/Topological_sorting%23Kahn%27s_algorithm
  1500  	//
  1501  	// Apps without predecessors are 'top' nodes. On each iteration, take
  1502  	// the next 'top' node, and decrease the predecessor count of each
  1503  	// successor app. Once that successor app has no more predecessors, take
  1504  	// it out of the predecessors set and add it to the queue of 'top'
  1505  	// nodes.
  1506  	for len(queue) > 0 {
  1507  		app := queue[0]
  1508  		queue = queue[1:]
  1509  		for _, successor := range successors[app.Name] {
  1510  			predecessors[successor.Name]--
  1511  			if predecessors[successor.Name] == 0 {
  1512  				delete(predecessors, successor.Name)
  1513  				queue = append(queue, successor)
  1514  			}
  1515  		}
  1516  		sorted = append(sorted, app)
  1517  	}
  1518  
  1519  	if len(predecessors) != 0 {
  1520  		// apps with predecessors unaccounted for are a part of
  1521  		// dependency cycle
  1522  		unsatisifed := bytes.Buffer{}
  1523  		for name := range predecessors {
  1524  			if unsatisifed.Len() > 0 {
  1525  				unsatisifed.WriteString(", ")
  1526  			}
  1527  			unsatisifed.WriteString(name)
  1528  		}
  1529  		return nil, fmt.Errorf("applications are part of a before/after cycle: %s", unsatisifed.String())
  1530  	}
  1531  	return sorted, nil
  1532  }
  1533  
  1534  // AppInfoBySnapApp supports sorting the given slice of app infos by
  1535  // (instance name, app name).
  1536  type AppInfoBySnapApp []*AppInfo
  1537  
  1538  func (a AppInfoBySnapApp) Len() int      { return len(a) }
  1539  func (a AppInfoBySnapApp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  1540  func (a AppInfoBySnapApp) Less(i, j int) bool {
  1541  	iName := a[i].Snap.InstanceName()
  1542  	jName := a[j].Snap.InstanceName()
  1543  	if iName == jName {
  1544  		return a[i].Name < a[j].Name
  1545  	}
  1546  	return iName < jName
  1547  }