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