// CanInstall checks whether the Snap passes a series of tests required for installation func canInstall(s *snap.Info, snapf snap.File, curInfo *snap.Info, allowGadget bool, inter interacter) error { // verify we have a valid architecture if !arch.IsSupportedArchitecture(s.Architectures) { return &ErrArchitectureNotSupported{s.Architectures} } if s.Type == snap.TypeGadget { if !allowGadget { if currentGadget, err := getGadget(); err == nil { if currentGadget.Name() != s.Name() { return ErrGadgetPackageInstall } } else { // there should always be a gadget package now return ErrGadgetPackageInstall } } } if err := checkLicenseAgreement(s, snapf, curInfo, inter); err != nil { return err } return nil }
// Download downloads the given snap and returns its filename. // The file is saved in temporary storage, and should be removed // after use to prevent the disk from running out of space. func (s *SnapUbuntuStoreRepository) Download(remoteSnap *snap.Info, pbar progress.Meter, auther Authenticator) (path string, err error) { w, err := ioutil.TempFile("", remoteSnap.Name()) if err != nil { return "", err } defer func() { if cerr := w.Close(); cerr != nil && err == nil { err = cerr } if err != nil { os.Remove(w.Name()) path = "" } }() url := remoteSnap.AnonDownloadURL if url == "" || auther != nil { url = remoteSnap.DownloadURL } req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } s.applyUbuntuStoreHeaders(req, "", auther) if err := download(remoteSnap.Name(), w, req, pbar); err != nil { return "", err } return w.Name(), w.Sync() }
func UndoCopyData(newInfo *snap.Info, flags InstallFlags, meter progress.Meter) { // XXX we were copying data, assume InhibitHooks was false if err := RemoveSnapData(newInfo); err != nil { logger.Noticef("When cleaning up data for %s %s: %v", newInfo.Name(), newInfo.Version, err) } }
// updateSnap "updates" an existing snap from YAML. func (s *backendSuite) updateSnap(c *C, oldSnapInfo *snap.Info, devMode bool, snapYaml string) *snap.Info { newSnapInfo, err := snap.InfoFromSnapYaml([]byte(snapYaml)) c.Assert(err, IsNil) c.Assert(newSnapInfo.Name(), Equals, oldSnapInfo.Name()) s.removePlugsSlots(c, oldSnapInfo) s.addPlugsSlots(c, newSnapInfo) err = s.backend.Setup(newSnapInfo, devMode, s.repo) c.Assert(err, IsNil) return newSnapInfo }
func (s *backendSuite) removePlugsSlots(c *C, snapInfo *snap.Info) { for _, plug := range s.repo.Plugs(snapInfo.Name()) { err := s.repo.RemovePlug(plug.Snap.Name(), plug.Name) c.Assert(err, IsNil) } for _, slot := range s.repo.Slots(snapInfo.Name()) { err := s.repo.RemoveSlot(slot.Snap.Name(), slot.Name) c.Assert(err, IsNil) } }
// AddSnap adds plugs and slots declared by the given snap to the repository. // // This function can be used to implement snap install or, when used along with // RemoveSnap, snap upgrade. // // AddSnap doesn't change existing plugs/slots. The caller is responsible for // ensuring that the snap is not present in the repository in any way prior to // calling this function. If this constraint is violated then no changes are // made and an error is returned. // // Each added plug/slot is validated according to the corresponding interface. // Unknown interfaces and plugs/slots that don't validate are not added. // Information about those failures are returned to the caller. func (r *Repository) AddSnap(snapInfo *snap.Info) error { r.m.Lock() defer r.m.Unlock() snapName := snapInfo.Name() if r.plugs[snapName] != nil || r.slots[snapName] != nil { return fmt.Errorf("cannot register interfaces for snap %q more than once", snapName) } bad := BadInterfacesError{ snap: snapName, issues: make(map[string]string), } for plugName, plugInfo := range snapInfo.Plugs { iface, ok := r.ifaces[plugInfo.Interface] if !ok { bad.issues[plugName] = "unknown interface" continue } plug := &Plug{PlugInfo: plugInfo} if err := iface.SanitizePlug(plug); err != nil { bad.issues[plugName] = err.Error() continue } if r.plugs[snapName] == nil { r.plugs[snapName] = make(map[string]*Plug) } r.plugs[snapName][plugName] = plug } for slotName, slotInfo := range snapInfo.Slots { iface, ok := r.ifaces[slotInfo.Interface] if !ok { bad.issues[slotName] = "unknown interface" continue } slot := &Slot{SlotInfo: slotInfo} if err := iface.SanitizeSlot(slot); err != nil { bad.issues[slotName] = err.Error() continue } if r.slots[snapName] == nil { r.slots[snapName] = make(map[string]*Slot) } r.slots[snapName][slotName] = slot } if len(bad.issues) > 0 { return &bad } return nil }
func removePackageDesktopFiles(s *snap.Info) error { glob := filepath.Join(dirs.SnapDesktopFilesDir, s.Name()+"_*.desktop") activeDesktopFiles, err := filepath.Glob(glob) if err != nil { return fmt.Errorf("cannot get desktop files for %v: %s", glob, err) } for _, f := range activeDesktopFiles { os.Remove(f) } return nil }
func installRemote(mStore *store.SnapUbuntuStoreRepository, remoteSnap *snap.Info, flags InstallFlags, meter progress.Meter) (string, error) { downloadedSnap, err := mStore.Download(remoteSnap, meter, nil) if err != nil { return "", fmt.Errorf("cannot download %s: %s", remoteSnap.Name(), err) } defer os.Remove(downloadedSnap) localSnap, err := (&Overlord{}).InstallWithSideInfo(downloadedSnap, &remoteSnap.SideInfo, flags, meter) if err != nil { return "", err } return localSnap.Name(), nil }
func doUpdate(mStore *store.SnapUbuntuStoreRepository, rsnap *snap.Info, flags InstallFlags, meter progress.Meter) error { _, err := installRemote(mStore, rsnap, flags, meter) if err == ErrSideLoaded { logger.Noticef("Skipping sideloaded package: %s", rsnap.Name()) return nil } else if err != nil { return err } if err := GarbageCollect(rsnap.Name(), flags, meter); err != nil { return err } return nil }
// updateSnap "updates" an existing snap from YAML. func (s *backendSuite) updateSnap(c *C, oldSnapInfo *snap.Info, devMode bool, snapYaml string, revision int) *snap.Info { newSnapInfo, err := snap.InfoFromSnapYaml([]byte(snapYaml)) c.Assert(err, IsNil) newSnapInfo.Revision = revision // this won't come from snap.yaml newSnapInfo.Developer = "acme" c.Assert(newSnapInfo.Name(), Equals, oldSnapInfo.Name()) err = s.repo.RemoveSnap(oldSnapInfo.Name()) c.Assert(err, IsNil) err = s.repo.AddSnap(newSnapInfo) c.Assert(err, IsNil) err = s.backend.Setup(newSnapInfo, devMode, s.repo) c.Assert(err, IsNil) return newSnapInfo }
// Setup creates udev rules specific to a given snap. // If any of the rules are changed or removed then udev database is reloaded. // // Since udev has no concept of a complain mode, devMode is ignored. // // If the method fails it should be re-tried (with a sensible strategy) by the caller. func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repository) error { snapName := snapInfo.Name() snippets, err := repo.SecuritySnippetsForSnap(snapInfo.Name(), interfaces.SecurityUDev) if err != nil { return fmt.Errorf("cannot obtain udev security snippets for snap %q: %s", snapName, err) } content, err := b.combineSnippets(snapInfo, snippets) if err != nil { return fmt.Errorf("cannot obtain expected udev rules for snap %q: %s", snapName, err) } glob := fmt.Sprintf("70-%s.rules", interfaces.SecurityTagGlob(snapName)) dir := dirs.SnapUdevRulesDir if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory for udev rules %q: %s", dir, err) } return ensureDirState(dir, glob, content, snapName) }
func CanRemove(s *snap.Info, active bool) bool { // Gadget snaps should not be removed as they are a key // building block for Gadgets. Prunning non active ones // is acceptible. if s.Type == snap.TypeGadget && active { return false } // You never want to remove an active kernel or OS if (s.Type == snap.TypeKernel || s.Type == snap.TypeOS) && active { return false } if IsBuiltInSoftware(s.Name()) && active { return false } return true }
func setupSnapSecurity(task *state.Task, snapInfo *snap.Info, repo *interfaces.Repository) error { st := task.State() var snapState snapstate.SnapState snapName := snapInfo.Name() if err := snapstate.Get(st, snapName, &snapState); err != nil { task.Errorf("cannot get state of snap %q: %s", snapName, err) return err } for _, backend := range securityBackends { st.Unlock() err := backend.Setup(snapInfo, snapState.DevMode(), repo) st.Lock() if err != nil { task.Errorf("cannot setup %s for snap %q: %s", backend.Name(), snapName, err) return err } } return nil }
// SaveManifest saves the manifest at the designated location for the snap containing information not in the snap.yaml. func SaveManifest(rsnap *snap.Info) error { if rsnap.Revision == 0 { return fmt.Errorf("internal error: should not be storring manifests for sideloaded snaps") } // XXX: we store OfficialName though it may not be the blessed one later content, err := yaml.Marshal(&rsnap.SideInfo) if err != nil { return err } if err := os.MkdirAll(dirs.SnapMetaDir, 0755); err != nil { return err } p := manifestPath(rsnap.Name(), rsnap.Revision) // don't worry about previous contents return osutil.AtomicWriteFile(p, content, 0644, 0) }
func addSquashfsMount(s *snap.Info, inhibitHooks bool, inter interacter) error { squashfsPath := stripGlobalRootDir(s.MountFile()) whereDir := stripGlobalRootDir(s.MountDir()) sysd := systemd.New(dirs.GlobalRootDir, inter) mountUnitName, err := sysd.WriteMountUnitFile(s.Name(), squashfsPath, whereDir) if err != nil { return err } // we always enable the mount unit even in inhibit hooks if err := sysd.Enable(mountUnitName); err != nil { return err } if !inhibitHooks { return sysd.Start(mountUnitName) } return nil }
// Setup creates dbus configuration files specific to a given snap. // // DBus has no concept of a complain mode so devMode is not supported func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repository) error { snapName := snapInfo.Name() // Get the snippets that apply to this snap snippets, err := repo.SecuritySnippetsForSnap(snapInfo.Name(), interfaces.SecurityDBus) if err != nil { return fmt.Errorf("cannot obtain DBus security snippets for snap %q: %s", snapName, err) } // Get the files that this snap should have content, err := b.combineSnippets(snapInfo, snippets) if err != nil { return fmt.Errorf("cannot obtain expected DBus configuration files for snap %q: %s", snapName, err) } glob := fmt.Sprintf("%s.conf", interfaces.SecurityTagGlob(snapName)) dir := dirs.SnapBusPolicyDir if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory for DBus configuration files %q: %s", dir, err) } _, _, err = osutil.EnsureDirState(dir, glob, content) if err != nil { return fmt.Errorf("cannot synchronize DBus configuration files for snap %q: %s", snapName, err) } return nil }
// Setup creates and loads apparmor profiles specific to a given snap. // The snap can be in developer mode to make security violations non-fatal to // the offending application process. // // This method should be called after changing plug, slots, connections between // them or application present in the snap. func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repository) error { snapName := snapInfo.Name() // Get the snippets that apply to this snap snippets, err := repo.SecuritySnippetsForSnap(snapName, interfaces.SecurityAppArmor) if err != nil { return fmt.Errorf("cannot obtain security snippets for snap %q: %s", snapName, err) } // Get the files that this snap should have content, err := b.combineSnippets(snapInfo, devMode, snippets) if err != nil { return fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err) } glob := interfaces.SecurityTagGlob(snapInfo.Name()) dir := dirs.SnapAppArmorDir if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory for apparmor profiles %q: %s", dir, err) } _, removed, errEnsure := osutil.EnsureDirState(dir, glob, content) // NOTE: load all profiles instead of just the changed profiles. We're // relying on apparmor cache to make this efficient. This gives us // certainty that each call to Setup ends up with working profiles. all := make([]string, 0, len(content)) for name := range content { all = append(all, name) } sort.Strings(all) errReload := reloadProfiles(all) errUnload := unloadProfiles(removed) if errEnsure != nil { return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure) } if errReload != nil { return errReload } return errUnload }
// RemoveGeneratedWrappers removes the generated services, binaries, desktop // wrappers func RemoveGeneratedWrappers(s *snap.Info, inter interacter) error { err1 := removePackageBinaries(s) if err1 != nil { logger.Noticef("Failed to remove binaries for %q: %v", s.Name(), err1) } err2 := removePackageServices(s, inter) if err2 != nil { logger.Noticef("Failed to remove services for %q: %v", s.Name(), err2) } err3 := removePackageDesktopFiles(s) if err3 != nil { logger.Noticef("Failed to remove desktop files for %q: %v", s.Name(), err3) } return firstErr(err1, err2, err3) }
func addPackageDesktopFiles(s *snap.Info) error { if err := os.MkdirAll(dirs.SnapDesktopFilesDir, 0755); err != nil { return err } baseDir := s.MountDir() desktopFiles, err := filepath.Glob(filepath.Join(baseDir, "meta", "gui", "*.desktop")) if err != nil { return fmt.Errorf("cannot get desktop files for %v: %s", baseDir, err) } for _, df := range desktopFiles { content, err := ioutil.ReadFile(df) if err != nil { return err } realBaseDir := stripGlobalRootDir(baseDir) content = sanitizeDesktopFile(s, realBaseDir, content) installedDesktopFileName := filepath.Join(dirs.SnapDesktopFilesDir, fmt.Sprintf("%s_%s", s.Name(), filepath.Base(df))) if err := osutil.AtomicWriteFile(installedDesktopFileName, []byte(content), 0755, 0); err != nil { return err } } return nil }
func cleanupGadgetHardwareUdevRules(s *snap.Info) error { oldFiles, err := filepath.Glob(filepath.Join(dirs.SnapUdevRulesDir, fmt.Sprintf("80-snappy_%s_*.rules", s.Name()))) if err != nil { return err } for _, f := range oldFiles { os.Remove(f) } // cleanup the additional files for _, h := range s.Legacy.Gadget.Hardware.Assign { jsonAdditionalPath := filepath.Join(dirs.SnapAppArmorDir, fmt.Sprintf("%s.json.additional", h.PartID)) err = os.Remove(jsonAdditionalPath) if err != nil && !os.IsNotExist(err) { logger.Noticef("Failed to remove %q: %v", jsonAdditionalPath, err) } } return nil }
// removeSnap "removes" an "installed" snap. func (s *backendSuite) removeSnap(c *C, snapInfo *snap.Info) { err := s.backend.Remove(snapInfo.Name()) c.Assert(err, IsNil) s.removePlugsSlots(c, snapInfo) }
func writeGadgetHardwareUdevRules(s *snap.Info) error { os.MkdirAll(dirs.SnapUdevRulesDir, 0755) // cleanup if err := cleanupGadgetHardwareUdevRules(s); err != nil { return err } // write new files for _, h := range s.Legacy.Gadget.Hardware.Assign { rulesContent, err := generateUdevRuleContent(&h) if err != nil { return err } outfile := filepath.Join(dirs.SnapUdevRulesDir, fmt.Sprintf("80-snappy_%s_%s.rules", s.Name(), h.PartID)) if err := osutil.AtomicWriteFile(outfile, []byte(rulesContent), 0644, 0); err != nil { return err } } return nil }
// Map a localSnap information plus the given active flag to a // map[string]interface{}, augmenting it with the given (purportedly remote) // snap. // // It is a programming error (->panic) to call mapSnap with both arguments // nil. func mapSnap(localSnap *snap.Info, active bool, remoteSnap *snap.Info) map[string]interface{} { var version, icon, name, developer, _type, description, summary string var revision int rollback := -1 update := -1 if localSnap == nil && remoteSnap == nil { panic("no localSnaps & remoteSnap is nil -- how did i even get here") } status := "available" installedSize := int64(-1) downloadSize := int64(-1) var prices map[string]float64 if remoteSnap != nil { prices = remoteSnap.Prices } if localSnap != nil { if active { status = "active" } else { status = "installed" } } var ref *snap.Info if localSnap != nil { ref = localSnap } else { ref = remoteSnap } name = ref.Name() developer = ref.Developer version = ref.Version revision = ref.Revision _type = string(ref.Type) if localSnap != nil { icon = snapIcon(localSnap) summary = localSnap.Summary() description = localSnap.Description() installedSize = localSnap.Size } if remoteSnap != nil { if icon == "" { icon = remoteSnap.IconURL } if description == "" { description = remoteSnap.Description() } if summary == "" { summary = remoteSnap.Summary() } downloadSize = remoteSnap.Size } if localSnap != nil && active { if remoteSnap != nil && revision != remoteSnap.Revision { update = remoteSnap.Revision } // WARNING this'll only get the right* rollback if // only two things can be installed // // *) not the actual right rollback because we aren't // marking things failed etc etc etc) // //if len(localSnaps) == 2 { // rollback = localSnaps[1^idx].Revision() //} } result := map[string]interface{}{ "icon": icon, "name": name, "developer": developer, "status": status, "type": _type, "vendor": "", "revision": revision, "version": version, "description": description, "summary": summary, "installed-size": installedSize, "download-size": downloadSize, } if len(prices) > 0 { result["prices"] = prices } if localSnap != nil { channel := localSnap.Channel if channel != "" { result["channel"] = channel } result["install-date"] = snapDate(localSnap) } if rollback > -1 { result["rollback-available"] = rollback } if update > -1 { result["update-available"] = update } return result }
// removeSnap "removes" an "installed" snap. func (s *backendSuite) removeSnap(c *C, snapInfo *snap.Info) { err := s.backend.Remove(snapInfo.Name()) c.Assert(err, IsNil) err = s.repo.RemoveSnap(snapInfo.Name()) c.Assert(err, IsNil) }
// FullName of a snap.Info is Name.Developer func FullName(p *snap.Info) string { return p.Name() + "." + p.Developer }
// checkLicenseAgreement returns nil if it's ok to proceed with installing the // package, as deduced from the license agreement (which might involve asking // the user), or an error that explains the reason why installation should not // proceed. func checkLicenseAgreement(s *snap.Info, snapf snap.File, cur *snap.Info, ag agreer) error { if s.LicenseAgreement != "explicit" { return nil } if ag == nil { return ErrLicenseNotAccepted } license, err := snapf.MetaMember("license.txt") if err != nil || len(license) == 0 { return ErrLicenseNotProvided } // don't ask for the license if // * the previous version also asked for license confirmation, and // * the license version is the same if cur != nil && (cur.LicenseAgreement == "explicit") && cur.LicenseVersion == s.LicenseVersion { return nil } msg := fmt.Sprintf("%s requires that you accept the following license before continuing", s.Name()) if !ag.Agreed(msg, string(license)) { return ErrLicenseNotAccepted } return nil }
// BareName of a snap.Info is just its Name func BareName(p *snap.Info) string { return p.Name() }