github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/backend/link.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2016 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 backend 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 27 "github.com/snapcore/snapd/boot" 28 "github.com/snapcore/snapd/cmd/snaplock/runinhibit" 29 "github.com/snapcore/snapd/logger" 30 "github.com/snapcore/snapd/progress" 31 "github.com/snapcore/snapd/snap" 32 "github.com/snapcore/snapd/snap/quota" 33 "github.com/snapcore/snapd/timings" 34 "github.com/snapcore/snapd/wrappers" 35 ) 36 37 // LinkContext carries additional information about the current or the previous 38 // state of the snap 39 type LinkContext struct { 40 // FirstInstall indicates whether this is the first time given snap is 41 // installed 42 FirstInstall bool 43 44 // ServiceOptions is used to configure services. 45 ServiceOptions *wrappers.SnapServiceOptions 46 47 // RunInhibitHint is used only in Unlink snap, and can be used to 48 // establish run inhibition lock for refresh operations. 49 RunInhibitHint runinhibit.Hint 50 51 // RequireMountedSnapdSnap indicates that the apps and services 52 // generated when linking need to use tooling from the snapd snap mount. 53 RequireMountedSnapdSnap bool 54 } 55 56 func updateCurrentSymlinks(info *snap.Info) (e error) { 57 mountDir := info.MountDir() 58 59 currentActiveSymlink := filepath.Join(mountDir, "..", "current") 60 if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) { 61 logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err) 62 } 63 64 dataDir := info.DataDir() 65 currentDataSymlink := filepath.Join(dataDir, "..", "current") 66 if err := os.Remove(currentDataSymlink); err != nil && !os.IsNotExist(err) { 67 logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err) 68 } 69 70 if err := os.MkdirAll(dataDir, 0755); err != nil { 71 return err 72 } 73 cleanup := []string{dataDir, ""}[:1] // cleanup has cap(2) 74 defer func() { 75 if e == nil { 76 return 77 } 78 for _, d := range cleanup { 79 if err := os.Remove(d); err != nil { 80 logger.Noticef("Cannot clean up %q: %v", d, err) 81 } 82 } 83 }() 84 85 if err := os.Symlink(filepath.Base(dataDir), currentDataSymlink); err != nil { 86 return err 87 } 88 cleanup = append(cleanup, currentDataSymlink) 89 90 return os.Symlink(filepath.Base(mountDir), currentActiveSymlink) 91 } 92 93 func hasFontConfigCache(info *snap.Info) bool { 94 if info.Type() == snap.TypeOS || info.Type() == snap.TypeSnapd { 95 return true 96 } 97 return false 98 } 99 100 // LinkSnap makes the snap available by generating wrappers and setting the current symlinks. 101 func (b Backend) LinkSnap(info *snap.Info, dev boot.Device, linkCtx LinkContext, tm timings.Measurer) (rebootRequired bool, e error) { 102 if info.Revision.Unset() { 103 return false, fmt.Errorf("cannot link snap %q with unset revision", info.InstanceName()) 104 } 105 106 var err error 107 timings.Run(tm, "generate-wrappers", fmt.Sprintf("generate wrappers for snap %s", info.InstanceName()), func(timings.Measurer) { 108 err = b.generateWrappers(info, linkCtx) 109 }) 110 if err != nil { 111 return false, err 112 } 113 defer func() { 114 if e == nil { 115 return 116 } 117 timings.Run(tm, "remove-wrappers", fmt.Sprintf("remove wrappers of snap %s", info.InstanceName()), func(timings.Measurer) { 118 removeGeneratedWrappers(info, linkCtx.FirstInstall, progress.Null) 119 }) 120 }() 121 122 // fontconfig is only relevant on classic and is carried by 'core' or 123 // 'snapd' snaps 124 // for non-core snaps, fontconfig cache needs to be updated before the 125 // snap applications are runnable 126 if dev.Classic() && !hasFontConfigCache(info) { 127 timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) { 128 // XXX: does this need cleaning up? (afaict no) 129 if err := updateFontconfigCaches(); err != nil { 130 logger.Noticef("cannot update fontconfig cache: %v", err) 131 } 132 }) 133 } 134 135 reboot, err := boot.Participant(info, info.Type(), dev).SetNextBoot() 136 if err != nil { 137 return false, err 138 } 139 140 if err := updateCurrentSymlinks(info); err != nil { 141 return false, err 142 } 143 // if anything below here could return error, you need to 144 // somehow clean up whatever updateCurrentSymlinks did 145 146 // for core snap, fontconfig cache can be updated after the snap has 147 // been made available 148 if dev.Classic() && hasFontConfigCache(info) { 149 timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) { 150 if err := updateFontconfigCaches(); err != nil { 151 logger.Noticef("cannot update fontconfig cache: %v", err) 152 } 153 }) 154 } 155 156 // Stop inhibiting application startup by removing the inhibitor file. 157 if err := runinhibit.Unlock(info.InstanceName()); err != nil { 158 return false, err 159 } 160 161 return reboot, nil 162 } 163 164 func (b Backend) StartServices(apps []*snap.AppInfo, disabledSvcs []string, meter progress.Meter, tm timings.Measurer) error { 165 flags := &wrappers.StartServicesFlags{Enable: true} 166 return wrappers.StartServices(apps, disabledSvcs, flags, meter, tm) 167 } 168 169 func (b Backend) StopServices(apps []*snap.AppInfo, reason snap.ServiceStopReason, meter progress.Meter, tm timings.Measurer) error { 170 return wrappers.StopServices(apps, nil, reason, meter, tm) 171 } 172 173 func (b Backend) generateWrappers(s *snap.Info, linkCtx LinkContext) error { 174 var err error 175 var cleanupFuncs []func(*snap.Info) error 176 defer func() { 177 if err != nil { 178 for _, cleanup := range cleanupFuncs { 179 cleanup(s) 180 } 181 } 182 }() 183 184 if s.Type() == snap.TypeSnapd { 185 // snapd services are handled separately 186 return GenerateSnapdWrappers(s) 187 } 188 189 // add the CLI apps from the snap.yaml 190 if err = wrappers.AddSnapBinaries(s); err != nil { 191 return err 192 } 193 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapBinaries) 194 195 vitalityRank := 0 196 var quotaGrp *quota.Group 197 if linkCtx.ServiceOptions != nil { 198 vitalityRank = linkCtx.ServiceOptions.VitalityRank 199 quotaGrp = linkCtx.ServiceOptions.QuotaGroup 200 } 201 // add the daemons from the snap.yaml 202 opts := &wrappers.AddSnapServicesOptions{ 203 VitalityRank: vitalityRank, 204 Preseeding: b.preseed, 205 RequireMountedSnapdSnap: linkCtx.RequireMountedSnapdSnap, 206 QuotaGroup: quotaGrp, 207 } 208 // TODO: switch to EnsureSnapServices 209 if err = wrappers.AddSnapServices(s, opts, progress.Null); err != nil { 210 return err 211 } 212 cleanupFuncs = append(cleanupFuncs, func(s *snap.Info) error { 213 return wrappers.RemoveSnapServices(s, progress.Null) 214 }) 215 216 // add D-Bus service activation files 217 if err = wrappers.AddSnapDBusActivationFiles(s); err != nil { 218 return err 219 } 220 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDBusActivationFiles) 221 222 // add the desktop files 223 if err = wrappers.AddSnapDesktopFiles(s); err != nil { 224 return err 225 } 226 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDesktopFiles) 227 228 // add the desktop icons 229 if err = wrappers.AddSnapIcons(s); err != nil { 230 return err 231 } 232 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapIcons) 233 234 return nil 235 } 236 237 func removeGeneratedWrappers(s *snap.Info, firstInstallUndo bool, meter progress.Meter) error { 238 if s.Type() == snap.TypeSnapd { 239 return removeGeneratedSnapdWrappers(s, firstInstallUndo, progress.Null) 240 } 241 242 err1 := wrappers.RemoveSnapBinaries(s) 243 if err1 != nil { 244 logger.Noticef("Cannot remove binaries for %q: %v", s.InstanceName(), err1) 245 } 246 247 err2 := wrappers.RemoveSnapDBusActivationFiles(s) 248 if err2 != nil { 249 logger.Noticef("Cannot remove D-Bus activation for %q: %v", s.InstanceName(), err2) 250 } 251 252 err3 := wrappers.RemoveSnapServices(s, meter) 253 if err3 != nil { 254 logger.Noticef("Cannot remove services for %q: %v", s.InstanceName(), err3) 255 } 256 257 err4 := wrappers.RemoveSnapDesktopFiles(s) 258 if err4 != nil { 259 logger.Noticef("Cannot remove desktop files for %q: %v", s.InstanceName(), err4) 260 } 261 262 err5 := wrappers.RemoveSnapIcons(s) 263 if err5 != nil { 264 logger.Noticef("Cannot remove desktop icons for %q: %v", s.InstanceName(), err5) 265 } 266 267 return firstErr(err1, err2, err3, err4, err5) 268 } 269 270 func GenerateSnapdWrappers(s *snap.Info) error { 271 // snapd services are handled separately via an explicit helper 272 return wrappers.AddSnapdSnapServices(s, progress.Null) 273 } 274 275 func removeGeneratedSnapdWrappers(s *snap.Info, firstInstall bool, meter progress.Meter) error { 276 if !firstInstall { 277 // snapd service units are only removed during first 278 // installation of the snapd snap, in other scenarios they are 279 // overwritten 280 return nil 281 } 282 return wrappers.RemoveSnapdSnapServicesOnCore(s, meter) 283 } 284 285 // UnlinkSnap makes the snap unavailable to the system removing wrappers and 286 // symlinks. The firstInstallUndo is true when undoing the first installation of 287 // the snap. 288 func (b Backend) UnlinkSnap(info *snap.Info, linkCtx LinkContext, meter progress.Meter) error { 289 var err0 error 290 if hint := linkCtx.RunInhibitHint; hint != runinhibit.HintNotInhibited { 291 // inhibit startup of new programs 292 err0 = runinhibit.LockWithHint(info.InstanceName(), hint) 293 } 294 295 // remove generated services, binaries etc 296 err1 := removeGeneratedWrappers(info, linkCtx.FirstInstall, meter) 297 298 // and finally remove current symlinks 299 err2 := removeCurrentSymlinks(info) 300 301 // FIXME: aggregate errors instead 302 return firstErr(err0, err1, err2) 303 } 304 305 // ServicesEnableState returns the current enabled/disabled states of a snap's 306 // services, primarily for committing before snap removal/disable/revert. 307 func (b Backend) ServicesEnableState(info *snap.Info, meter progress.Meter) (map[string]bool, error) { 308 return wrappers.ServicesEnableState(info, meter) 309 } 310 311 func (b Backend) QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) { 312 return wrappers.QueryDisabledServices(info, pb) 313 } 314 315 func removeCurrentSymlinks(info snap.PlaceInfo) error { 316 var err1, err2 error 317 318 // the snap "current" symlink 319 currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current") 320 err1 = os.Remove(currentActiveSymlink) 321 if err1 != nil && !os.IsNotExist(err1) { 322 logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err1) 323 } else { 324 err1 = nil 325 } 326 327 // the data "current" symlink 328 currentDataSymlink := filepath.Join(info.DataDir(), "..", "current") 329 err2 = os.Remove(currentDataSymlink) 330 if err2 != nil && !os.IsNotExist(err2) { 331 logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err2) 332 } else { 333 err2 = nil 334 } 335 336 if err1 != nil && err2 != nil { 337 return fmt.Errorf("cannot remove snap current symlink: %v and %v", err1, err2) 338 } else if err1 != nil { 339 return fmt.Errorf("cannot remove snap current symlink: %v", err1) 340 } else if err2 != nil { 341 return fmt.Errorf("cannot remove snap current symlink: %v", err2) 342 } 343 344 return nil 345 }