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