github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/interfaces/systemd/backend.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2021 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 systemd implements integration between snappy interfaces and 21 // arbitrary systemd units that may be required for "oneshot" style tasks. 22 package systemd 23 24 import ( 25 "fmt" 26 "os" 27 "path/filepath" 28 "time" 29 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/interfaces" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/osutil" 34 "github.com/snapcore/snapd/snap" 35 sysd "github.com/snapcore/snapd/systemd" 36 "github.com/snapcore/snapd/timings" 37 ) 38 39 func serviceName(snapName, distinctServiceSuffix string) string { 40 return snap.ScopedSecurityTag(snapName, "interface", distinctServiceSuffix) + ".service" 41 } 42 43 // Backend is responsible for maintaining apparmor profiles for ubuntu-core-launcher. 44 type Backend struct { 45 preseed bool 46 } 47 48 // Initialize does nothing. 49 func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error { 50 if opts != nil && opts.Preseed { 51 b.preseed = true 52 } 53 return nil 54 } 55 56 // Name returns the name of the backend. 57 func (b *Backend) Name() interfaces.SecuritySystem { 58 return interfaces.SecuritySystemd 59 } 60 61 // Setup creates and starts systemd services specific to a given snap. 62 // 63 // This method should be called after changing plug, slots, connections between 64 // them or application present in the snap. 65 func (b *Backend) Setup(snapInfo *snap.Info, confinement interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error { 66 // Record all the extra systemd services for this snap. 67 snapName := snapInfo.InstanceName() 68 // Get the services that apply to this snap 69 spec, err := repo.SnapSpecification(b.Name(), snapName) 70 if err != nil { 71 return fmt.Errorf("cannot obtain systemd services for snap %q: %s", snapName, err) 72 } 73 content := deriveContent(spec.(*Specification), snapInfo) 74 // synchronize the content with the filesystem 75 dir := dirs.SnapServicesDir 76 if err := os.MkdirAll(dir, 0755); err != nil { 77 return fmt.Errorf("cannot create directory for systemd services %q: %s", dir, err) 78 } 79 glob := serviceName(snapName, "*") 80 81 var systemd sysd.Systemd 82 if b.preseed { 83 systemd = sysd.NewEmulationMode(dirs.GlobalRootDir) 84 } else { 85 systemd = sysd.New(sysd.SystemMode, &dummyReporter{}) 86 } 87 88 // We need to be carefully here and stop all removed service units before 89 // we remove their files as otherwise systemd is not able to disable/stop 90 // them anymore. 91 if err := b.disableRemovedServices(systemd, dir, glob, content); err != nil { 92 logger.Noticef("cannot stop removed services: %s", err) 93 } 94 changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content) 95 // Reload systemd whenever something is added or removed 96 if !b.preseed && (len(changed) > 0 || len(removed) > 0) { 97 err := systemd.DaemonReload() 98 if err != nil { 99 logger.Noticef("cannot reload systemd state: %s", err) 100 } 101 } 102 // Ensure the service is running right now and on reboots 103 for _, service := range changed { 104 if err := systemd.Enable(service); err != nil { 105 logger.Noticef("cannot enable service %q: %s", service, err) 106 } 107 if !b.preseed { 108 // If we have a new service here which isn't started yet the restart 109 // operation will start it. 110 if err := systemd.Restart(service, 10*time.Second); err != nil { 111 logger.Noticef("cannot restart service %q: %s", service, err) 112 } 113 } 114 } 115 return errEnsure 116 } 117 118 // Remove disables, stops and removes systemd services of a given snap. 119 func (b *Backend) Remove(snapName string) error { 120 var systemd sysd.Systemd 121 if b.preseed { 122 // removing while preseeding is not a viable scenario, but implemented 123 // for completness. 124 systemd = sysd.NewEmulationMode(dirs.GlobalRootDir) 125 } else { 126 systemd = sysd.New(sysd.SystemMode, &dummyReporter{}) 127 } 128 // Remove all the files matching snap glob 129 glob := serviceName(snapName, "*") 130 _, removed, errEnsure := osutil.EnsureDirState(dirs.SnapServicesDir, glob, nil) 131 for _, service := range removed { 132 if err := systemd.Disable(service); err != nil { 133 logger.Noticef("cannot disable service %q: %s", service, err) 134 } 135 if !b.preseed { 136 if err := systemd.Stop(service, 5*time.Second); err != nil { 137 logger.Noticef("cannot stop service %q: %s", service, err) 138 } 139 } 140 } 141 // Reload systemd whenever something is removed 142 if !b.preseed && len(removed) > 0 { 143 err := systemd.DaemonReload() 144 if err != nil { 145 logger.Noticef("cannot reload systemd state: %s", err) 146 } 147 } 148 return errEnsure 149 } 150 151 // NewSpecification returns a new systemd specification. 152 func (b *Backend) NewSpecification() interfaces.Specification { 153 return &Specification{} 154 } 155 156 // SandboxFeatures returns nil 157 func (b *Backend) SandboxFeatures() []string { 158 return nil 159 } 160 161 // deriveContent computes .service files based on requests made to the specification. 162 func deriveContent(spec *Specification, snapInfo *snap.Info) map[string]osutil.FileState { 163 services := spec.Services() 164 if len(services) == 0 { 165 return nil 166 } 167 content := make(map[string]osutil.FileState) 168 for suffix, service := range services { 169 filename := serviceName(snapInfo.InstanceName(), suffix) 170 content[filename] = &osutil.MemoryFileState{ 171 Content: []byte(service.String()), 172 Mode: 0644, 173 } 174 } 175 return content 176 } 177 178 func (b *Backend) disableRemovedServices(systemd sysd.Systemd, dir, glob string, content map[string]osutil.FileState) error { 179 paths, err := filepath.Glob(filepath.Join(dir, glob)) 180 if err != nil { 181 return err 182 } 183 for _, path := range paths { 184 service := filepath.Base(path) 185 if content[service] == nil { 186 if err := systemd.Disable(service); err != nil { 187 logger.Noticef("cannot disable service %q: %s", service, err) 188 } 189 if !b.preseed { 190 if err := systemd.Stop(service, 5*time.Second); err != nil { 191 logger.Noticef("cannot stop service %q: %s", service, err) 192 } 193 } 194 } 195 } 196 return nil 197 } 198 199 type dummyReporter struct{} 200 201 func (dr *dummyReporter) Notify(msg string) { 202 }