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