github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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/timings" 33 "github.com/snapcore/snapd/wrappers" 34 ) 35 36 // LinkContext carries additional information about the current or the previous 37 // state of the snap 38 type LinkContext struct { 39 // FirstInstall indicates whether this is the first time given snap is 40 // installed 41 FirstInstall bool 42 43 // VitalityRank is used to hint how much the services should be 44 // protected from the OOM killer 45 VitalityRank int 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 // add the daemons from the snap.yaml 196 opts := &wrappers.AddSnapServicesOptions{ 197 Preseeding: b.preseed, 198 VitalityRank: linkCtx.VitalityRank, 199 RequireMountedSnapdSnap: linkCtx.RequireMountedSnapdSnap, 200 } 201 if err = wrappers.AddSnapServices(s, opts, progress.Null); err != nil { 202 return err 203 } 204 cleanupFuncs = append(cleanupFuncs, func(s *snap.Info) error { 205 return wrappers.RemoveSnapServices(s, progress.Null) 206 }) 207 208 // add D-Bus service activation files 209 if err = wrappers.AddSnapDBusActivationFiles(s); err != nil { 210 return err 211 } 212 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDBusActivationFiles) 213 214 // add the desktop files 215 if err = wrappers.AddSnapDesktopFiles(s); err != nil { 216 return err 217 } 218 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDesktopFiles) 219 220 // add the desktop icons 221 if err = wrappers.AddSnapIcons(s); err != nil { 222 return err 223 } 224 cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapIcons) 225 226 return nil 227 } 228 229 func removeGeneratedWrappers(s *snap.Info, firstInstallUndo bool, meter progress.Meter) error { 230 if s.Type() == snap.TypeSnapd { 231 return removeGeneratedSnapdWrappers(s, firstInstallUndo, progress.Null) 232 } 233 234 err1 := wrappers.RemoveSnapBinaries(s) 235 if err1 != nil { 236 logger.Noticef("Cannot remove binaries for %q: %v", s.InstanceName(), err1) 237 } 238 239 err2 := wrappers.RemoveSnapDBusActivationFiles(s) 240 if err2 != nil { 241 logger.Noticef("Cannot remove D-Bus activation for %q: %v", s.InstanceName(), err2) 242 } 243 244 err3 := wrappers.RemoveSnapServices(s, meter) 245 if err3 != nil { 246 logger.Noticef("Cannot remove services for %q: %v", s.InstanceName(), err3) 247 } 248 249 err4 := wrappers.RemoveSnapDesktopFiles(s) 250 if err4 != nil { 251 logger.Noticef("Cannot remove desktop files for %q: %v", s.InstanceName(), err4) 252 } 253 254 err5 := wrappers.RemoveSnapIcons(s) 255 if err5 != nil { 256 logger.Noticef("Cannot remove desktop icons for %q: %v", s.InstanceName(), err5) 257 } 258 259 return firstErr(err1, err2, err3, err4, err5) 260 } 261 262 func GenerateSnapdWrappers(s *snap.Info) error { 263 // snapd services are handled separately via an explicit helper 264 return wrappers.AddSnapdSnapServices(s, progress.Null) 265 } 266 267 func removeGeneratedSnapdWrappers(s *snap.Info, firstInstall bool, meter progress.Meter) error { 268 if !firstInstall { 269 // snapd service units are only removed during first 270 // installation of the snapd snap, in other scenarios they are 271 // overwritten 272 return nil 273 } 274 return wrappers.RemoveSnapdSnapServicesOnCore(s, meter) 275 } 276 277 // UnlinkSnap makes the snap unavailable to the system removing wrappers and 278 // symlinks. The firstInstallUndo is true when undoing the first installation of 279 // the snap. 280 func (b Backend) UnlinkSnap(info *snap.Info, linkCtx LinkContext, meter progress.Meter) error { 281 var err0 error 282 if hint := linkCtx.RunInhibitHint; hint != runinhibit.HintNotInhibited { 283 // inhibit startup of new programs 284 err0 = runinhibit.LockWithHint(info.InstanceName(), hint) 285 } 286 287 // remove generated services, binaries etc 288 err1 := removeGeneratedWrappers(info, linkCtx.FirstInstall, meter) 289 290 // and finally remove current symlinks 291 err2 := removeCurrentSymlinks(info) 292 293 // FIXME: aggregate errors instead 294 return firstErr(err0, err1, err2) 295 } 296 297 // ServicesEnableState returns the current enabled/disabled states of a snap's 298 // services, primarily for committing before snap removal/disable/revert. 299 func (b Backend) ServicesEnableState(info *snap.Info, meter progress.Meter) (map[string]bool, error) { 300 return wrappers.ServicesEnableState(info, meter) 301 } 302 303 func (b Backend) QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) { 304 return wrappers.QueryDisabledServices(info, pb) 305 } 306 307 func removeCurrentSymlinks(info snap.PlaceInfo) error { 308 var err1, err2 error 309 310 // the snap "current" symlink 311 currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current") 312 err1 = os.Remove(currentActiveSymlink) 313 if err1 != nil && !os.IsNotExist(err1) { 314 logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err1) 315 } else { 316 err1 = nil 317 } 318 319 // the data "current" symlink 320 currentDataSymlink := filepath.Join(info.DataDir(), "..", "current") 321 err2 = os.Remove(currentDataSymlink) 322 if err2 != nil && !os.IsNotExist(err2) { 323 logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err2) 324 } else { 325 err2 = nil 326 } 327 328 if err1 != nil && err2 != nil { 329 return fmt.Errorf("cannot remove snap current symlink: %v and %v", err1, err2) 330 } else if err1 != nil { 331 return fmt.Errorf("cannot remove snap current symlink: %v", err1) 332 } else if err2 != nil { 333 return fmt.Errorf("cannot remove snap current symlink: %v", err2) 334 } 335 336 return nil 337 }