github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/snap/info.go (about)

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