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