github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/snap.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 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 daemon 21 22 import ( 23 "errors" 24 "fmt" 25 "os" 26 "path/filepath" 27 "sort" 28 "strings" 29 30 "github.com/snapcore/snapd/client" 31 "github.com/snapcore/snapd/client/clientutil" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/overlord/assertstate" 34 "github.com/snapcore/snapd/overlord/healthstate" 35 "github.com/snapcore/snapd/overlord/snapstate" 36 "github.com/snapcore/snapd/overlord/state" 37 "github.com/snapcore/snapd/snap" 38 ) 39 40 var errNoSnap = errors.New("snap not installed") 41 42 // snapIcon tries to find the icon inside the snap 43 func snapIcon(info snap.PlaceInfo) string { 44 found, _ := filepath.Glob(filepath.Join(info.MountDir(), "meta", "gui", "icon.*")) 45 if len(found) == 0 { 46 return "" 47 } 48 49 return found[0] 50 } 51 52 func publisherAccount(st *state.State, snapID string) (snap.StoreAccount, error) { 53 if snapID == "" { 54 return snap.StoreAccount{}, nil 55 } 56 57 pubAcct, err := assertstate.Publisher(st, snapID) 58 if err != nil { 59 return snap.StoreAccount{}, fmt.Errorf("cannot find publisher details: %v", err) 60 } 61 return snap.StoreAccount{ 62 ID: pubAcct.AccountID(), 63 Username: pubAcct.Username(), 64 DisplayName: pubAcct.DisplayName(), 65 Validation: pubAcct.Validation(), 66 }, nil 67 } 68 69 type aboutSnap struct { 70 info *snap.Info 71 snapst *snapstate.SnapState 72 health *client.SnapHealth 73 } 74 75 func clientHealthFromHealthstate(h *healthstate.HealthState) *client.SnapHealth { 76 if h == nil { 77 return nil 78 } 79 return &client.SnapHealth{ 80 Revision: h.Revision, 81 Timestamp: h.Timestamp, 82 Status: h.Status.String(), 83 Message: h.Message, 84 Code: h.Code, 85 } 86 } 87 88 // localSnapInfo returns the information about the current snap for the given name plus the SnapState with the active flag and other snap revisions. 89 func localSnapInfo(st *state.State, name string) (aboutSnap, error) { 90 st.Lock() 91 defer st.Unlock() 92 93 var snapst snapstate.SnapState 94 err := snapstate.Get(st, name, &snapst) 95 if err != nil && err != state.ErrNoState { 96 return aboutSnap{}, fmt.Errorf("cannot consult state: %v", err) 97 } 98 99 info, err := snapst.CurrentInfo() 100 if err == snapstate.ErrNoCurrent { 101 return aboutSnap{}, errNoSnap 102 } 103 if err != nil { 104 return aboutSnap{}, fmt.Errorf("cannot read snap details: %v", err) 105 } 106 107 info.Publisher, err = publisherAccount(st, info.SnapID) 108 if err != nil { 109 return aboutSnap{}, err 110 } 111 112 health, err := healthstate.Get(st, name) 113 if err != nil { 114 return aboutSnap{}, err 115 } 116 117 return aboutSnap{ 118 info: info, 119 snapst: &snapst, 120 health: clientHealthFromHealthstate(health), 121 }, nil 122 } 123 124 // allLocalSnapInfos returns the information about the all current snaps and their SnapStates. 125 func allLocalSnapInfos(st *state.State, all bool, wanted map[string]bool) ([]aboutSnap, error) { 126 st.Lock() 127 defer st.Unlock() 128 129 snapStates, err := snapstate.All(st) 130 if err != nil { 131 return nil, err 132 } 133 about := make([]aboutSnap, 0, len(snapStates)) 134 135 healths, err := healthstate.All(st) 136 if err != nil { 137 return nil, err 138 } 139 140 var firstErr error 141 for name, snapst := range snapStates { 142 if len(wanted) > 0 && !wanted[name] { 143 continue 144 } 145 health := clientHealthFromHealthstate(healths[name]) 146 var aboutThis []aboutSnap 147 var info *snap.Info 148 var err error 149 if all { 150 for _, seq := range snapst.Sequence { 151 info, err = snap.ReadInfo(name, seq) 152 if err != nil { 153 // single revision may be broken 154 _, instanceKey := snap.SplitInstanceName(name) 155 info = &snap.Info{ 156 SideInfo: *seq, 157 InstanceKey: instanceKey, 158 Broken: err.Error(), 159 } 160 // clear the error 161 err = nil 162 } 163 info.Publisher, err = publisherAccount(st, seq.SnapID) 164 if err != nil && firstErr == nil { 165 firstErr = err 166 } 167 aboutThis = append(aboutThis, aboutSnap{info, snapst, health}) 168 } 169 } else { 170 info, err = snapst.CurrentInfo() 171 if err == nil { 172 info.Publisher, err = publisherAccount(st, info.SnapID) 173 aboutThis = append(aboutThis, aboutSnap{info, snapst, health}) 174 } 175 } 176 177 if err != nil { 178 // XXX: aggregate instead? 179 if firstErr == nil { 180 firstErr = err 181 } 182 continue 183 } 184 about = append(about, aboutThis...) 185 } 186 187 return about, firstErr 188 } 189 190 // this differs from snap.SplitSnapApp in the handling of the 191 // snap-only case: 192 // snap.SplitSnapApp("foo") is ("foo", "foo"), 193 // splitAppName("foo") is ("foo", ""). 194 func splitAppName(s string) (snap, app string) { 195 if idx := strings.IndexByte(s, '.'); idx > -1 { 196 return s[:idx], s[idx+1:] 197 } 198 199 return s, "" 200 } 201 202 type appInfoOptions struct { 203 service bool 204 } 205 206 func (opts appInfoOptions) String() string { 207 if opts.service { 208 return "service" 209 } 210 211 return "app" 212 } 213 214 // appInfosFor returns a sorted list apps described by names. 215 // 216 // * If names is empty, returns all apps of the wanted kinds (which 217 // could be an empty list). 218 // * An element of names can be a snap name, in which case all apps 219 // from the snap of the wanted kind are included in the result (and 220 // it's an error if the snap has no apps of the wanted kind). 221 // * An element of names can instead be snap.app, in which case that app is 222 // included in the result (and it's an error if the snap and app don't 223 // both exist, or if the app is not a wanted kind) 224 // On error an appropriate error Response is returned; a nil Response means 225 // no error. 226 // 227 // It's a programming error to call this with wanted having neither 228 // services nor commands set. 229 func appInfosFor(st *state.State, names []string, opts appInfoOptions) ([]*snap.AppInfo, Response) { 230 snapNames := make(map[string]bool) 231 requested := make(map[string]bool) 232 for _, name := range names { 233 requested[name] = true 234 name, _ = splitAppName(name) 235 snapNames[name] = true 236 } 237 238 snaps, err := allLocalSnapInfos(st, false, snapNames) 239 if err != nil { 240 return nil, InternalError("cannot list local snaps! %v", err) 241 } 242 243 found := make(map[string]bool) 244 appInfos := make([]*snap.AppInfo, 0, len(requested)) 245 for _, snp := range snaps { 246 snapName := snp.info.InstanceName() 247 apps := make([]*snap.AppInfo, 0, len(snp.info.Apps)) 248 for _, app := range snp.info.Apps { 249 if !opts.service || app.IsService() { 250 apps = append(apps, app) 251 } 252 } 253 254 if len(apps) == 0 && requested[snapName] { 255 return nil, AppNotFound("snap %q has no %ss", snapName, opts) 256 } 257 258 includeAll := len(requested) == 0 || requested[snapName] 259 if includeAll { 260 // want all services in a snap 261 found[snapName] = true 262 } 263 264 for _, app := range apps { 265 appName := snapName + "." + app.Name 266 if includeAll || requested[appName] { 267 appInfos = append(appInfos, app) 268 found[appName] = true 269 } 270 } 271 } 272 273 for k := range requested { 274 if !found[k] { 275 if snapNames[k] { 276 return nil, SnapNotFound(k, fmt.Errorf("snap %q not found", k)) 277 } else { 278 snap, app := splitAppName(k) 279 return nil, AppNotFound("snap %q has no %s %q", snap, opts, app) 280 } 281 } 282 } 283 284 sort.Sort(snap.AppInfoBySnapApp(appInfos)) 285 286 return appInfos, nil 287 } 288 289 func mapLocal(about aboutSnap, sd clientutil.StatusDecorator) *client.Snap { 290 localSnap, snapst := about.info, about.snapst 291 result, err := clientutil.ClientSnapFromSnapInfo(localSnap, sd) 292 if err != nil { 293 logger.Noticef("cannot get full app info for snap %q: %v", localSnap.InstanceName(), err) 294 } 295 result.InstalledSize = localSnap.Size 296 297 if icon := snapIcon(localSnap); icon != "" { 298 result.Icon = icon 299 } 300 301 result.Status = "installed" 302 if snapst.Active && localSnap.Revision == snapst.Current { 303 result.Status = "active" 304 } 305 306 result.TrackingChannel = snapst.TrackingChannel 307 result.IgnoreValidation = snapst.IgnoreValidation 308 result.CohortKey = snapst.CohortKey 309 result.DevMode = snapst.DevMode 310 result.TryMode = snapst.TryMode 311 result.JailMode = snapst.JailMode 312 result.MountedFrom = localSnap.MountFile() 313 if result.TryMode { 314 // Readlink instead of EvalSymlinks because it's only expected 315 // to be one level, and should still resolve if the target does 316 // not exist (this might help e.g. snapcraft clean up after a 317 // prime dir) 318 result.MountedFrom, _ = os.Readlink(result.MountedFrom) 319 } 320 result.Health = about.health 321 322 return result 323 } 324 325 func mapRemote(remoteSnap *snap.Info) *client.Snap { 326 result, err := clientutil.ClientSnapFromSnapInfo(remoteSnap, nil) 327 if err != nil { 328 logger.Noticef("cannot get full app info for snap %q: %v", remoteSnap.SnapName(), err) 329 } 330 result.DownloadSize = remoteSnap.Size 331 if remoteSnap.MustBuy { 332 result.Status = "priced" 333 } else { 334 result.Status = "available" 335 } 336 337 return result 338 }