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