github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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-time,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 AddAffectedSnapsByKind("conditional-auto-refresh", conditionalAutoRefreshAffectedSnaps) 472 473 return m, nil 474 } 475 476 // StartUp implements StateStarterUp.Startup. 477 func (m *SnapManager) StartUp() error { 478 writeSnapReadme() 479 480 m.state.Lock() 481 defer m.state.Unlock() 482 if err := m.SyncCookies(m.state); err != nil { 483 return fmt.Errorf("failed to generate cookies: %q", err) 484 } 485 return nil 486 } 487 488 func (m *SnapManager) CanStandby() bool { 489 if n, err := NumSnaps(m.state); err == nil && n == 0 { 490 return true 491 } 492 return false 493 } 494 495 func genRefreshRequestSalt(st *state.State) error { 496 var refreshPrivacyKey string 497 498 st.Lock() 499 defer st.Unlock() 500 501 if err := st.Get("refresh-privacy-key", &refreshPrivacyKey); err != nil && err != state.ErrNoState { 502 return err 503 } 504 if refreshPrivacyKey != "" { 505 // nothing to do 506 return nil 507 } 508 509 refreshPrivacyKey = randutil.RandomString(16) 510 st.Set("refresh-privacy-key", refreshPrivacyKey) 511 512 return nil 513 } 514 515 func (m *SnapManager) blockedTask(cand *state.Task, running []*state.Task) bool { 516 // Serialize "prerequisites", the state lock is not enough as 517 // Install() inside doPrerequisites() will unlock to talk to 518 // the store. 519 if cand.Kind() == "prerequisites" { 520 for _, t := range running { 521 if t.Kind() == "prerequisites" { 522 return true 523 } 524 } 525 } 526 527 return false 528 } 529 530 // NextRefresh returns the time the next update of the system's snaps 531 // will be attempted. 532 // The caller should be holding the state lock. 533 func (m *SnapManager) NextRefresh() time.Time { 534 return m.autoRefresh.NextRefresh() 535 } 536 537 // EffectiveRefreshHold returns the time until to which refreshes are 538 // held if refresh.hold configuration is set and accounting for the 539 // max postponement since the last refresh. 540 // The caller should be holding the state lock. 541 func (m *SnapManager) EffectiveRefreshHold() (time.Time, error) { 542 return m.autoRefresh.EffectiveRefreshHold() 543 } 544 545 // LastRefresh returns the time the last snap update. 546 // The caller should be holding the state lock. 547 func (m *SnapManager) LastRefresh() (time.Time, error) { 548 return m.autoRefresh.LastRefresh() 549 } 550 551 // RefreshSchedule returns the current refresh schedule as a string suitable for 552 // display to a user and a flag indicating whether the schedule is a legacy one. 553 // The caller should be holding the state lock. 554 func (m *SnapManager) RefreshSchedule() (string, bool, error) { 555 return m.autoRefresh.RefreshSchedule() 556 } 557 558 // EnsureAutoRefreshesAreDelayed will delay refreshes for the specified amount 559 // of time, as well as return any active auto-refresh changes that are currently 560 // not ready so that the client can wait for those. 561 func (m *SnapManager) EnsureAutoRefreshesAreDelayed(delay time.Duration) ([]*state.Change, error) { 562 // always delay for at least the specified time, this ensures that even if 563 // there are active refreshes right now, there won't be more auto-refreshes 564 // that happen after the current set finish 565 err := m.autoRefresh.ensureRefreshHoldAtLeast(delay) 566 if err != nil { 567 return nil, err 568 } 569 570 // look for auto refresh changes in progress 571 autoRefreshChgsInFlight := []*state.Change{} 572 for _, chg := range m.state.Changes() { 573 if chg.Kind() == "auto-refresh" && !chg.Status().Ready() { 574 autoRefreshChgsInFlight = append(autoRefreshChgsInFlight, chg) 575 } 576 } 577 578 return autoRefreshChgsInFlight, nil 579 } 580 581 // ensureForceDevmodeDropsDevmodeFromState undoes the forced devmode 582 // in snapstate for forced devmode distros. 583 func (m *SnapManager) ensureForceDevmodeDropsDevmodeFromState() error { 584 if !sandbox.ForceDevMode() { 585 return nil 586 } 587 588 m.state.Lock() 589 defer m.state.Unlock() 590 591 // int because we might want to come back and do a second pass at cleanup 592 var fixed int 593 if err := m.state.Get("fix-forced-devmode", &fixed); err != nil && err != state.ErrNoState { 594 return err 595 } 596 597 if fixed > 0 { 598 return nil 599 } 600 601 for _, name := range []string{"core", "ubuntu-core"} { 602 var snapst SnapState 603 if err := Get(m.state, name, &snapst); err == state.ErrNoState { 604 // nothing to see here 605 continue 606 } else if err != nil { 607 // bad 608 return err 609 } 610 if info := snapst.CurrentSideInfo(); info == nil || info.SnapID == "" { 611 continue 612 } 613 snapst.DevMode = false 614 Set(m.state, name, &snapst) 615 } 616 m.state.Set("fix-forced-devmode", 1) 617 618 return nil 619 } 620 621 // changeInFlight returns true if there is any change in the state 622 // in non-ready state. 623 func changeInFlight(st *state.State) bool { 624 for _, chg := range st.Changes() { 625 if !chg.IsReady() { 626 // another change already in motion 627 return true 628 } 629 } 630 return false 631 } 632 633 // ensureSnapdSnapTransition will migrate systems to use the "snapd" snap 634 func (m *SnapManager) ensureSnapdSnapTransition() error { 635 m.state.Lock() 636 defer m.state.Unlock() 637 638 // we only auto-transition people on classic systems, for core we 639 // will need to do a proper re-model 640 if !release.OnClassic { 641 return nil 642 } 643 644 // check if snapd snap is installed 645 var snapst SnapState 646 err := Get(m.state, "snapd", &snapst) 647 if err != nil && err != state.ErrNoState { 648 return err 649 } 650 // nothing to do 651 if snapst.IsInstalled() { 652 return nil 653 } 654 655 // check if the user opts into the snapd snap 656 optedIntoSnapdTransition, err := optedIntoSnapdSnap(m.state) 657 if err != nil { 658 return err 659 } 660 // nothing to do: the user does not want the snapd snap yet 661 if !optedIntoSnapdTransition { 662 return nil 663 } 664 665 // ensure we only transition systems that have snaps already 666 installedSnaps, err := NumSnaps(m.state) 667 if err != nil { 668 return err 669 } 670 // no installed snaps (yet): do nothing (fresh classic install) 671 if installedSnaps == 0 { 672 return nil 673 } 674 675 // get current core snap and use same channel/user for the snapd snap 676 err = Get(m.state, "core", &snapst) 677 // Note that state.ErrNoState should never happen in practise. However 678 // if it *does* happen we still want to fix those systems by installing 679 // the snapd snap. 680 if err != nil && err != state.ErrNoState { 681 return err 682 } 683 coreChannel := snapst.TrackingChannel 684 // snapd/core are never blocked on auth so we don't need to copy 685 // the userID from the snapst here 686 userID := 0 687 688 if changeInFlight(m.state) { 689 // check that there is no change in flight already, this is a 690 // precaution to ensure the snapd transition is safe 691 return nil 692 } 693 694 // ensure we limit the retries in case something goes wrong 695 var lastSnapdTransitionAttempt time.Time 696 err = m.state.Get("snapd-transition-last-retry-time", &lastSnapdTransitionAttempt) 697 if err != nil && err != state.ErrNoState { 698 return err 699 } 700 now := time.Now() 701 if !lastSnapdTransitionAttempt.IsZero() && lastSnapdTransitionAttempt.Add(snapdTransitionDelayWithRandomess).After(now) { 702 return nil 703 } 704 m.state.Set("snapd-transition-last-retry-time", now) 705 706 var retryCount int 707 err = m.state.Get("snapd-transition-retry", &retryCount) 708 if err != nil && err != state.ErrNoState { 709 return err 710 } 711 m.state.Set("snapd-transition-retry", retryCount+1) 712 713 ts, err := Install(context.Background(), m.state, "snapd", &RevisionOptions{Channel: coreChannel}, userID, Flags{}) 714 if err != nil { 715 return err 716 } 717 718 msg := i18n.G("Transition to the snapd snap") 719 chg := m.state.NewChange("transition-to-snapd-snap", msg) 720 chg.AddAll(ts) 721 722 return nil 723 } 724 725 // ensureUbuntuCoreTransition will migrate systems that use "ubuntu-core" 726 // to the new "core" snap 727 func (m *SnapManager) ensureUbuntuCoreTransition() error { 728 m.state.Lock() 729 defer m.state.Unlock() 730 731 var snapst SnapState 732 err := Get(m.state, "ubuntu-core", &snapst) 733 if err == state.ErrNoState { 734 return nil 735 } 736 if err != nil && err != state.ErrNoState { 737 return err 738 } 739 740 // check that there is no change in flight already, this is a 741 // precaution to ensure the core transition is safe 742 if changeInFlight(m.state) { 743 // another change already in motion 744 return nil 745 } 746 747 // ensure we limit the retries in case something goes wrong 748 var lastUbuntuCoreTransitionAttempt time.Time 749 err = m.state.Get("ubuntu-core-transition-last-retry-time", &lastUbuntuCoreTransitionAttempt) 750 if err != nil && err != state.ErrNoState { 751 return err 752 } 753 now := time.Now() 754 if !lastUbuntuCoreTransitionAttempt.IsZero() && lastUbuntuCoreTransitionAttempt.Add(6*time.Hour).After(now) { 755 return nil 756 } 757 758 tss, trErr := TransitionCore(m.state, "ubuntu-core", "core") 759 if _, ok := trErr.(*ChangeConflictError); ok { 760 // likely just too early, retry at next Ensure 761 return nil 762 } 763 764 m.state.Set("ubuntu-core-transition-last-retry-time", now) 765 766 var retryCount int 767 err = m.state.Get("ubuntu-core-transition-retry", &retryCount) 768 if err != nil && err != state.ErrNoState { 769 return err 770 } 771 m.state.Set("ubuntu-core-transition-retry", retryCount+1) 772 773 if trErr != nil { 774 return trErr 775 } 776 777 msg := i18n.G("Transition ubuntu-core to core") 778 chg := m.state.NewChange("transition-ubuntu-core", msg) 779 for _, ts := range tss { 780 chg.AddAll(ts) 781 } 782 783 return nil 784 } 785 786 // atSeed implements at seeding policy for refreshes. 787 func (m *SnapManager) atSeed() error { 788 m.state.Lock() 789 defer m.state.Unlock() 790 var seeded bool 791 err := m.state.Get("seeded", &seeded) 792 if err != state.ErrNoState { 793 // already seeded or other error 794 return err 795 } 796 if err := m.autoRefresh.AtSeed(); err != nil { 797 return err 798 } 799 if err := m.refreshHints.AtSeed(); err != nil { 800 return err 801 } 802 return nil 803 } 804 805 var ( 806 localInstallCleanupWait = time.Duration(24 * time.Hour) 807 localInstallLastCleanup time.Time 808 ) 809 810 // localInstallCleanup removes files that might've been left behind by an 811 // old aborted local install. 812 // 813 // They're usually cleaned up, but if they're created and then snapd 814 // stops before writing the change to disk (killed, light cut, etc) 815 // it'll be left behind. 816 // 817 // The code that creates the files is in daemon/api.go's postSnaps 818 func (m *SnapManager) localInstallCleanup() error { 819 m.state.Lock() 820 defer m.state.Unlock() 821 822 now := time.Now() 823 cutoff := now.Add(-localInstallCleanupWait) 824 if localInstallLastCleanup.After(cutoff) { 825 return nil 826 } 827 localInstallLastCleanup = now 828 829 d, err := os.Open(dirs.SnapBlobDir) 830 if err != nil { 831 if os.IsNotExist(err) { 832 return nil 833 } 834 return err 835 } 836 defer d.Close() 837 838 var filenames []string 839 var fis []os.FileInfo 840 for err == nil { 841 // TODO: if we had fstatat we could avoid a bunch of stats 842 fis, err = d.Readdir(100) 843 // fis is nil if err isn't 844 for _, fi := range fis { 845 name := fi.Name() 846 if !strings.HasPrefix(name, dirs.LocalInstallBlobTempPrefix) { 847 continue 848 } 849 if fi.ModTime().After(cutoff) { 850 continue 851 } 852 filenames = append(filenames, name) 853 } 854 } 855 if err != io.EOF { 856 return err 857 } 858 return osutil.UnlinkManyAt(d, filenames) 859 } 860 861 // Ensure implements StateManager.Ensure. 862 func (m *SnapManager) Ensure() error { 863 if m.preseed { 864 return nil 865 } 866 867 // do not exit right away on error 868 errs := []error{ 869 m.atSeed(), 870 m.ensureAliasesV2(), 871 m.ensureForceDevmodeDropsDevmodeFromState(), 872 m.ensureUbuntuCoreTransition(), 873 m.ensureSnapdSnapTransition(), 874 // we should check for full regular refreshes before 875 // considering issuing a hint only refresh request 876 m.autoRefresh.Ensure(), 877 m.refreshHints.Ensure(), 878 m.catalogRefresh.Ensure(), 879 m.localInstallCleanup(), 880 } 881 882 //FIXME: use firstErr helper 883 for _, e := range errs { 884 if e != nil { 885 return e 886 } 887 } 888 889 return nil 890 }