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