func generateSnapServicesFile(app *snap.AppInfo, baseDir string) (string, error) { if err := snap.ValidateApp(app); err != nil { return "", err } desc := fmt.Sprintf("service %s for snap %s - autogenerated DO NO EDIT", app.Name, app.Snap.Name()) socketFileName := "" if app.Socket { socketFileName = filepath.Base(app.ServiceSocketFile()) } return systemd.New(dirs.GlobalRootDir, nil).GenServiceFile( &systemd.ServiceDescription{ SnapName: app.Snap.Name(), AppName: app.Name, Version: app.Snap.Version, Revision: app.Snap.Revision, Description: desc, SnapPath: baseDir, Start: app.Command, Stop: app.Stop, PostStop: app.PostStop, StopTimeout: serviceStopTimeout(app), AaProfile: app.SecurityTag(), BusName: app.BusName, Type: app.Daemon, UdevAppName: app.SecurityTag(), Socket: app.Socket, SocketFileName: socketFileName, Restart: app.RestartCond, }), nil }
// FindServices finds all matching services (empty string matches all) // and lets you perform different actions (start, stop, etc) on them. // // If a snap is specified and no matching snaps are found, // ErrPackageNotFound is returned. If a snap is specified and the // matching snaps has no matching services, ErrServiceNotFound is // returned. // // If no snap is specified, an empty result is not an error. func FindServices(snapName string, serviceName string, pb progress.Meter) (ServiceActor, error) { var svcs []*svcT repo := NewMetaLocalRepository() installed, _ := repo.Installed() foundSnap := false for _, part := range installed { if !part.IsActive() { continue } snap, ok := part.(*SnapPart) if !ok { // can't happen continue } if snapName != "" && snapName != snap.Name() { continue } foundSnap = true yamls := snap.ServiceYamls() for i := range yamls { if serviceName != "" && serviceName != yamls[i].Name { continue } s := &svcT{ m: snap.m, svc: &yamls[i], } svcs = append(svcs, s) } } if snapName != "" { if !foundSnap { return nil, ErrPackageNotFound } if len(svcs) == 0 { return nil, ErrServiceNotFound } } return &serviceActor{ svcs: svcs, pb: pb, sysd: systemd.New(dirs.GlobalRootDir, pb), }, nil }
func removePackageServices(m *snapYaml, baseDir string, inter interacter) error { sysd := systemd.New(dirs.GlobalRootDir, inter) for _, app := range m.Apps { if app.Daemon == "" { continue } serviceName := filepath.Base(generateServiceFileName(m, app)) if err := sysd.Disable(serviceName); err != nil { return err } if err := sysd.Stop(serviceName, time.Duration(app.StopTimeout)); err != nil { if !systemd.IsTimeout(err) { return err } inter.Notify(fmt.Sprintf("%s refused to stop, killing.", serviceName)) // ignore errors for kill; nothing we'd do differently at this point sysd.Kill(serviceName, "TERM") time.Sleep(killWait) sysd.Kill(serviceName, "KILL") } if err := os.Remove(generateServiceFileName(m, app)); err != nil && !os.IsNotExist(err) { logger.Noticef("Failed to remove service file for %q: %v", serviceName, err) } if err := os.Remove(generateSocketFileName(m, app)); err != nil && !os.IsNotExist(err) { logger.Noticef("Failed to remove socket file for %q: %v", serviceName, err) } // Also remove DBus system policy file if err := os.Remove(generateBusPolicyFileName(m, app)); err != nil && !os.IsNotExist(err) { logger.Noticef("Failed to remove bus policy file for service %q: %v", serviceName, err) } } // only reload if we actually had services // FIXME: filter for services if len(m.Apps) > 0 { if err := sysd.DaemonReload(); err != nil { return err } } return nil }
func (m *packageYaml) removeSquashfsMount(baseDir string, inter interacter) error { sysd := systemd.New(dirs.GlobalRootDir, inter) unit := systemd.MountUnitPath(stripGlobalRootDir(baseDir), "mount") if helpers.FileExists(unit) { // we ignore errors, nothing should stop removals if err := sysd.Disable(filepath.Base(unit)); err != nil { logger.Noticef("Failed to disable %q: %s, but continuing anyway.", unit, err) } if err := sysd.Stop(filepath.Base(unit), time.Duration(1*time.Second)); err != nil { logger.Noticef("Failed to stop %q: %s, but continuing anyway.", unit, err) } if err := os.Remove(unit); err != nil { return err } } return nil }
func removePackageServices(s *snap.Info, inter interacter) error { sysd := systemd.New(dirs.GlobalRootDir, inter) nservices := 0 for _, app := range s.Apps { if app.Daemon == "" { continue } nservices++ serviceName := filepath.Base(app.ServiceFile()) if err := sysd.Disable(serviceName); err != nil { return err } if err := sysd.Stop(serviceName, serviceStopTimeout(app)); err != nil { if !systemd.IsTimeout(err) { return err } inter.Notify(fmt.Sprintf("%s refused to stop, killing.", serviceName)) // ignore errors for kill; nothing we'd do differently at this point sysd.Kill(serviceName, "TERM") time.Sleep(killWait) sysd.Kill(serviceName, "KILL") } if err := os.Remove(app.ServiceFile()); err != nil && !os.IsNotExist(err) { logger.Noticef("Failed to remove service file for %q: %v", serviceName, err) } if err := os.Remove(app.ServiceSocketFile()); err != nil && !os.IsNotExist(err) { logger.Noticef("Failed to remove socket file for %q: %v", serviceName, err) } } // only reload if we actually had services if nservices > 0 { if err := sysd.DaemonReload(); err != nil { return err } } return nil }
func generateSnapSocketFile(app *snap.AppInfo, baseDir string) (string, error) { if err := snap.ValidateApp(app); err != nil { return "", err } // lp: #1515709, systemd will default to 0666 if no socket mode // is specified if app.SocketMode == "" { app.SocketMode = "0660" } serviceFileName := filepath.Base(app.ServiceFile()) return systemd.New(dirs.GlobalRootDir, nil).GenSocketFile( &systemd.ServiceDescription{ ServiceFileName: serviceFileName, ListenStream: app.ListenStream, SocketMode: app.SocketMode, }), nil }
func (m *packageYaml) addSquashfsMount(baseDir string, inhibitHooks bool, inter interacter) error { squashfsPath := stripGlobalRootDir(squashfs.BlobPath(baseDir)) whereDir := stripGlobalRootDir(baseDir) sysd := systemd.New(dirs.GlobalRootDir, inter) mountUnitName, err := sysd.WriteMountUnitFile(m.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 }
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 }
func generateSnapSocketFile(app *AppYaml, baseDir string, aaProfile string, m *snapYaml) (string, error) { if err := verifyAppYaml(app); err != nil { return "", err } // lp: #1515709, systemd will default to 0666 if no socket mode // is specified if app.SocketMode == "" { app.SocketMode = "0660" } serviceFileName := filepath.Base(generateServiceFileName(m, app)) return systemd.New(dirs.GlobalRootDir, nil).GenSocketFile( &systemd.ServiceDescription{ ServiceFileName: serviceFileName, ListenStream: app.ListenStream, SocketMode: app.SocketMode, SocketUser: app.SocketUser, SocketGroup: app.SocketGroup, }), nil }
func generateSnapServicesFile(app *AppYaml, baseDir string, aaProfile string, m *snapYaml) (string, error) { if err := verifyAppYaml(app); err != nil { return "", err } udevPartName := m.qualifiedName(originFromBasedir(baseDir)) desc := app.Description if desc == "" { desc = fmt.Sprintf("service %s for package %s", app.Name, m.Name) } socketFileName := "" if app.Socket { socketFileName = filepath.Base(generateSocketFileName(m, app)) } return systemd.New(dirs.GlobalRootDir, nil).GenServiceFile( &systemd.ServiceDescription{ AppName: m.Name, ServiceName: app.Name, Version: m.Version, Description: desc, AppPath: baseDir, Start: app.Command, Stop: app.Stop, PostStop: app.PostStop, StopTimeout: time.Duration(app.StopTimeout), AaProfile: aaProfile, IsFramework: m.Type == snap.TypeFramework, IsNetworked: app.Ports != nil && len(app.Ports.External) > 0, BusName: app.BusName, Type: app.Daemon, UdevAppName: udevPartName, Socket: app.Socket, SocketFileName: socketFileName, Restart: app.RestartCond, }), nil }
func generateSnapServicesFile(service ServiceYaml, baseDir string, aaProfile string, m *packageYaml) (string, error) { if err := verifyServiceYaml(service); err != nil { return "", err } udevPartName := m.qualifiedName(originFromBasedir(baseDir)) desc := service.Description if desc == "" { desc = fmt.Sprintf("service %s for package %s", service.Name, m.Name) } socketFileName := "" if service.Socket { socketFileName = filepath.Base(generateSocketFileName(m, service)) } return systemd.New(dirs.GlobalRootDir, nil).GenServiceFile( &systemd.ServiceDescription{ AppName: m.Name, ServiceName: service.Name, Version: m.Version, Description: desc, AppPath: baseDir, Start: service.Start, Stop: service.Stop, PostStop: service.PostStop, StopTimeout: time.Duration(service.StopTimeout), AaProfile: aaProfile, IsFramework: m.Type == pkg.TypeFramework, IsNetworked: service.Ports != nil && len(service.Ports.External) > 0, BusName: service.BusName, Forking: service.Forking, UdevAppName: udevPartName, Socket: service.Socket, SocketFileName: socketFileName, }), nil }
// Install installs the snap func (s *SnapPart) Install(inter progress.Meter, flags InstallFlags) (name string, err error) { allowOEM := (flags & AllowOEM) != 0 inhibitHooks := (flags & InhibitHooks) != 0 if s.IsInstalled() { return "", ErrAlreadyInstalled } if err := s.CanInstall(allowOEM, inter); err != nil { return "", err } manifestData, err := s.deb.ControlMember("manifest") if err != nil { logger.Noticef("Snap inspect failed for %q: %v", s.Name(), err) return "", err } // the "oem" parts are special if s.Type() == pkg.TypeOem { if err := installOemHardwareUdevRules(s.m); err != nil { return "", err } } fullName := QualifiedName(s) dataDir := filepath.Join(dirs.SnapDataDir, fullName, s.Version()) var oldPart *SnapPart if currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current")); currentActiveDir != "" { oldPart, err = NewInstalledSnapPart(filepath.Join(currentActiveDir, "meta", "package.yaml"), s.origin) if err != nil { return "", err } } if err := os.MkdirAll(s.basedir, 0755); err != nil { logger.Noticef("Can not create %q: %v", s.basedir, err) return "", err } // if anything goes wrong here we cleanup defer func() { if err != nil { if e := os.RemoveAll(s.basedir); e != nil && !os.IsNotExist(e) { logger.Noticef("Failed to remove %q: %v", s.basedir, e) } } }() // we need to call the external helper so that we can reliable drop // privs if err := s.deb.UnpackWithDropPrivs(s.basedir, dirs.GlobalRootDir); err != nil { return "", err } // legacy, the hooks (e.g. apparmor) need this. Once we converted // all hooks this can go away clickMetaDir := filepath.Join(s.basedir, ".click", "info") if err := os.MkdirAll(clickMetaDir, 0755); err != nil { return "", err } if err := writeCompatManifestJSON(clickMetaDir, manifestData, s.origin); err != nil { return "", err } // write the hashes now if err := s.deb.ExtractHashes(filepath.Join(s.basedir, "meta")); err != nil { return "", err } // deal with the data: // // if there was a previous version, stop it // from being active so that it stops running and can no longer be // started then copy the data // // otherwise just create a empty data dir if oldPart != nil { // we need to stop making it active err = oldPart.deactivate(inhibitHooks, inter) defer func() { if err != nil { if cerr := oldPart.activate(inhibitHooks, inter); cerr != nil { logger.Noticef("Setting old version back to active failed: %v", cerr) } } }() if err != nil { return "", err } err = copySnapData(fullName, oldPart.Version(), s.Version()) } else { err = os.MkdirAll(dataDir, 0755) } defer func() { if err != nil { if cerr := removeSnapData(fullName, s.Version()); cerr != nil { logger.Noticef("When cleaning up data for %s %s: %v", s.Name(), s.Version(), cerr) } } }() if err != nil { return "", err } // and finally make active err = s.activate(inhibitHooks, inter) defer func() { if err != nil && oldPart != nil { if cerr := oldPart.activate(inhibitHooks, inter); cerr != nil { logger.Noticef("When setting old %s version back to active: %v", s.Name(), cerr) } } }() if err != nil { return "", err } // oh, one more thing: refresh the security bits if !inhibitHooks { deps, err := s.Dependents() if err != nil { return "", err } sysd := systemd.New(dirs.GlobalRootDir, inter) stopped := make(map[string]time.Duration) defer func() { if err != nil { for serviceName := range stopped { if e := sysd.Start(serviceName); e != nil { inter.Notify(fmt.Sprintf("unable to restart %s with the old %s: %s", serviceName, s.Name(), e)) } } } }() for _, dep := range deps { if !dep.IsActive() { continue } for _, svc := range dep.ServiceYamls() { serviceName := filepath.Base(generateServiceFileName(dep.m, svc)) timeout := time.Duration(svc.StopTimeout) if err = sysd.Stop(serviceName, timeout); err != nil { inter.Notify(fmt.Sprintf("unable to stop %s; aborting install: %s", serviceName, err)) return "", err } stopped[serviceName] = timeout } } if err := s.RefreshDependentsSecurity(oldPart, inter); err != nil { return "", err } started := make(map[string]time.Duration) defer func() { if err != nil { for serviceName, timeout := range started { if e := sysd.Stop(serviceName, timeout); e != nil { inter.Notify(fmt.Sprintf("unable to stop %s with the old %s: %s", serviceName, s.Name(), e)) } } } }() for serviceName, timeout := range stopped { if err = sysd.Start(serviceName); err != nil { inter.Notify(fmt.Sprintf("unable to restart %s; aborting install: %s", serviceName, err)) return "", err } started[serviceName] = timeout } } return s.Name(), nil }
func addPackageServices(m *snapYaml, baseDir string, inhibitHooks bool, inter interacter) error { for _, app := range m.Apps { if app.Daemon == "" { continue } aaProfile, err := getSecurityProfile(m, app.Name, baseDir) if err != nil { return err } // this will remove the global base dir when generating the // service file, this ensures that /snaps/foo/1.0/bin/start // is in the service file when the SetRoot() option // is used realBaseDir := stripGlobalRootDir(baseDir) // Generate service file content, err := generateSnapServicesFile(app, realBaseDir, aaProfile, m) if err != nil { return err } serviceFilename := generateServiceFileName(m, app) os.MkdirAll(filepath.Dir(serviceFilename), 0755) if err := helpers.AtomicWriteFile(serviceFilename, []byte(content), 0644, 0); err != nil { return err } // Generate systemd socket file if needed if app.Socket { content, err := generateSnapSocketFile(app, realBaseDir, aaProfile, m) if err != nil { return err } socketFilename := generateSocketFileName(m, app) os.MkdirAll(filepath.Dir(socketFilename), 0755) if err := helpers.AtomicWriteFile(socketFilename, []byte(content), 0644, 0); err != nil { return err } } // If necessary, generate the DBus policy file so the framework // service is allowed to start if m.Type == snap.TypeFramework && app.BusName != "" { content, err := genBusPolicyFile(app.BusName) if err != nil { return err } policyFilename := generateBusPolicyFileName(m, app) os.MkdirAll(filepath.Dir(policyFilename), 0755) if err := helpers.AtomicWriteFile(policyFilename, []byte(content), 0644, 0); err != nil { return err } } // daemon-reload and start only if we are not in the // inhibitHooks mode // // *but* always run enable (which just sets a symlink) serviceName := filepath.Base(generateServiceFileName(m, app)) sysd := systemd.New(dirs.GlobalRootDir, inter) if !inhibitHooks { if err := sysd.DaemonReload(); err != nil { return err } } // we always enable the service even in inhibit hooks if err := sysd.Enable(serviceName); err != nil { return err } if !inhibitHooks { if err := sysd.Start(serviceName); err != nil { return err } } if app.Socket { socketName := filepath.Base(generateSocketFileName(m, app)) // we always enable the socket even in inhibit hooks if err := sysd.Enable(socketName); err != nil { return err } if !inhibitHooks { if err := sysd.Start(socketName); err != nil { return err } } } } return nil }
func addPackageServices(s *snap.Info, inter interacter) error { baseDir := s.MountDir() for _, app := range s.Apps { if app.Daemon == "" { continue } // this will remove the global base dir when generating the // service file, this ensures that /snap/foo/1.0/bin/start // is in the service file when the SetRoot() option // is used realBaseDir := stripGlobalRootDir(baseDir) // Generate service file content, err := generateSnapServicesFile(app, realBaseDir) if err != nil { return err } svcFilePath := app.ServiceFile() os.MkdirAll(filepath.Dir(svcFilePath), 0755) if err := osutil.AtomicWriteFile(svcFilePath, []byte(content), 0644, 0); err != nil { return err } // Generate systemd socket file if needed if app.Socket { content, err := generateSnapSocketFile(app, realBaseDir) if err != nil { return err } svcSocketFilePath := app.ServiceSocketFile() os.MkdirAll(filepath.Dir(svcSocketFilePath), 0755) if err := osutil.AtomicWriteFile(svcSocketFilePath, []byte(content), 0644, 0); err != nil { return err } } // daemon-reload and enable plus start serviceName := filepath.Base(app.ServiceFile()) sysd := systemd.New(dirs.GlobalRootDir, inter) if err := sysd.DaemonReload(); err != nil { return err } // enable the service if err := sysd.Enable(serviceName); err != nil { return err } if err := sysd.Start(serviceName); err != nil { return err } if app.Socket { socketName := filepath.Base(app.ServiceSocketFile()) // enable the socket if err := sysd.Enable(socketName); err != nil { return err } if err := sysd.Start(socketName); err != nil { return err } } } return nil }
// Install installs the snap func (s *SnapFile) Install(inter progress.Meter, flags InstallFlags) (name string, err error) { allowGadget := (flags & AllowGadget) != 0 inhibitHooks := (flags & InhibitHooks) != 0 // we do not Verify() the package here. This is done earlier in // NewSnapFile() to ensure that we do not mount/inspect // potentially dangerous snaps if err := s.CanInstall(allowGadget, inter); err != nil { return "", err } // the "gadget" parts are special if s.Type() == snap.TypeGadget { if err := installGadgetHardwareUdevRules(s.m); err != nil { return "", err } } fullName := QualifiedName(s) dataDir := filepath.Join(dirs.SnapDataDir, fullName, s.Version()) var oldPart *SnapPart if currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.instdir, "..", "current")); currentActiveDir != "" { oldPart, err = NewInstalledSnapPart(filepath.Join(currentActiveDir, "meta", "package.yaml"), s.origin) if err != nil { return "", err } } if err := os.MkdirAll(s.instdir, 0755); err != nil { logger.Noticef("Can not create %q: %v", s.instdir, err) return "", err } // if anything goes wrong here we cleanup defer func() { if err != nil { if e := os.RemoveAll(s.instdir); e != nil && !os.IsNotExist(e) { logger.Noticef("Failed to remove %q: %v", s.instdir, e) } } }() // we need to call the external helper so that we can reliable drop // privs if err := s.deb.Install(s.instdir); err != nil { return "", err } // generate the mount unit for the squashfs if err := s.m.addSquashfsMount(s.instdir, inhibitHooks, inter); err != nil { return "", err } // if anything goes wrong we ensure we stop defer func() { if err != nil { if e := s.m.removeSquashfsMount(s.instdir, inter); e != nil { logger.Noticef("Failed to remove mount unit for %s: %s", fullName, e) } } }() // FIXME: special handling is bad 'mkay if s.m.Type == snap.TypeKernel { if err := extractKernelAssets(s, inter, flags); err != nil { return "", fmt.Errorf("failed to install kernel %s", err) } } // deal with the data: // // if there was a previous version, stop it // from being active so that it stops running and can no longer be // started then copy the data // // otherwise just create a empty data dir if oldPart != nil { // we need to stop making it active err = oldPart.deactivate(inhibitHooks, inter) defer func() { if err != nil { if cerr := oldPart.activate(inhibitHooks, inter); cerr != nil { logger.Noticef("Setting old version back to active failed: %v", cerr) } } }() if err != nil { return "", err } err = copySnapData(fullName, oldPart.Version(), s.Version()) } else { err = os.MkdirAll(dataDir, 0755) } defer func() { if err != nil { if cerr := removeSnapData(fullName, s.Version()); cerr != nil { logger.Noticef("When cleaning up data for %s %s: %v", s.Name(), s.Version(), cerr) } } }() if err != nil { return "", err } if !inhibitHooks { newPart, err := newSnapPartFromYaml(filepath.Join(s.instdir, "meta", "package.yaml"), s.origin, s.m) if err != nil { return "", err } // and finally make active err = newPart.activate(inhibitHooks, inter) defer func() { if err != nil && oldPart != nil { if cerr := oldPart.activate(inhibitHooks, inter); cerr != nil { logger.Noticef("When setting old %s version back to active: %v", s.Name(), cerr) } } }() if err != nil { return "", err } // oh, one more thing: refresh the security bits deps, err := newPart.Dependents() if err != nil { return "", err } sysd := systemd.New(dirs.GlobalRootDir, inter) stopped := make(map[string]time.Duration) defer func() { if err != nil { for serviceName := range stopped { if e := sysd.Start(serviceName); e != nil { inter.Notify(fmt.Sprintf("unable to restart %s with the old %s: %s", serviceName, s.Name(), e)) } } } }() for _, dep := range deps { if !dep.IsActive() { continue } for _, svc := range dep.ServiceYamls() { serviceName := filepath.Base(generateServiceFileName(dep.m, svc)) timeout := time.Duration(svc.StopTimeout) if err = sysd.Stop(serviceName, timeout); err != nil { inter.Notify(fmt.Sprintf("unable to stop %s; aborting install: %s", serviceName, err)) return "", err } stopped[serviceName] = timeout } } if err := newPart.RefreshDependentsSecurity(oldPart, inter); err != nil { return "", err } started := make(map[string]time.Duration) defer func() { if err != nil { for serviceName, timeout := range started { if e := sysd.Stop(serviceName, timeout); e != nil { inter.Notify(fmt.Sprintf("unable to stop %s with the old %s: %s", serviceName, s.Name(), e)) } } } }() for serviceName, timeout := range stopped { if err = sysd.Start(serviceName); err != nil { inter.Notify(fmt.Sprintf("unable to restart %s; aborting install: %s", serviceName, err)) return "", err } started[serviceName] = timeout } } return s.Name(), nil }