github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/snap/info.go (about)

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