github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/snapmgr.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2017 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 snapstate 21 22 import ( 23 "context" 24 "errors" 25 "fmt" 26 "io" 27 "os" 28 "strings" 29 "time" 30 31 "gopkg.in/tomb.v2" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/errtracker" 35 "github.com/snapcore/snapd/i18n" 36 "github.com/snapcore/snapd/logger" 37 "github.com/snapcore/snapd/osutil" 38 "github.com/snapcore/snapd/overlord/snapstate/backend" 39 "github.com/snapcore/snapd/overlord/state" 40 "github.com/snapcore/snapd/randutil" 41 "github.com/snapcore/snapd/release" 42 "github.com/snapcore/snapd/sandbox" 43 "github.com/snapcore/snapd/snap" 44 "github.com/snapcore/snapd/snap/channel" 45 "github.com/snapcore/snapd/snapdenv" 46 "github.com/snapcore/snapd/store" 47 ) 48 49 var ( 50 snapdTransitionDelayWithRandomess = 3*time.Hour + randutil.RandomDuration(4*time.Hour) 51 ) 52 53 // overridden in the tests 54 var errtrackerReport = errtracker.Report 55 56 // SnapManager is responsible for the installation and removal of snaps. 57 type SnapManager struct { 58 state *state.State 59 backend managerBackend 60 61 autoRefresh *autoRefresh 62 refreshHints *refreshHints 63 catalogRefresh *catalogRefresh 64 65 preseed bool 66 } 67 68 // SnapSetup holds the necessary snap details to perform most snap manager tasks. 69 type SnapSetup struct { 70 // FIXME: rename to RequestedChannel to convey the meaning better 71 Channel string `json:"channel,omitempty"` 72 UserID int `json:"user-id,omitempty"` 73 Base string `json:"base,omitempty"` 74 Type snap.Type `json:"type,omitempty"` 75 // PlugsOnly indicates whether the relevant revisions for the 76 // operation have only plugs (#plugs >= 0), and absolutely no 77 // slots (#slots == 0). 78 PlugsOnly bool `json:"plugs-only,omitempty"` 79 80 CohortKey string `json:"cohort-key,omitempty"` 81 82 // FIXME: implement rename of this as suggested in 83 // https://github.com/snapcore/snapd/pull/4103#discussion_r169569717 84 // 85 // Prereq is a list of snap-names that need to get installed 86 // together with this snap. Typically used when installing 87 // content-snaps with default-providers. 88 Prereq []string `json:"prereq,omitempty"` 89 90 Flags 91 92 SnapPath string `json:"snap-path,omitempty"` 93 94 DownloadInfo *snap.DownloadInfo `json:"download-info,omitempty"` 95 SideInfo *snap.SideInfo `json:"side-info,omitempty"` 96 auxStoreInfo 97 98 // InstanceKey is set by the user during installation and differs for 99 // each instance of given snap 100 InstanceKey string `json:"instance-key,omitempty"` 101 } 102 103 func (snapsup *SnapSetup) InstanceName() string { 104 return snap.InstanceName(snapsup.SnapName(), snapsup.InstanceKey) 105 } 106 107 func (snapsup *SnapSetup) SnapName() string { 108 if snapsup.SideInfo.RealName == "" { 109 panic("SnapSetup.SideInfo.RealName not set") 110 } 111 return snapsup.SideInfo.RealName 112 } 113 114 func (snapsup *SnapSetup) Revision() snap.Revision { 115 return snapsup.SideInfo.Revision 116 } 117 118 func (snapsup *SnapSetup) placeInfo() snap.PlaceInfo { 119 return snap.MinimalPlaceInfo(snapsup.InstanceName(), snapsup.Revision()) 120 } 121 122 func (snapsup *SnapSetup) MountDir() string { 123 return snap.MountDir(snapsup.InstanceName(), snapsup.Revision()) 124 } 125 126 func (snapsup *SnapSetup) MountFile() string { 127 return snap.MountFile(snapsup.InstanceName(), snapsup.Revision()) 128 } 129 130 // SnapState holds the state for a snap installed in the system. 131 type SnapState struct { 132 SnapType string `json:"type"` // Use Type and SetType 133 Sequence []*snap.SideInfo `json:"sequence"` 134 Active bool `json:"active,omitempty"` 135 136 // LastActiveDisabledServices is a list of services that were disabled in 137 // this snap when it was last active - i.e. when it was disabled, before 138 // it was reverted, or before a refresh happens. 139 // It is set during unlink-snap and unlink-current-snap and reset during 140 // link-snap since it is only meant to be saved when snapd needs to remove 141 // systemd units. 142 // Note that to handle potential service renames, only services that exist 143 // in the snap are removed from this list on link-snap, so that we can 144 // remember services that were disabled in another revision and then renamed 145 // or otherwise removed from the snap in a future refresh. 146 LastActiveDisabledServices []string `json:"last-active-disabled-services,omitempty"` 147 148 // tracking services enabled and disabled by hooks 149 ServicesEnabledByHooks []string `json:"services-enabled-by-hooks,omitempty"` 150 ServicesDisabledByHooks []string `json:"services-disabled-by-hooks,omitempty"` 151 152 // Current indicates the current active revision if Active is 153 // true or the last active revision if Active is false 154 // (usually while a snap is being operated on or disabled) 155 Current snap.Revision `json:"current"` 156 TrackingChannel string `json:"channel,omitempty"` 157 Flags 158 // aliases, see aliasesv2.go 159 Aliases map[string]*AliasTarget `json:"aliases,omitempty"` 160 AutoAliasesDisabled bool `json:"auto-aliases-disabled,omitempty"` 161 AliasesPending bool `json:"aliases-pending,omitempty"` 162 163 // UserID of the user requesting the install 164 UserID int `json:"user-id,omitempty"` 165 166 // InstanceKey is set by the user during installation and differs for 167 // each instance of given snap 168 InstanceKey string `json:"instance-key,omitempty"` 169 CohortKey string `json:"cohort-key,omitempty"` 170 171 // RefreshInhibitedime records the time when the refresh was first 172 // attempted but inhibited because the snap was busy. This value is 173 // reset on each successful refresh. 174 RefreshInhibitedTime *time.Time `json:"refresh-inhibited-time,omitempty"` 175 176 // LastRefreshTime records the time when the snap was last refreshed. 177 LastRefreshTime *time.Time `json:"last-refresh,omitempty"` 178 } 179 180 func (snapst *SnapState) SetTrackingChannel(s string) error { 181 s, err := channel.Full(s) 182 if err != nil { 183 return err 184 } 185 snapst.TrackingChannel = s 186 return nil 187 } 188 189 // Type returns the type of the snap or an error. 190 // Should never error if Current is not nil. 191 func (snapst *SnapState) Type() (snap.Type, error) { 192 if snapst.SnapType == "" { 193 return snap.Type(""), fmt.Errorf("snap type unset") 194 } 195 return snap.Type(snapst.SnapType), nil 196 } 197 198 // SetType records the type of the snap. 199 func (snapst *SnapState) SetType(typ snap.Type) { 200 snapst.SnapType = string(typ) 201 } 202 203 // IsInstalled returns whether the snap is installed, i.e. snapst represents an installed snap with Current revision set. 204 func (snapst *SnapState) IsInstalled() bool { 205 if snapst.Current.Unset() { 206 if len(snapst.Sequence) > 0 { 207 panic(fmt.Sprintf("snapst.Current and snapst.Sequence out of sync: %#v %#v", snapst.Current, snapst.Sequence)) 208 } 209 210 return false 211 } 212 return true 213 } 214 215 // LocalRevision returns the "latest" local revision. Local revisions 216 // start at -1 and are counted down. 217 func (snapst *SnapState) LocalRevision() snap.Revision { 218 var local snap.Revision 219 for _, si := range snapst.Sequence { 220 if si.Revision.Local() && si.Revision.N < local.N { 221 local = si.Revision 222 } 223 } 224 return local 225 } 226 227 // CurrentSideInfo returns the side info for the revision indicated by snapst.Current in the snap revision sequence if there is one. 228 func (snapst *SnapState) CurrentSideInfo() *snap.SideInfo { 229 if !snapst.IsInstalled() { 230 return nil 231 } 232 if idx := snapst.LastIndex(snapst.Current); idx >= 0 { 233 return snapst.Sequence[idx] 234 } 235 panic("cannot find snapst.Current in the snapst.Sequence") 236 } 237 238 func (snapst *SnapState) previousSideInfo() *snap.SideInfo { 239 n := len(snapst.Sequence) 240 if n < 2 { 241 return nil 242 } 243 // find "current" and return the one before that 244 currentIndex := snapst.LastIndex(snapst.Current) 245 if currentIndex <= 0 { 246 return nil 247 } 248 return snapst.Sequence[currentIndex-1] 249 } 250 251 // LastIndex returns the last index of the given revision in the 252 // snapst.Sequence 253 func (snapst *SnapState) LastIndex(revision snap.Revision) int { 254 for i := len(snapst.Sequence) - 1; i >= 0; i-- { 255 if snapst.Sequence[i].Revision == revision { 256 return i 257 } 258 } 259 return -1 260 } 261 262 // Block returns revisions that should be blocked on refreshes, 263 // computed from Sequence[currentRevisionIndex+1:]. 264 func (snapst *SnapState) Block() []snap.Revision { 265 // return revisions from Sequence[currentIndex:] 266 currentIndex := snapst.LastIndex(snapst.Current) 267 if currentIndex < 0 || currentIndex+1 == len(snapst.Sequence) { 268 return nil 269 } 270 out := make([]snap.Revision, len(snapst.Sequence)-currentIndex-1) 271 for i, si := range snapst.Sequence[currentIndex+1:] { 272 out[i] = si.Revision 273 } 274 return out 275 } 276 277 var ErrNoCurrent = errors.New("snap has no current revision") 278 279 // Retrieval functions 280 281 const ( 282 errorOnBroken = 1 << iota 283 withAuxStoreInfo 284 ) 285 286 var snapReadInfo = snap.ReadInfo 287 288 // AutomaticSnapshot allows to hook snapshot manager's AutomaticSnapshot. 289 var AutomaticSnapshot func(st *state.State, instanceName string) (ts *state.TaskSet, err error) 290 var AutomaticSnapshotExpiration func(st *state.State) (time.Duration, error) 291 var EstimateSnapshotSize func(st *state.State, instanceName string, users []string) (uint64, error) 292 293 func readInfo(name string, si *snap.SideInfo, flags int) (*snap.Info, error) { 294 info, err := snapReadInfo(name, si) 295 if err != nil && flags&errorOnBroken != 0 { 296 return nil, err 297 } 298 if err != nil { 299 logger.Noticef("cannot read snap info of snap %q at revision %s: %s", name, si.Revision, err) 300 } 301 if bse, ok := err.(snap.BrokenSnapError); ok { 302 _, instanceKey := snap.SplitInstanceName(name) 303 info = &snap.Info{ 304 SuggestedName: name, 305 Broken: bse.Broken(), 306 InstanceKey: instanceKey, 307 } 308 info.Apps = snap.GuessAppsForBroken(info) 309 if si != nil { 310 info.SideInfo = *si 311 } 312 err = nil 313 } 314 if err == nil && flags&withAuxStoreInfo != 0 { 315 if err := retrieveAuxStoreInfo(info); err != nil { 316 logger.Debugf("cannot read auxiliary store info for snap %q: %v", name, err) 317 } 318 } 319 return info, err 320 } 321 322 var revisionDate = revisionDateImpl 323 324 // revisionDate returns a good approximation of when a revision reached the system. 325 func revisionDateImpl(info *snap.Info) time.Time { 326 fi, err := os.Lstat(info.MountFile()) 327 if err != nil { 328 return time.Time{} 329 } 330 return fi.ModTime() 331 } 332 333 // CurrentInfo returns the information about the current active revision or the last active revision (if the snap is inactive). It returns the ErrNoCurrent error if snapst.Current is unset. 334 func (snapst *SnapState) CurrentInfo() (*snap.Info, error) { 335 cur := snapst.CurrentSideInfo() 336 if cur == nil { 337 return nil, ErrNoCurrent 338 } 339 340 name := snap.InstanceName(cur.RealName, snapst.InstanceKey) 341 return readInfo(name, cur, withAuxStoreInfo) 342 } 343 344 func (snapst *SnapState) InstanceName() string { 345 cur := snapst.CurrentSideInfo() 346 if cur == nil { 347 return "" 348 } 349 return snap.InstanceName(cur.RealName, snapst.InstanceKey) 350 } 351 352 func revisionInSequence(snapst *SnapState, needle snap.Revision) bool { 353 for _, si := range snapst.Sequence { 354 if si.Revision == needle { 355 return true 356 } 357 } 358 return false 359 } 360 361 type cachedStoreKey struct{} 362 363 // ReplaceStore replaces the store used by the manager. 364 func ReplaceStore(state *state.State, store StoreService) { 365 state.Cache(cachedStoreKey{}, store) 366 } 367 368 func cachedStore(st *state.State) StoreService { 369 ubuntuStore := st.Cached(cachedStoreKey{}) 370 if ubuntuStore == nil { 371 return nil 372 } 373 return ubuntuStore.(StoreService) 374 } 375 376 // the store implementation has the interface consumed here 377 var _ StoreService = (*store.Store)(nil) 378 379 // Store returns the store service provided by the optional device context or 380 // the one used by the snapstate package if the former has no 381 // override. 382 func Store(st *state.State, deviceCtx DeviceContext) StoreService { 383 if deviceCtx != nil { 384 sto := deviceCtx.Store() 385 if sto != nil { 386 return sto 387 } 388 } 389 if cachedStore := cachedStore(st); cachedStore != nil { 390 return cachedStore 391 } 392 panic("internal error: needing the store before managers have initialized it") 393 } 394 395 // Manager returns a new snap manager. 396 func Manager(st *state.State, runner *state.TaskRunner) (*SnapManager, error) { 397 preseed := snapdenv.Preseeding() 398 m := &SnapManager{ 399 state: st, 400 autoRefresh: newAutoRefresh(st), 401 refreshHints: newRefreshHints(st), 402 catalogRefresh: newCatalogRefresh(st), 403 preseed: preseed, 404 } 405 if preseed { 406 m.backend = backend.NewForPreseedMode() 407 } else { 408 m.backend = backend.Backend{} 409 } 410 411 if err := os.MkdirAll(dirs.SnapCookieDir, 0700); err != nil { 412 return nil, fmt.Errorf("cannot create directory %q: %v", dirs.SnapCookieDir, err) 413 } 414 415 if err := genRefreshRequestSalt(st); err != nil { 416 return nil, fmt.Errorf("cannot generate request salt: %v", err) 417 } 418 419 // this handler does nothing 420 runner.AddHandler("nop", func(t *state.Task, _ *tomb.Tomb) error { 421 return nil 422 }, nil) 423 424 // install/update related 425 426 // TODO: no undo handler here, we may use the GC for this and just 427 // remove anything that is not referenced anymore 428 runner.AddHandler("prerequisites", m.doPrerequisites, nil) 429 runner.AddHandler("prepare-snap", m.doPrepareSnap, m.undoPrepareSnap) 430 runner.AddHandler("download-snap", m.doDownloadSnap, m.undoPrepareSnap) 431 runner.AddHandler("mount-snap", m.doMountSnap, m.undoMountSnap) 432 runner.AddHandler("unlink-current-snap", m.doUnlinkCurrentSnap, m.undoUnlinkCurrentSnap) 433 runner.AddHandler("copy-snap-data", m.doCopySnapData, m.undoCopySnapData) 434 runner.AddCleanup("copy-snap-data", m.cleanupCopySnapData) 435 runner.AddHandler("link-snap", m.doLinkSnap, m.undoLinkSnap) 436 runner.AddHandler("start-snap-services", m.startSnapServices, m.undoStartSnapServices) 437 runner.AddHandler("switch-snap-channel", m.doSwitchSnapChannel, nil) 438 runner.AddHandler("toggle-snap-flags", m.doToggleSnapFlags, nil) 439 runner.AddHandler("check-rerefresh", m.doCheckReRefresh, nil) 440 runner.AddHandler("conditional-auto-refresh", m.doConditionalAutoRefresh, nil) 441 442 // FIXME: drop the task entirely after a while 443 // (having this wart here avoids yet-another-patch) 444 runner.AddHandler("cleanup", func(*state.Task, *tomb.Tomb) error { return nil }, nil) 445 446 // remove related 447 runner.AddHandler("stop-snap-services", m.stopSnapServices, m.undoStopSnapServices) 448 runner.AddHandler("unlink-snap", m.doUnlinkSnap, m.undoUnlinkSnap) 449 runner.AddHandler("clear-snap", m.doClearSnapData, nil) 450 runner.AddHandler("discard-snap", m.doDiscardSnap, nil) 451 452 // alias related 453 // FIXME: drop the task entirely after a while 454 runner.AddHandler("clear-aliases", func(*state.Task, *tomb.Tomb) error { return nil }, nil) 455 runner.AddHandler("set-auto-aliases", m.doSetAutoAliases, m.undoRefreshAliases) 456 runner.AddHandler("setup-aliases", m.doSetupAliases, m.doRemoveAliases) 457 runner.AddHandler("refresh-aliases", m.doRefreshAliases, m.undoRefreshAliases) 458 runner.AddHandler("prune-auto-aliases", m.doPruneAutoAliases, m.undoRefreshAliases) 459 runner.AddHandler("remove-aliases", m.doRemoveAliases, m.doSetupAliases) 460 runner.AddHandler("alias", m.doAlias, m.undoRefreshAliases) 461 runner.AddHandler("unalias", m.doUnalias, m.undoRefreshAliases) 462 runner.AddHandler("disable-aliases", m.doDisableAliases, m.undoRefreshAliases) 463 runner.AddHandler("prefer-aliases", m.doPreferAliases, m.undoRefreshAliases) 464 465 // misc 466 runner.AddHandler("switch-snap", m.doSwitchSnap, nil) 467 468 // control serialisation 469 runner.AddBlocked(m.blockedTask) 470 471 return m, nil 472 } 473 474 // StartUp implements StateStarterUp.Startup. 475 func (m *SnapManager) StartUp() error { 476 writeSnapReadme() 477 478 m.state.Lock() 479 defer m.state.Unlock() 480 if err := m.SyncCookies(m.state); err != nil { 481 return fmt.Errorf("failed to generate cookies: %q", err) 482 } 483 return nil 484 } 485 486 func (m *SnapManager) CanStandby() bool { 487 if n, err := NumSnaps(m.state); err == nil && n == 0 { 488 return true 489 } 490 return false 491 } 492 493 func genRefreshRequestSalt(st *state.State) error { 494 var refreshPrivacyKey string 495 496 st.Lock() 497 defer st.Unlock() 498 499 if err := st.Get("refresh-privacy-key", &refreshPrivacyKey); err != nil && err != state.ErrNoState { 500 return err 501 } 502 if refreshPrivacyKey != "" { 503 // nothing to do 504 return nil 505 } 506 507 refreshPrivacyKey = randutil.RandomString(16) 508 st.Set("refresh-privacy-key", refreshPrivacyKey) 509 510 return nil 511 } 512 513 func (m *SnapManager) blockedTask(cand *state.Task, running []*state.Task) bool { 514 // Serialize "prerequisites", the state lock is not enough as 515 // Install() inside doPrerequisites() will unlock to talk to 516 // the store. 517 if cand.Kind() == "prerequisites" { 518 for _, t := range running { 519 if t.Kind() == "prerequisites" { 520 return true 521 } 522 } 523 } 524 525 return false 526 } 527 528 // NextRefresh returns the time the next update of the system's snaps 529 // will be attempted. 530 // The caller should be holding the state lock. 531 func (m *SnapManager) NextRefresh() time.Time { 532 return m.autoRefresh.NextRefresh() 533 } 534 535 // EffectiveRefreshHold returns the time until to which refreshes are 536 // held if refresh.hold configuration is set and accounting for the 537 // max postponement since the last refresh. 538 // The caller should be holding the state lock. 539 func (m *SnapManager) EffectiveRefreshHold() (time.Time, error) { 540 return m.autoRefresh.EffectiveRefreshHold() 541 } 542 543 // LastRefresh returns the time the last snap update. 544 // The caller should be holding the state lock. 545 func (m *SnapManager) LastRefresh() (time.Time, error) { 546 return m.autoRefresh.LastRefresh() 547 } 548 549 // RefreshSchedule returns the current refresh schedule as a string suitable for 550 // display to a user and a flag indicating whether the schedule is a legacy one. 551 // The caller should be holding the state lock. 552 func (m *SnapManager) RefreshSchedule() (string, bool, error) { 553 return m.autoRefresh.RefreshSchedule() 554 } 555 556 // EnsureAutoRefreshesAreDelayed will delay refreshes for the specified amount 557 // of time, as well as return any active auto-refresh changes that are currently 558 // not ready so that the client can wait for those. 559 func (m *SnapManager) EnsureAutoRefreshesAreDelayed(delay time.Duration) ([]*state.Change, error) { 560 // always delay for at least the specified time, this ensures that even if 561 // there are active refreshes right now, there won't be more auto-refreshes 562 // that happen after the current set finish 563 err := m.autoRefresh.ensureRefreshHoldAtLeast(delay) 564 if err != nil { 565 return nil, err 566 } 567 568 // look for auto refresh changes in progress 569 autoRefreshChgsInFlight := []*state.Change{} 570 for _, chg := range m.state.Changes() { 571 if chg.Kind() == "auto-refresh" && !chg.Status().Ready() { 572 autoRefreshChgsInFlight = append(autoRefreshChgsInFlight, chg) 573 } 574 } 575 576 return autoRefreshChgsInFlight, nil 577 } 578 579 // ensureForceDevmodeDropsDevmodeFromState undoes the forced devmode 580 // in snapstate for forced devmode distros. 581 func (m *SnapManager) ensureForceDevmodeDropsDevmodeFromState() error { 582 if !sandbox.ForceDevMode() { 583 return nil 584 } 585 586 m.state.Lock() 587 defer m.state.Unlock() 588 589 // int because we might want to come back and do a second pass at cleanup 590 var fixed int 591 if err := m.state.Get("fix-forced-devmode", &fixed); err != nil && err != state.ErrNoState { 592 return err 593 } 594 595 if fixed > 0 { 596 return nil 597 } 598 599 for _, name := range []string{"core", "ubuntu-core"} { 600 var snapst SnapState 601 if err := Get(m.state, name, &snapst); err == state.ErrNoState { 602 // nothing to see here 603 continue 604 } else if err != nil { 605 // bad 606 return err 607 } 608 if info := snapst.CurrentSideInfo(); info == nil || info.SnapID == "" { 609 continue 610 } 611 snapst.DevMode = false 612 Set(m.state, name, &snapst) 613 } 614 m.state.Set("fix-forced-devmode", 1) 615 616 return nil 617 } 618 619 // changeInFlight returns true if there is any change in the state 620 // in non-ready state. 621 func changeInFlight(st *state.State) bool { 622 for _, chg := range st.Changes() { 623 if !chg.IsReady() { 624 // another change already in motion 625 return true 626 } 627 } 628 return false 629 } 630 631 // ensureSnapdSnapTransition will migrate systems to use the "snapd" snap 632 func (m *SnapManager) ensureSnapdSnapTransition() error { 633 m.state.Lock() 634 defer m.state.Unlock() 635 636 // we only auto-transition people on classic systems, for core we 637 // will need to do a proper re-model 638 if !release.OnClassic { 639 return nil 640 } 641 642 // check if snapd snap is installed 643 var snapst SnapState 644 err := Get(m.state, "snapd", &snapst) 645 if err != nil && err != state.ErrNoState { 646 return err 647 } 648 // nothing to do 649 if snapst.IsInstalled() { 650 return nil 651 } 652 653 // check if the user opts into the snapd snap 654 optedIntoSnapdTransition, err := optedIntoSnapdSnap(m.state) 655 if err != nil { 656 return err 657 } 658 // nothing to do: the user does not want the snapd snap yet 659 if !optedIntoSnapdTransition { 660 return nil 661 } 662 663 // ensure we only transition systems that have snaps already 664 installedSnaps, err := NumSnaps(m.state) 665 if err != nil { 666 return err 667 } 668 // no installed snaps (yet): do nothing (fresh classic install) 669 if installedSnaps == 0 { 670 return nil 671 } 672 673 // get current core snap and use same channel/user for the snapd snap 674 err = Get(m.state, "core", &snapst) 675 // Note that state.ErrNoState should never happen in practise. However 676 // if it *does* happen we still want to fix those systems by installing 677 // the snapd snap. 678 if err != nil && err != state.ErrNoState { 679 return err 680 } 681 coreChannel := snapst.TrackingChannel 682 // snapd/core are never blocked on auth so we don't need to copy 683 // the userID from the snapst here 684 userID := 0 685 686 if changeInFlight(m.state) { 687 // check that there is no change in flight already, this is a 688 // precaution to ensure the snapd transition is safe 689 return nil 690 } 691 692 // ensure we limit the retries in case something goes wrong 693 var lastSnapdTransitionAttempt time.Time 694 err = m.state.Get("snapd-transition-last-retry-time", &lastSnapdTransitionAttempt) 695 if err != nil && err != state.ErrNoState { 696 return err 697 } 698 now := time.Now() 699 if !lastSnapdTransitionAttempt.IsZero() && lastSnapdTransitionAttempt.Add(snapdTransitionDelayWithRandomess).After(now) { 700 return nil 701 } 702 m.state.Set("snapd-transition-last-retry-time", now) 703 704 var retryCount int 705 err = m.state.Get("snapd-transition-retry", &retryCount) 706 if err != nil && err != state.ErrNoState { 707 return err 708 } 709 m.state.Set("snapd-transition-retry", retryCount+1) 710 711 ts, err := Install(context.Background(), m.state, "snapd", &RevisionOptions{Channel: coreChannel}, userID, Flags{}) 712 if err != nil { 713 return err 714 } 715 716 msg := i18n.G("Transition to the snapd snap") 717 chg := m.state.NewChange("transition-to-snapd-snap", msg) 718 chg.AddAll(ts) 719 720 return nil 721 } 722 723 // ensureUbuntuCoreTransition will migrate systems that use "ubuntu-core" 724 // to the new "core" snap 725 func (m *SnapManager) ensureUbuntuCoreTransition() error { 726 m.state.Lock() 727 defer m.state.Unlock() 728 729 var snapst SnapState 730 err := Get(m.state, "ubuntu-core", &snapst) 731 if err == state.ErrNoState { 732 return nil 733 } 734 if err != nil && err != state.ErrNoState { 735 return err 736 } 737 738 // check that there is no change in flight already, this is a 739 // precaution to ensure the core transition is safe 740 if changeInFlight(m.state) { 741 // another change already in motion 742 return nil 743 } 744 745 // ensure we limit the retries in case something goes wrong 746 var lastUbuntuCoreTransitionAttempt time.Time 747 err = m.state.Get("ubuntu-core-transition-last-retry-time", &lastUbuntuCoreTransitionAttempt) 748 if err != nil && err != state.ErrNoState { 749 return err 750 } 751 now := time.Now() 752 if !lastUbuntuCoreTransitionAttempt.IsZero() && lastUbuntuCoreTransitionAttempt.Add(6*time.Hour).After(now) { 753 return nil 754 } 755 756 tss, trErr := TransitionCore(m.state, "ubuntu-core", "core") 757 if _, ok := trErr.(*ChangeConflictError); ok { 758 // likely just too early, retry at next Ensure 759 return nil 760 } 761 762 m.state.Set("ubuntu-core-transition-last-retry-time", now) 763 764 var retryCount int 765 err = m.state.Get("ubuntu-core-transition-retry", &retryCount) 766 if err != nil && err != state.ErrNoState { 767 return err 768 } 769 m.state.Set("ubuntu-core-transition-retry", retryCount+1) 770 771 if trErr != nil { 772 return trErr 773 } 774 775 msg := i18n.G("Transition ubuntu-core to core") 776 chg := m.state.NewChange("transition-ubuntu-core", msg) 777 for _, ts := range tss { 778 chg.AddAll(ts) 779 } 780 781 return nil 782 } 783 784 // atSeed implements at seeding policy for refreshes. 785 func (m *SnapManager) atSeed() error { 786 m.state.Lock() 787 defer m.state.Unlock() 788 var seeded bool 789 err := m.state.Get("seeded", &seeded) 790 if err != state.ErrNoState { 791 // already seeded or other error 792 return err 793 } 794 if err := m.autoRefresh.AtSeed(); err != nil { 795 return err 796 } 797 if err := m.refreshHints.AtSeed(); err != nil { 798 return err 799 } 800 return nil 801 } 802 803 var ( 804 localInstallCleanupWait = time.Duration(24 * time.Hour) 805 localInstallLastCleanup time.Time 806 ) 807 808 // localInstallCleanup removes files that might've been left behind by an 809 // old aborted local install. 810 // 811 // They're usually cleaned up, but if they're created and then snapd 812 // stops before writing the change to disk (killed, light cut, etc) 813 // it'll be left behind. 814 // 815 // The code that creates the files is in daemon/api.go's postSnaps 816 func (m *SnapManager) localInstallCleanup() error { 817 m.state.Lock() 818 defer m.state.Unlock() 819 820 now := time.Now() 821 cutoff := now.Add(-localInstallCleanupWait) 822 if localInstallLastCleanup.After(cutoff) { 823 return nil 824 } 825 localInstallLastCleanup = now 826 827 d, err := os.Open(dirs.SnapBlobDir) 828 if err != nil { 829 if os.IsNotExist(err) { 830 return nil 831 } 832 return err 833 } 834 defer d.Close() 835 836 var filenames []string 837 var fis []os.FileInfo 838 for err == nil { 839 // TODO: if we had fstatat we could avoid a bunch of stats 840 fis, err = d.Readdir(100) 841 // fis is nil if err isn't 842 for _, fi := range fis { 843 name := fi.Name() 844 if !strings.HasPrefix(name, dirs.LocalInstallBlobTempPrefix) { 845 continue 846 } 847 if fi.ModTime().After(cutoff) { 848 continue 849 } 850 filenames = append(filenames, name) 851 } 852 } 853 if err != io.EOF { 854 return err 855 } 856 return osutil.UnlinkManyAt(d, filenames) 857 } 858 859 // Ensure implements StateManager.Ensure. 860 func (m *SnapManager) Ensure() error { 861 if m.preseed { 862 return nil 863 } 864 865 // do not exit right away on error 866 errs := []error{ 867 m.atSeed(), 868 m.ensureAliasesV2(), 869 m.ensureForceDevmodeDropsDevmodeFromState(), 870 m.ensureUbuntuCoreTransition(), 871 m.ensureSnapdSnapTransition(), 872 // we should check for full regular refreshes before 873 // considering issuing a hint only refresh request 874 m.autoRefresh.Ensure(), 875 m.refreshHints.Ensure(), 876 m.catalogRefresh.Ensure(), 877 m.localInstallCleanup(), 878 } 879 880 //FIXME: use firstErr helper 881 for _, e := range errs { 882 if e != nil { 883 return e 884 } 885 } 886 887 return nil 888 }