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