func (f *fakeSnappyBackend) RemoveSnapCommonData(info *snap.Info) error { f.ops = append(f.ops, fakeOp{ op: "remove-snap-common-data", name: info.MountDir(), }) return nil }
// CheckInterfaces checks whether plugs and slots of snap are allowed for installation. func CheckInterfaces(st *state.State, snapInfo *snap.Info) error { // XXX: AddImplicitSlots is really a brittle interface snap.AddImplicitSlots(snapInfo) baseDecl, err := assertstate.BaseDeclaration(st) if err != nil { return fmt.Errorf("internal error: cannot find base declaration: %v", err) } var snapDecl *asserts.SnapDeclaration if snapInfo.SnapID != "" { var err error snapDecl, err = assertstate.SnapDeclaration(st, snapInfo.SnapID) if err != nil { return fmt.Errorf("cannot find snap declaration for %q: %v", snapInfo.Name(), err) } } ic := policy.InstallCandidate{ Snap: snapInfo, SnapDeclaration: snapDecl, BaseDeclaration: baseDecl, } return ic.Check() }
func (f *fakeSnappyBackend) StopSnapServices(info *snap.Info, meter progress.Meter) error { f.ops = append(f.ops, fakeOp{ op: "stop-snap-services", name: info.MountDir(), }) return nil }
func runSnapConfine(info *snap.Info, securityTag, snapApp, command, hook string, args []string) error { if err := createUserDataDirs(info); err != nil { logger.Noticef("WARNING: cannot create user data directory: %s", err) } cmd := []string{ filepath.Join(dirs.LibExecDir, "snap-confine"), } if info.NeedsClassic() { cmd = append(cmd, "--classic") } cmd = append(cmd, securityTag) cmd = append(cmd, filepath.Join(dirs.LibExecDir, "snap-exec")) if command != "" { cmd = append(cmd, "--command="+command) } if hook != "" { cmd = append(cmd, "--hook="+hook) } // snap-exec is POSIXly-- options must come before positionals. cmd = append(cmd, snapApp) cmd = append(cmd, args...) return syscallExec(cmd[0], cmd, snapenv.ExecEnv(info)) }
// AddSnapDesktopFiles puts in place the desktop files for the applications from the snap. func AddSnapDesktopFiles(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 } installedDesktopFileName := filepath.Join(dirs.SnapDesktopFilesDir, fmt.Sprintf("%s_%s", s.Name(), filepath.Base(df))) content = sanitizeDesktopFile(s, installedDesktopFileName, content) if err := osutil.AtomicWriteFile(installedDesktopFileName, []byte(content), 0755, 0); err != nil { return err } } // updates mime info etc if err := updateDesktopDatabase(desktopFiles); err != nil { return err } return nil }
func sanitizeDesktopFile(s *snap.Info, desktopFile string, rawcontent []byte) []byte { newContent := []string{} scanner := bufio.NewScanner(bytes.NewReader(rawcontent)) for scanner.Scan() { line := scanner.Text() // whitespace/comments are just copied if strings.TrimSpace(line) == "" || strings.HasPrefix(strings.TrimSpace(line), "#") { newContent = append(newContent, line) continue } // ignore everything we have not whitelisted if !isValidDesktopFilePrefix(line) && !isValidLocalizedDesktopFilePrefix(line) { continue } // rewrite exec lines to an absolute path for the binary if strings.HasPrefix(line, "Exec=") { var err error line, err = rewriteExecLine(s, desktopFile, line) if err != nil { // something went wrong, ignore the line continue } } // do variable substitution line = strings.Replace(line, "${SNAP}", s.MountDir(), -1) newContent = append(newContent, line) } return []byte(strings.Join(newContent, "\n")) }
// templateVariables returns text defining apparmor variables that can be used in the // apparmor template and by apparmor snippets. func templateVariables(info *snap.Info) []byte { var buf bytes.Buffer fmt.Fprintf(&buf, "@{SNAP_NAME}=\"%s\"\n", info.Name()) fmt.Fprintf(&buf, "@{SNAP_REVISION}=\"%s\"\n", info.Revision) fmt.Fprintf(&buf, "@{INSTALL_DIR}=\"/snap\"") return buf.Bytes() }
// KernelOrOsRebootRequired returns whether a reboot is required to swith to the given OS or kernel snap. func KernelOrOsRebootRequired(s *snap.Info) bool { if s.Type != snap.TypeKernel && s.Type != snap.TypeOS { return false } bootloader, err := partition.FindBootloader() if err != nil { logger.Noticef("cannot get boot settings: %s", err) return false } var nextBoot, goodBoot string switch s.Type { case snap.TypeKernel: nextBoot = "snap_try_kernel" goodBoot = "snap_kernel" case snap.TypeOS: nextBoot = "snap_try_core" goodBoot = "snap_core" } m, err := bootloader.GetBootVars(nextBoot, goodBoot) if err != nil { return false } squashfsName := filepath.Base(s.MountFile()) if m[nextBoot] == squashfsName && m[goodBoot] != m[nextBoot] { return true } return false }
// Setup creates a conf file with list of kernel modules required by given snap, // writes it in /etc/modules-load.d/ directory and immediately loads the modules // using /sbin/modprobe. The 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, confinement interfaces.ConfinementOptions, repo *interfaces.Repository) error { snapName := snapInfo.Name() // Get the snippets that apply to this snap snippets, err := repo.SecuritySnippetsForSnap(snapInfo.Name(), interfaces.SecurityKMod) if err != nil { return fmt.Errorf("cannot obtain kmod security snippets for snap %q: %s", snapName, err) } // Get the files that this snap should have glob := interfaces.SecurityTagGlob(snapName) content, modules, err := b.combineSnippets(snapInfo, snippets) if err != nil { return fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err) } dir := dirs.SnapKModModulesDir if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory for kmod files %q: %s", dir, err) } changed, _, err := osutil.EnsureDirState(dirs.SnapKModModulesDir, glob, content) if err != nil { return err } if len(changed) > 0 { return loadModules(modules) } return nil }
// combineSnippets combines security snippets collected from all the interfaces // affecting a given snap into a de-duplicated list of kernel modules. func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]byte) (content map[string]*osutil.FileState, modules []string, err error) { content = make(map[string]*osutil.FileState) for _, appInfo := range snapInfo.Apps { for _, snippet := range snippets[appInfo.SecurityTag()] { // split snippet by newline to get the list of modules for _, line := range bytes.Split(snippet, []byte{'\n'}) { l := bytes.TrimSpace(line) // ignore empty lines and comments if len(l) > 0 && l[0] != '#' { modules = append(modules, string(l)) } } } } sort.Strings(modules) modules = uniqueLines(modules) if len(modules) > 0 { var buffer bytes.Buffer buffer.WriteString("# This file is automatically generated.\n") for _, module := range modules { buffer.WriteString(module) buffer.WriteByte('\n') } content[fmt.Sprintf("%s.conf", snap.SecurityTag(snapInfo.Name()))] = &osutil.FileState{ Content: buffer.Bytes(), Mode: 0644, } } return content, modules, nil }
// SetNextBoot will schedule the given OS or kernel snap to be used in // the next boot func SetNextBoot(s *snap.Info) error { if release.OnClassic { return nil } if s.Type != snap.TypeOS && s.Type != snap.TypeKernel { return nil } bootloader, err := partition.FindBootloader() if err != nil { return fmt.Errorf("cannot set next boot: %s", err) } var bootvar string switch s.Type { case snap.TypeOS: bootvar = "snap_try_core" case snap.TypeKernel: bootvar = "snap_try_kernel" } blobName := filepath.Base(s.MountFile()) if err := bootloader.SetBootVar(bootvar, blobName); err != nil { return err } if err := bootloader.SetBootVar("snap_mode", "try"); err != nil { return err } return nil }
func (f *fakeSnappyBackend) ClearTrashedData(si *snap.Info) { f.ops = append(f.ops, fakeOp{ op: "cleanup-trash", name: si.Name(), revno: si.Revision, }) }
func (f *fakeSnappyBackend) UnlinkSnap(info *snap.Info, meter progress.Meter) error { meter.Notify("unlink") f.ops = append(f.ops, fakeOp{ op: "unlink-snap", name: info.MountDir(), }) return nil }
// snapDate returns the time of the snap mount directory. func snapDate(info *snap.Info) time.Time { st, err := os.Stat(info.MountDir()) if err != nil { return time.Time{} } return st.ModTime() }
// snapIcon tries to find the icon inside the snap func snapIcon(info *snap.Info) string { // XXX: copy of snap.Snap.Icon which will go away found, _ := filepath.Glob(filepath.Join(info.MountDir(), "meta", "gui", "icon.*")) if len(found) == 0 { return info.IconURL } return found[0] }
func (f *fakeSnappyBackend) CurrentInfo(curInfo *snap.Info) { old := "<no-current>" if curInfo != nil { old = curInfo.MountDir() } f.ops = append(f.ops, fakeOp{ op: "current", old: old, }) }
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) } }
func (f *fakeStoreClient) Download(remoteSnap *snap.Info, pb progress.Meter, sa store.Authenticator) (path string, err error) { f.downloadCalls[getDownloadCall(remoteSnap.Name(), remoteSnap.Channel)]++ f.totalDownloadCalls++ if f.downloadErr { if f.totalDownloadCalls > f.correctDownloadCalls { return "", errors.New("") } } return getSnapFilename(remoteSnap.Name(), remoteSnap.Channel), nil }
// snapCommonDataDirs returns the list of data directories common between versions of the given snap func snapCommonDataDirs(snap *snap.Info) ([]string, error) { // collect the directories, homes first found, err := filepath.Glob(snap.CommonDataHomeDir()) if err != nil { return nil, err } // then system data found = append(found, snap.CommonDataDir()) return found, nil }
// 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 }
// userEnv returns the user-level environment variables for a snap. // Despite this being a bit snap-specific, this is in helpers.go because it's // used by so many other modules, we run into circular dependencies if it's // somewhere more reasonable like the snappy module. func userEnv(info *snap.Info, home string) map[string]string { result := map[string]string{ "SNAP_USER_COMMON": info.UserCommonDataDir(home), "SNAP_USER_DATA": info.UserDataDir(home), "XDG_RUNTIME_DIR": info.UserXdgRuntimeDir(os.Geteuid()), } // For non-classic snaps, we set HOME but on classic allow snaps to see real HOME if !info.NeedsClassic() { result["HOME"] = info.UserDataDir(home) } return result }
// 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 := snaptest.MockInfo(c, snapYaml, &snap.SideInfo{ Revision: snap.R(revision), Developer: "acme", }) 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 (b *Backend) Setup(snapInfo *snap.Info, confinement interfaces.ConfinementOptions, repo *interfaces.Repository) error { snapName := snapInfo.Name() rawSnippets, err := repo.SecuritySnippetsForSnap(snapInfo.Name(), interfaces.SecuritySystemd) if err != nil { return fmt.Errorf("cannot obtain systemd security snippets for snap %q: %s", snapName, err) } snippets, err := unmarshalRawSnippetMap(rawSnippets) if err != nil { return fmt.Errorf("cannot unmarshal systemd snippets for snap %q: %s", snapName, err) } snippet, err := mergeSnippetMap(snippets) if err != nil { return fmt.Errorf("cannot merge systemd snippets for snap %q: %s", snapName, err) } content, err := renderSnippet(snippet) if err != nil { return fmt.Errorf("cannot render systemd snippets for snap %q: %s", snapName, err) } dir := dirs.SnapServicesDir if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory for systemd services %q: %s", dir, err) } glob := interfaces.InterfaceServiceName(snapName, "*") systemd := sysd.New(dirs.GlobalRootDir, &dummyReporter{}) // We need to be carefully here and stop all removed service units before // we remove their files as otherwise systemd is not able to disable/stop // them anymore. if err := disableRemovedServices(systemd, dir, glob, content); err != nil { logger.Noticef("cannot stop removed services: %s", err) } changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content) // Reload systemd whenever something is added or removed if len(changed) > 0 || len(removed) > 0 { err := systemd.DaemonReload() if err != nil { logger.Noticef("cannot reload systemd state: %s", err) } } // Ensure the service is running right now and on reboots for _, service := range changed { if err := systemd.Enable(service); err != nil { logger.Noticef("cannot enable service %q: %s", service, err) } // If we have a new service here which isn't started yet the restart // operation will start it. if err := systemd.Restart(service, 10*time.Second); err != nil { logger.Noticef("cannot restart service %q: %s", service, err) } } return errEnsure }
// FetchAndCheckSnapAssertions fetches and cross checks the snap assertions matching the given snap file using the provided asserts.Fetcher and assertion database. func FetchAndCheckSnapAssertions(snapPath string, info *snap.Info, f asserts.Fetcher, db asserts.RODatabase) error { sha3_384, size, err := asserts.SnapFileSHA3_384(snapPath) if err != nil { return err } if err := snapasserts.FetchSnapAssertions(f, sha3_384); err != nil { return fmt.Errorf("cannot fetch snap signatures/assertions: %v", err) } // cross checks return snapasserts.CrossCheck(info.Name(), sha3_384, size, &info.SideInfo, db) }
func (f *fakeSnappyBackend) UndoCopySnapData(newInfo *snap.Info, oldInfo *snap.Info, p progress.Meter) error { p.Notify("undo-copy-data") old := "<no-old>" if oldInfo != nil { old = oldInfo.MountDir() } f.ops = append(f.ops, fakeOp{ op: "undo-copy-snap-data", name: newInfo.MountDir(), old: old, }) return nil }
// ClearTrashedData removes the trash. It returns no errors on the assumption that it is called very late in the game. func (b Backend) ClearTrashedData(oldSnap *snap.Info) { dirs, err := snapDataDirs(oldSnap) if err != nil { logger.Noticef("Cannot remove previous data for %q: %v", oldSnap.Name(), err) return } for _, d := range dirs { if err := clearTrash(d); err != nil { logger.Noticef("Cannot remove %s: %v", d, err) } } }
// combineSnippets combines security snippets collected from all the interfaces // affecting a given snap into a content map applicable to EnsureDirState. func (b *Backend) combineSnippets(snapInfo *snap.Info, snippets map[string][][]byte) (result [][]byte, err error) { var snapSnippets = make(map[string][]byte) // We put all snippets from apps and hooks in the following part in a // map to reach a deduplicated set of snippets we can then write out // in a per snap udev rules file. for _, appInfo := range snapInfo.Apps { securityTag := appInfo.SecurityTag() appSnippets := snippets[securityTag] if len(appSnippets) == 0 { continue } for _, snippet := range appSnippets { snapSnippets[string(snippet)] = snippet } } for _, hookInfo := range snapInfo.Hooks { securityTag := hookInfo.SecurityTag() hookSnippets := snippets[securityTag] if len(hookSnippets) == 0 { continue } for _, snippet := range hookSnippets { snapSnippets[string(snippet)] = snippet } } nonePrefix := snap.NoneSecurityTag(snapInfo.Name(), "") for securityTag, slotSnippets := range snippets { if !strings.HasPrefix(securityTag, nonePrefix) { continue } for _, snippet := range slotSnippets { snapSnippets[string(snippet)] = snippet } } var combinedSnippets [][]byte for _, snippet := range snapSnippets { combinedSnippets = append(combinedSnippets, snippet) } return combinedSnippets, nil }
func setupSnapSecurity(task *state.Task, snapInfo *snap.Info, devMode bool, repo *interfaces.Repository) error { st := task.State() snapName := snapInfo.Name() for _, backend := range backends.All { st.Unlock() err := backend.Setup(snapInfo, devMode, repo) st.Lock() if err != nil { task.Errorf("cannot setup %s for snap %q: %s", backend.Name(), snapName, err) return err } } return nil }
func (b *Backend) Setup(snapInfo *snap.Info, devMode bool, repo *interfaces.Repository) error { snapName := snapInfo.Name() rawSnippets, err := repo.SecuritySnippetsForSnap(snapInfo.Name(), interfaces.SecuritySystemd) if err != nil { return fmt.Errorf("cannot obtain systemd security snippets for snap %q: %s", snapName, err) } snippets, err := unmarshalRawSnippetMap(rawSnippets) if err != nil { return fmt.Errorf("cannot unmarshal systemd snippets for snap %q: %s", snapName, err) } snippet, err := mergeSnippetMap(snippets) if err != nil { return fmt.Errorf("cannot merge systemd snippets for snap %q: %s", snapName, err) } content, err := renderSnippet(snippet) if err != nil { return fmt.Errorf("cannot render systemd snippets for snap %q: %s", snapName, err) } dir := dirs.SnapServicesDir if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory for systemd services %q: %s", dir, err) } glob := interfaces.InterfaceServiceName(snapName, "*") changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content) systemd := sysd.New(dirs.GlobalRootDir, &dummyReporter{}) // Reload systemd whenever something is added or removed if len(changed) > 0 || len(removed) > 0 { err := systemd.DaemonReload() if err != nil { logger.Noticef("cannot reload systemd state: %s", err) } } // Start any new services for _, service := range changed { err := systemd.Start(service) if err != nil { logger.Noticef("cannot start service %q: %s", service, err) } } // Stop any removed services for _, service := range removed { err := systemd.Stop(service, 10*time.Second) if err != nil { logger.Noticef("cannot stop service %q: %s", service, err) } } return errEnsure }
func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, flags snapstate.Flags) error { kind := "" var currentInfo func(*state.State) (*snap.Info, error) var getName func(*asserts.Model) string switch snapInfo.Type { case snap.TypeGadget: kind = "gadget" currentInfo = snapstate.GadgetInfo getName = (*asserts.Model).Gadget case snap.TypeKernel: kind = "kernel" currentInfo = snapstate.KernelInfo getName = (*asserts.Model).Kernel default: // not a relevant check return nil } if release.OnClassic { // for the time being return fmt.Errorf("cannot install a %s snap on classic", kind) } currentSnap, err := currentInfo(st) if err != nil && err != state.ErrNoState { return fmt.Errorf("cannot find original %s snap: %v", kind, err) } if currentSnap != nil { // already installed, snapstate takes care return nil } // first installation of a gadget/kernel model, err := Model(st) if err == state.ErrNoState { return fmt.Errorf("cannot install %s without model assertion", kind) } if err != nil { return err } expectedName := getName(model) if snapInfo.Name() != expectedName { return fmt.Errorf("cannot install %s %q, model assertion requests %q", kind, snapInfo.Name(), expectedName) } return nil }