github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/devicestate/devicemgr.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2021 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 devicestate 21 22 import ( 23 "context" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "os" 28 "path/filepath" 29 "regexp" 30 "strings" 31 "time" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/asserts/sysdb" 35 "github.com/snapcore/snapd/boot" 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/gadget" 38 "github.com/snapcore/snapd/i18n" 39 "github.com/snapcore/snapd/kernel/fde" 40 "github.com/snapcore/snapd/logger" 41 "github.com/snapcore/snapd/osutil" 42 "github.com/snapcore/snapd/overlord/assertstate" 43 "github.com/snapcore/snapd/overlord/auth" 44 "github.com/snapcore/snapd/overlord/configstate/config" 45 "github.com/snapcore/snapd/overlord/devicestate/internal" 46 "github.com/snapcore/snapd/overlord/hookstate" 47 "github.com/snapcore/snapd/overlord/snapstate" 48 "github.com/snapcore/snapd/overlord/state" 49 "github.com/snapcore/snapd/overlord/storecontext" 50 "github.com/snapcore/snapd/progress" 51 "github.com/snapcore/snapd/release" 52 "github.com/snapcore/snapd/seed" 53 "github.com/snapcore/snapd/snap" 54 "github.com/snapcore/snapd/snap/snapfile" 55 "github.com/snapcore/snapd/snapdenv" 56 "github.com/snapcore/snapd/strutil" 57 "github.com/snapcore/snapd/sysconfig" 58 "github.com/snapcore/snapd/systemd" 59 "github.com/snapcore/snapd/timings" 60 ) 61 62 var ( 63 cloudInitStatus = sysconfig.CloudInitStatus 64 restrictCloudInit = sysconfig.RestrictCloudInit 65 ) 66 67 // EarlyConfig is a hook set by configstate that can process early configuration 68 // during managers' startup. 69 var EarlyConfig func(st *state.State, preloadGadget func() (sysconfig.Device, *gadget.Info, error)) error 70 71 // DeviceManager is responsible for managing the device identity and device 72 // policies. 73 type DeviceManager struct { 74 // sysMode is the system mode from modeenv or "" on pre-UC20, 75 // use SystemMode instead 76 sysMode string 77 // saveAvailable keeps track whether /var/lib/snapd/save 78 // is available, i.e. exists and is mounted from ubuntu-save 79 // if the latter exists. 80 // TODO: evolve this to state to track things if we start mounting 81 // save as rw vs ro, or mount/umount it fully on demand 82 saveAvailable bool 83 84 state *state.State 85 hookMgr *hookstate.HookManager 86 87 cachedKeypairMgr asserts.KeypairManager 88 89 // newStore can make new stores for remodeling 90 newStore func(storecontext.DeviceBackend) snapstate.StoreService 91 92 bootOkRan bool 93 bootRevisionsUpdated bool 94 95 seedTimings *timings.Timings 96 97 ensureSeedInConfigRan bool 98 99 ensureInstalledRan bool 100 101 ensureTriedRecoverySystemRan bool 102 103 cloudInitAlreadyRestricted bool 104 cloudInitErrorAttemptStart *time.Time 105 cloudInitEnabledInactiveAttemptStart *time.Time 106 107 lastBecomeOperationalAttempt time.Time 108 becomeOperationalBackoff time.Duration 109 registered bool 110 reg chan struct{} 111 112 preseed bool 113 } 114 115 // Manager returns a new device manager. 116 func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, newStore func(storecontext.DeviceBackend) snapstate.StoreService) (*DeviceManager, error) { 117 delayedCrossMgrInit() 118 119 m := &DeviceManager{ 120 state: s, 121 hookMgr: hookManager, 122 newStore: newStore, 123 reg: make(chan struct{}), 124 preseed: snapdenv.Preseeding(), 125 } 126 127 modeEnv, err := maybeReadModeenv() 128 if err != nil { 129 return nil, err 130 } 131 if modeEnv != nil { 132 m.sysMode = modeEnv.Mode 133 } 134 135 s.Lock() 136 s.Cache(deviceMgrKey{}, m) 137 s.Unlock() 138 139 if err := m.confirmRegistered(); err != nil { 140 return nil, err 141 } 142 143 hookManager.Register(regexp.MustCompile("^prepare-device$"), newBasicHookStateHandler) 144 hookManager.Register(regexp.MustCompile("^install-device$"), newBasicHookStateHandler) 145 146 runner.AddHandler("generate-device-key", m.doGenerateDeviceKey, nil) 147 runner.AddHandler("request-serial", m.doRequestSerial, nil) 148 runner.AddHandler("mark-preseeded", m.doMarkPreseeded, nil) 149 runner.AddHandler("mark-seeded", m.doMarkSeeded, nil) 150 runner.AddHandler("setup-run-system", m.doSetupRunSystem, nil) 151 runner.AddHandler("restart-system-to-run-mode", m.doRestartSystemToRunMode, nil) 152 runner.AddHandler("prepare-remodeling", m.doPrepareRemodeling, nil) 153 runner.AddCleanup("prepare-remodeling", m.cleanupRemodel) 154 // this *must* always run last and finalizes a remodel 155 runner.AddHandler("set-model", m.doSetModel, nil) 156 runner.AddCleanup("set-model", m.cleanupRemodel) 157 // There is no undo for successful gadget updates. The system is 158 // rebooted during update, if it boots up to the point where snapd runs 159 // we deem the new assets (be it bootloader or firmware) functional. The 160 // deployed boot assets must be backward compatible with reverted kernel 161 // or gadget snaps. There are no further changes to the boot assets, 162 // unless a new gadget update is deployed. 163 runner.AddHandler("update-gadget-assets", m.doUpdateGadgetAssets, nil) 164 // There is no undo handler for successful boot config update. The 165 // config assets are assumed to be always backwards compatible. 166 runner.AddHandler("update-managed-boot-config", m.doUpdateManagedBootConfig, nil) 167 // kernel command line updates from a gadget supplied file 168 runner.AddHandler("update-gadget-cmdline", m.doUpdateGadgetCommandLine, m.undoUpdateGadgetCommandLine) 169 // recovery systems 170 runner.AddHandler("create-recovery-system", m.doCreateRecoverySystem, m.undoCreateRecoverySystem) 171 runner.AddHandler("finalize-recovery-system", m.doFinalizeTriedRecoverySystem, m.undoFinalizeTriedRecoverySystem) 172 runner.AddCleanup("finalize-recovery-system", m.cleanupRecoverySystem) 173 174 runner.AddBlocked(gadgetUpdateBlocked) 175 176 // wire FDE kernel hook support into boot 177 boot.HasFDESetupHook = m.hasFDESetupHook 178 boot.RunFDESetupHook = m.runFDESetupHook 179 hookManager.Register(regexp.MustCompile("^fde-setup$"), newFdeSetupHandler) 180 181 return m, nil 182 } 183 184 type genericHook struct{} 185 186 func (h genericHook) Before() error { return nil } 187 func (h genericHook) Done() error { return nil } 188 func (h genericHook) Error(err error) (bool, error) { return false, nil } 189 190 func newBasicHookStateHandler(context *hookstate.Context) hookstate.Handler { 191 return genericHook{} 192 } 193 194 func maybeReadModeenv() (*boot.Modeenv, error) { 195 modeEnv, err := boot.ReadModeenv("") 196 if err != nil && !os.IsNotExist(err) { 197 return nil, fmt.Errorf("cannot read modeenv: %v", err) 198 } 199 return modeEnv, nil 200 } 201 202 // ReloadModeenv is only useful for integration testing 203 func (m *DeviceManager) ReloadModeenv() error { 204 osutil.MustBeTestBinary("ReloadModeenv can only be called from tests") 205 modeEnv, err := maybeReadModeenv() 206 if err != nil { 207 return err 208 } 209 if modeEnv != nil { 210 m.sysMode = modeEnv.Mode 211 } 212 return nil 213 } 214 215 type SysExpectation int 216 217 const ( 218 // SysAny indicates any system is appropriate. 219 SysAny SysExpectation = iota 220 // SysHasModeenv indicates only systems with modeenv are appropriate. 221 SysHasModeenv 222 ) 223 224 // SystemMode returns the current mode of the system. 225 // An expectation about the system controls the returned mode when 226 // none is set explicitly, as it's the case on pre-UC20 systems. In 227 // which case, with SysAny, the mode defaults to implicit "run", thus 228 // covering pre-UC20 systems. With SysHasModeeenv, as there is always 229 // an explicit mode in systems that use modeenv, no implicit default 230 // is used and thus "" is returned for pre-UC20 systems. 231 func (m *DeviceManager) SystemMode(sysExpect SysExpectation) string { 232 if m.sysMode == "" { 233 if sysExpect == SysHasModeenv { 234 return "" 235 } 236 return "run" 237 } 238 return m.sysMode 239 } 240 241 // StartUp implements StateStarterUp.Startup. 242 func (m *DeviceManager) StartUp() error { 243 // TODO:UC20: ubuntu-save needs to be mounted for recover too 244 if !release.OnClassic && m.SystemMode(SysHasModeenv) == "run" { 245 // UC20 and run mode => setup /var/lib/snapd/save 246 if err := m.maybeSetupUbuntuSave(); err != nil { 247 return fmt.Errorf("cannot set up ubuntu-save: %v", err) 248 } 249 } 250 251 // TODO: setup proper timings measurements for this 252 253 m.state.Lock() 254 defer m.state.Unlock() 255 return EarlyConfig(m.state, m.preloadGadget) 256 } 257 258 func (m *DeviceManager) maybeSetupUbuntuSave() error { 259 // only called for UC20 260 261 saveMounted, err := osutil.IsMounted(dirs.SnapSaveDir) 262 if err != nil { 263 return err 264 } 265 if saveMounted { 266 logger.Noticef("save already mounted under %v", dirs.SnapSaveDir) 267 m.saveAvailable = true 268 return nil 269 } 270 271 runMntSaveMounted, err := osutil.IsMounted(boot.InitramfsUbuntuSaveDir) 272 if err != nil { 273 return err 274 } 275 if !runMntSaveMounted { 276 // we don't have ubuntu-save, save will be used directly 277 logger.Noticef("no ubuntu-save mount") 278 m.saveAvailable = true 279 return nil 280 } 281 282 logger.Noticef("bind-mounting ubuntu-save under %v", dirs.SnapSaveDir) 283 284 err = systemd.New(systemd.SystemMode, progress.Null).Mount(boot.InitramfsUbuntuSaveDir, 285 dirs.SnapSaveDir, "-o", "bind") 286 if err != nil { 287 logger.Noticef("bind-mounting ubuntu-save failed %v", err) 288 return fmt.Errorf("cannot bind mount %v under %v: %v", boot.InitramfsUbuntuSaveDir, dirs.SnapSaveDir, err) 289 } 290 m.saveAvailable = true 291 return nil 292 } 293 294 type deviceMgrKey struct{} 295 296 func deviceMgr(st *state.State) *DeviceManager { 297 mgr := st.Cached(deviceMgrKey{}) 298 if mgr == nil { 299 panic("internal error: device manager is not yet associated with state") 300 } 301 return mgr.(*DeviceManager) 302 } 303 304 func (m *DeviceManager) CanStandby() bool { 305 var seeded bool 306 if err := m.state.Get("seeded", &seeded); err != nil { 307 return false 308 } 309 return seeded 310 } 311 312 func (m *DeviceManager) confirmRegistered() error { 313 m.state.Lock() 314 defer m.state.Unlock() 315 316 device, err := m.device() 317 if err != nil { 318 return err 319 } 320 321 if device.Serial != "" { 322 m.markRegistered() 323 } 324 return nil 325 } 326 327 func (m *DeviceManager) markRegistered() { 328 if m.registered { 329 return 330 } 331 m.registered = true 332 close(m.reg) 333 } 334 335 func gadgetUpdateBlocked(cand *state.Task, running []*state.Task) bool { 336 if cand.Kind() == "update-gadget-assets" && len(running) != 0 { 337 // update-gadget-assets must be the only task running 338 return true 339 } 340 for _, other := range running { 341 if other.Kind() == "update-gadget-assets" { 342 // no other task can be started when 343 // update-gadget-assets is running 344 return true 345 } 346 } 347 348 return false 349 } 350 351 func (m *DeviceManager) changeInFlight(kind string) bool { 352 for _, chg := range m.state.Changes() { 353 if chg.Kind() == kind && !chg.Status().Ready() { 354 // change already in motion 355 return true 356 } 357 } 358 return false 359 } 360 361 // helpers to keep count of attempts to get a serial, useful to decide 362 // to give up holding off trying to auto-refresh 363 364 type ensureOperationalAttemptsKey struct{} 365 366 func incEnsureOperationalAttempts(st *state.State) { 367 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 368 st.Cache(ensureOperationalAttemptsKey{}, cur+1) 369 } 370 371 func ensureOperationalAttempts(st *state.State) int { 372 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 373 return cur 374 } 375 376 // ensureOperationalShouldBackoff returns whether we should abstain from 377 // further become-operational tentatives while its backoff interval is 378 // not expired. 379 func (m *DeviceManager) ensureOperationalShouldBackoff(now time.Time) bool { 380 if !m.lastBecomeOperationalAttempt.IsZero() && m.lastBecomeOperationalAttempt.Add(m.becomeOperationalBackoff).After(now) { 381 return true 382 } 383 if m.becomeOperationalBackoff == 0 { 384 m.becomeOperationalBackoff = 5 * time.Minute 385 } else { 386 newBackoff := m.becomeOperationalBackoff * 2 387 if newBackoff > (12 * time.Hour) { 388 newBackoff = 24 * time.Hour 389 } 390 m.becomeOperationalBackoff = newBackoff 391 } 392 m.lastBecomeOperationalAttempt = now 393 return false 394 } 395 396 func setClassicFallbackModel(st *state.State, device *auth.DeviceState) error { 397 err := assertstate.Add(st, sysdb.GenericClassicModel()) 398 if err != nil && !asserts.IsUnaccceptedUpdate(err) { 399 return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err) 400 } 401 device.Brand = "generic" 402 device.Model = "generic-classic" 403 if err := internal.SetDevice(st, device); err != nil { 404 return err 405 } 406 return nil 407 } 408 409 func (m *DeviceManager) ensureOperational() error { 410 m.state.Lock() 411 defer m.state.Unlock() 412 413 if m.SystemMode(SysAny) != "run" { 414 // avoid doing registration in ephemeral mode 415 // note: this also stop auto-refreshes indirectly 416 return nil 417 } 418 419 device, err := m.device() 420 if err != nil { 421 return err 422 } 423 424 if device.Serial != "" { 425 // serial is set, we are all set 426 return nil 427 } 428 429 perfTimings := timings.New(map[string]string{"ensure": "become-operational"}) 430 431 // conditions to trigger device registration 432 // 433 // * have a model assertion with a gadget (core and 434 // device-like classic) in which case we need also to wait 435 // for the gadget to have been installed though 436 // TODO: consider a way to support lazy registration on classic 437 // even with a gadget and some preseeded snaps 438 // 439 // * classic with a model assertion with a non-default store specified 440 // * lazy classic case (might have a model with no gadget nor store 441 // or no model): we wait to have some snaps installed or be 442 // in the process to install some 443 444 var seeded bool 445 err = m.state.Get("seeded", &seeded) 446 if err != nil && err != state.ErrNoState { 447 return err 448 } 449 450 if device.Brand == "" || device.Model == "" { 451 if !release.OnClassic || !seeded { 452 return nil 453 } 454 // we are on classic and seeded but there is no model: 455 // use a fallback model! 456 err := setClassicFallbackModel(m.state, device) 457 if err != nil { 458 return err 459 } 460 } 461 462 if m.changeInFlight("become-operational") { 463 return nil 464 } 465 466 var storeID, gadget string 467 model, err := m.Model() 468 if err != nil && err != state.ErrNoState { 469 return err 470 } 471 if err == nil { 472 gadget = model.Gadget() 473 storeID = model.Store() 474 } else { 475 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 476 } 477 478 if gadget == "" && storeID == "" { 479 // classic: if we have no gadget and no non-default store 480 // wait to have snaps or snap installation 481 482 n, err := snapstate.NumSnaps(m.state) 483 if err != nil { 484 return err 485 } 486 if n == 0 && !snapstate.Installing(m.state) { 487 return nil 488 } 489 } 490 491 var hasPrepareDeviceHook bool 492 // if there's a gadget specified wait for it 493 if gadget != "" { 494 // if have a gadget wait until seeded to proceed 495 if !seeded { 496 // this will be run again, so eventually when the system is 497 // seeded the code below runs 498 return nil 499 500 } 501 502 gadgetInfo, err := snapstate.CurrentInfo(m.state, gadget) 503 if err != nil { 504 return err 505 } 506 hasPrepareDeviceHook = (gadgetInfo.Hooks["prepare-device"] != nil) 507 } 508 509 // have some backoff between full retries 510 if m.ensureOperationalShouldBackoff(time.Now()) { 511 return nil 512 } 513 // increment attempt count 514 incEnsureOperationalAttempts(m.state) 515 516 // XXX: some of these will need to be split and use hooks 517 // retries might need to embrace more than one "task" then, 518 // need to be careful 519 520 tasks := []*state.Task{} 521 522 var prepareDevice *state.Task 523 if hasPrepareDeviceHook { 524 summary := i18n.G("Run prepare-device hook") 525 hooksup := &hookstate.HookSetup{ 526 Snap: gadget, 527 Hook: "prepare-device", 528 } 529 prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil) 530 tasks = append(tasks, prepareDevice) 531 } 532 533 genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key")) 534 if prepareDevice != nil { 535 genKey.WaitFor(prepareDevice) 536 } 537 tasks = append(tasks, genKey) 538 requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial")) 539 requestSerial.WaitFor(genKey) 540 tasks = append(tasks, requestSerial) 541 542 chg := m.state.NewChange("become-operational", i18n.G("Initialize device")) 543 chg.AddAll(state.NewTaskSet(tasks...)) 544 545 state.TagTimingsWithChange(perfTimings, chg) 546 perfTimings.Save(m.state) 547 548 return nil 549 } 550 551 var startTime time.Time 552 553 func init() { 554 startTime = time.Now() 555 } 556 557 func (m *DeviceManager) setTimeOnce(name string, t time.Time) error { 558 var prev time.Time 559 err := m.state.Get(name, &prev) 560 if err != nil && err != state.ErrNoState { 561 return err 562 } 563 if !prev.IsZero() { 564 // already set 565 return nil 566 } 567 m.state.Set(name, t) 568 return nil 569 } 570 571 func (m *DeviceManager) seedStart() (*timings.Timings, error) { 572 if m.seedTimings != nil { 573 // reuse the early cached one 574 return m.seedTimings, nil 575 } 576 perfTimings := timings.New(map[string]string{"ensure": "seed"}) 577 578 var recordedStart string 579 var start time.Time 580 if m.preseed { 581 recordedStart = "preseed-start-time" 582 start = timeNow() 583 } else { 584 recordedStart = "seed-start-time" 585 start = startTime 586 } 587 if err := m.setTimeOnce(recordedStart, start); err != nil { 588 return nil, err 589 } 590 return perfTimings, nil 591 } 592 593 func (m *DeviceManager) preloadGadget() (sysconfig.Device, *gadget.Info, error) { 594 var sysLabel string 595 modeEnv, err := maybeReadModeenv() 596 if err != nil { 597 return nil, nil, err 598 } 599 if modeEnv != nil { 600 sysLabel = modeEnv.RecoverySystem 601 } 602 603 // we time preloadGadget + first ensureSeeded together 604 // under --ensure=seed 605 tm, err := m.seedStart() 606 if err != nil { 607 return nil, nil, err 608 } 609 // cached for first ensureSeeded 610 m.seedTimings = tm 611 612 // Here we behave as if there was no gadget if we encounter 613 // errors, under the assumption that those will be resurfaced 614 // in ensureSeed. This preserves having a failing to seed 615 // snapd continuing running. 616 // 617 // TODO: consider changing that again but we need consider the 618 // effect of the different failure mode. 619 // 620 // We also assume that anything sensitive will not be guarded 621 // just by option flags. For example automatic user creation 622 // also requires the model to be known/set. Otherwise ignoring 623 // errors here would be problematic. 624 var deviceSeed seed.Seed 625 timings.Run(tm, "import-assertions[early]", "early import assertions from seed", func(nested timings.Measurer) { 626 deviceSeed, err = loadDeviceSeed(m.state, sysLabel) 627 }) 628 if err != nil { 629 // this same error will be resurfaced in ensureSeed later 630 if err != seed.ErrNoAssertions { 631 logger.Debugf("early import assertions from seed failed: %v", err) 632 } 633 return nil, nil, state.ErrNoState 634 } 635 model := deviceSeed.Model() 636 if model.Gadget() == "" { 637 // no gadget 638 return nil, nil, state.ErrNoState 639 } 640 var gi *gadget.Info 641 timings.Run(tm, "preload-verified-gadget-metadata", "preload verified gadget metadata from seed", func(nested timings.Measurer) { 642 gi, err = func() (*gadget.Info, error) { 643 if err := deviceSeed.LoadEssentialMeta([]snap.Type{snap.TypeGadget}, nested); err != nil { 644 return nil, err 645 } 646 essGadget := deviceSeed.EssentialSnaps() 647 if len(essGadget) != 1 { 648 return nil, fmt.Errorf("multiple gadgets among essential snaps are unexpected") 649 } 650 snapf, err := snapfile.Open(essGadget[0].Path) 651 if err != nil { 652 return nil, err 653 } 654 return gadget.ReadInfoFromSnapFile(snapf, model) 655 }() 656 }) 657 if err != nil { 658 logger.Noticef("preload verified gadget metadata from seed failed: %v", err) 659 return nil, nil, state.ErrNoState 660 } 661 662 dev := newModelDeviceContext(m, model) 663 return dev, gi, nil 664 } 665 666 var populateStateFromSeed = populateStateFromSeedImpl 667 668 // ensureSeeded makes sure that the snaps from seed.yaml get installed 669 // with the matching assertions 670 func (m *DeviceManager) ensureSeeded() error { 671 m.state.Lock() 672 defer m.state.Unlock() 673 674 var seeded bool 675 err := m.state.Get("seeded", &seeded) 676 if err != nil && err != state.ErrNoState { 677 return err 678 } 679 if seeded { 680 return nil 681 } 682 683 if m.changeInFlight("seed") { 684 return nil 685 } 686 687 perfTimings, err := m.seedStart() 688 if err != nil { 689 return err 690 } 691 // we time preloadGadget + first ensureSeeded together 692 // succcessive ensureSeeded should be timed separately 693 m.seedTimings = nil 694 695 var opts *populateStateFromSeedOptions 696 if m.preseed { 697 opts = &populateStateFromSeedOptions{Preseed: true} 698 } else { 699 modeEnv, err := maybeReadModeenv() 700 if err != nil { 701 return err 702 } 703 if modeEnv != nil { 704 opts = &populateStateFromSeedOptions{ 705 Mode: modeEnv.Mode, 706 Label: modeEnv.RecoverySystem, 707 } 708 } 709 } 710 711 var tsAll []*state.TaskSet 712 timings.Run(perfTimings, "state-from-seed", "populate state from seed", func(tm timings.Measurer) { 713 tsAll, err = populateStateFromSeed(m.state, opts, tm) 714 }) 715 if err != nil { 716 return err 717 } 718 if len(tsAll) == 0 { 719 return nil 720 } 721 722 chg := m.state.NewChange("seed", "Initialize system state") 723 for _, ts := range tsAll { 724 chg.AddAll(ts) 725 } 726 m.state.EnsureBefore(0) 727 728 state.TagTimingsWithChange(perfTimings, chg) 729 perfTimings.Save(m.state) 730 return nil 731 } 732 733 func (m *DeviceManager) ensureBootOk() error { 734 m.state.Lock() 735 defer m.state.Unlock() 736 737 if release.OnClassic { 738 return nil 739 } 740 741 // boot-ok/update-boot-revision is only relevant in run-mode 742 if m.SystemMode(SysAny) != "run" { 743 return nil 744 } 745 746 if !m.bootOkRan { 747 deviceCtx, err := DeviceCtx(m.state, nil, nil) 748 if err != nil && err != state.ErrNoState { 749 return err 750 } 751 if err == nil { 752 if err := boot.MarkBootSuccessful(deviceCtx); err != nil { 753 return err 754 } 755 } 756 m.bootOkRan = true 757 } 758 759 if !m.bootRevisionsUpdated { 760 if err := snapstate.UpdateBootRevisions(m.state); err != nil { 761 return err 762 } 763 m.bootRevisionsUpdated = true 764 } 765 766 return nil 767 } 768 769 func (m *DeviceManager) ensureCloudInitRestricted() error { 770 m.state.Lock() 771 defer m.state.Unlock() 772 773 if m.cloudInitAlreadyRestricted { 774 return nil 775 } 776 777 var seeded bool 778 err := m.state.Get("seeded", &seeded) 779 if err != nil && err != state.ErrNoState { 780 return err 781 } 782 783 // On Ubuntu Core devices that have been seeded, we want to restrict 784 // cloud-init so that its more dangerous (for an IoT device at least) 785 // features are not exploitable after a device has been seeded. This allows 786 // device administrators and other tools (such as multipass) to still 787 // configure an Ubuntu Core device on first boot, and also allows cloud 788 // vendors to run cloud-init with only a specific data-source on subsequent 789 // boots but disallows arbitrary cloud-init {user,meta,vendor}-data to be 790 // attached to a device via a USB drive and inject code onto the device. 791 792 if seeded && !release.OnClassic { 793 opts := &sysconfig.CloudInitRestrictOptions{} 794 795 // check the current state of cloud-init, if it is disabled or already 796 // restricted then we have nothing to do 797 cloudInitStatus, err := cloudInitStatus() 798 if err != nil { 799 return err 800 } 801 statusMsg := "" 802 803 switch cloudInitStatus { 804 case sysconfig.CloudInitDisabledPermanently, sysconfig.CloudInitRestrictedBySnapd: 805 // already been permanently disabled, nothing to do 806 m.cloudInitAlreadyRestricted = true 807 return nil 808 case sysconfig.CloudInitNotFound: 809 // no cloud init at all 810 statusMsg = "not found" 811 case sysconfig.CloudInitUntriggered: 812 // hasn't been used 813 statusMsg = "reported to be in disabled state" 814 case sysconfig.CloudInitDone: 815 // is done being used 816 statusMsg = "reported to be done" 817 case sysconfig.CloudInitErrored: 818 // cloud-init errored, so we give the device admin / developer a few 819 // minutes to reboot the machine to re-run cloud-init and try again, 820 // otherwise we will disable cloud-init permanently 821 822 // initialize the time we first saw cloud-init in error state 823 if m.cloudInitErrorAttemptStart == nil { 824 // save the time we started the attempt to restrict 825 now := timeNow() 826 m.cloudInitErrorAttemptStart = &now 827 logger.Noticef("System initialized, cloud-init reported to be in error state, will disable in 3 minutes") 828 } 829 830 // check if 3 minutes have elapsed since we first saw cloud-init in 831 // error state 832 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitErrorAttemptStart) 833 if timeSinceFirstAttempt <= 3*time.Minute { 834 // we need to keep waiting for cloud-init, up to 3 minutes 835 nextCheck := 3*time.Minute - timeSinceFirstAttempt 836 m.state.EnsureBefore(nextCheck) 837 return nil 838 } 839 // otherwise, we timed out waiting for cloud-init to be fixed or 840 // rebooted and should restrict cloud-init 841 // we will restrict cloud-init below, but we need to force the 842 // disable, as by default RestrictCloudInit will error on state 843 // CloudInitErrored 844 opts.ForceDisable = true 845 statusMsg = "reported to be in error state after 3 minutes" 846 default: 847 // in unknown states we are conservative and let the device run for 848 // a while to see if it transitions to a known state, but eventually 849 // will disable anyways 850 fallthrough 851 case sysconfig.CloudInitEnabled: 852 // we will give cloud-init up to 5 minutes to try and run, if it 853 // still has not transitioned to some other known state, then we 854 // will give up waiting for it and disable it anyways 855 856 // initialize the first time we saw cloud-init in enabled state 857 if m.cloudInitEnabledInactiveAttemptStart == nil { 858 // save the time we started the attempt to restrict 859 now := timeNow() 860 m.cloudInitEnabledInactiveAttemptStart = &now 861 } 862 863 // keep re-scheduling again in 10 seconds until we hit 5 minutes 864 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitEnabledInactiveAttemptStart) 865 if timeSinceFirstAttempt <= 5*time.Minute { 866 // TODO: should we log a message here about waiting for cloud-init 867 // to be in a "known state"? 868 m.state.EnsureBefore(10 * time.Second) 869 return nil 870 } 871 872 // otherwise, we gave cloud-init 5 minutes to run, if it's still not 873 // done disable it anyways 874 // note we we need to force the disable, as by default 875 // RestrictCloudInit will error on state CloudInitEnabled 876 opts.ForceDisable = true 877 statusMsg = "failed to transition to done or error state after 5 minutes" 878 } 879 880 // we should always have a model if we are seeded and are not on classic 881 model, err := m.Model() 882 if err != nil { 883 return err 884 } 885 886 // For UC20, we want to always disable cloud-init after it has run on 887 // first boot unless we are in a "real cloud", i.e. not using NoCloud, 888 // or if we installed cloud-init configuration from the gadget 889 if model.Grade() != asserts.ModelGradeUnset { 890 // always disable NoCloud/local datasources after first boot on 891 // uc20, this is because even if the gadget has a cloud.conf 892 // configuring NoCloud, the config installed by cloud-init should 893 // not work differently for later boots, so it's sufficient that 894 // NoCloud runs on first-boot and never again 895 opts.DisableAfterLocalDatasourcesRun = true 896 } 897 898 // now restrict/disable cloud-init 899 res, err := restrictCloudInit(cloudInitStatus, opts) 900 if err != nil { 901 return err 902 } 903 904 // log a message about what we did 905 actionMsg := "" 906 switch res.Action { 907 case "disable": 908 actionMsg = "disabled permanently" 909 case "restrict": 910 // log different messages depending on what datasource was used 911 if res.DataSource == "NoCloud" { 912 actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label" 913 } else { 914 // all other datasources just log that we limited it to that datasource 915 actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource) 916 } 917 default: 918 return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action) 919 } 920 logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg) 921 922 m.cloudInitAlreadyRestricted = true 923 } 924 925 return nil 926 } 927 928 func (m *DeviceManager) ensureInstalled() error { 929 m.state.Lock() 930 defer m.state.Unlock() 931 932 if release.OnClassic { 933 return nil 934 } 935 936 if m.ensureInstalledRan { 937 return nil 938 } 939 940 if m.SystemMode(SysHasModeenv) != "install" { 941 return nil 942 } 943 944 var seeded bool 945 err := m.state.Get("seeded", &seeded) 946 if err != nil && err != state.ErrNoState { 947 return err 948 } 949 if !seeded { 950 return nil 951 } 952 953 if m.changeInFlight("install-system") { 954 return nil 955 } 956 957 model, err := m.Model() 958 if err != nil && err != state.ErrNoState { 959 return err 960 } 961 if err != nil { 962 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 963 } 964 965 // check if the gadget has an install-device hook 966 var hasInstallDeviceHook bool 967 968 gadgetInfo, err := snapstate.CurrentInfo(m.state, model.Gadget()) 969 if err != nil { 970 return fmt.Errorf("internal error: device is seeded in install mode but has no gadget snap: %v", err) 971 } 972 hasInstallDeviceHook = (gadgetInfo.Hooks["install-device"] != nil) 973 974 m.ensureInstalledRan = true 975 976 var prev *state.Task 977 setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode")) 978 979 tasks := []*state.Task{setupRunSystem} 980 addTask := func(t *state.Task) { 981 t.WaitFor(prev) 982 tasks = append(tasks, t) 983 prev = t 984 } 985 prev = setupRunSystem 986 987 // add the install-device hook before ensure-next-boot-to-run-mode if it 988 // exists in the snap 989 var installDevice *state.Task 990 if hasInstallDeviceHook { 991 summary := i18n.G("Run install-device hook") 992 hooksup := &hookstate.HookSetup{ 993 // TODO: what's a reasonable timeout for the install-device hook? 994 Snap: model.Gadget(), 995 Hook: "install-device", 996 } 997 installDevice = hookstate.HookTask(m.state, summary, hooksup, nil) 998 addTask(installDevice) 999 } 1000 1001 restartSystem := m.state.NewTask("restart-system-to-run-mode", i18n.G("Ensure next boot to run mode")) 1002 addTask(restartSystem) 1003 1004 if installDevice != nil { 1005 // reference used by snapctl reboot 1006 installDevice.Set("restart-task", restartSystem.ID()) 1007 } 1008 1009 chg := m.state.NewChange("install-system", i18n.G("Install the system")) 1010 chg.AddAll(state.NewTaskSet(tasks...)) 1011 1012 return nil 1013 } 1014 1015 var timeNow = time.Now 1016 1017 // StartOfOperationTime returns the time when snapd started operating, 1018 // and sets it in the state when called for the first time. 1019 // The StartOfOperationTime time is seed-time if available, 1020 // or current time otherwise. 1021 func (m *DeviceManager) StartOfOperationTime() (time.Time, error) { 1022 var opTime time.Time 1023 if m.preseed { 1024 return opTime, fmt.Errorf("internal error: unexpected call to StartOfOperationTime in preseed mode") 1025 } 1026 err := m.state.Get("start-of-operation-time", &opTime) 1027 if err == nil { 1028 return opTime, nil 1029 } 1030 if err != nil && err != state.ErrNoState { 1031 return opTime, err 1032 } 1033 1034 // start-of-operation-time not set yet, use seed-time if available 1035 var seedTime time.Time 1036 err = m.state.Get("seed-time", &seedTime) 1037 if err != nil && err != state.ErrNoState { 1038 return opTime, err 1039 } 1040 if err == nil { 1041 opTime = seedTime 1042 } else { 1043 opTime = timeNow() 1044 } 1045 m.state.Set("start-of-operation-time", opTime) 1046 return opTime, nil 1047 } 1048 1049 func markSeededInConfig(st *state.State) error { 1050 var seedDone bool 1051 tr := config.NewTransaction(st) 1052 if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) { 1053 return err 1054 } 1055 if !seedDone { 1056 if err := tr.Set("core", "seed.loaded", true); err != nil { 1057 return err 1058 } 1059 tr.Commit() 1060 } 1061 return nil 1062 } 1063 1064 func (m *DeviceManager) ensureSeedInConfig() error { 1065 m.state.Lock() 1066 defer m.state.Unlock() 1067 1068 if !m.ensureSeedInConfigRan { 1069 // get global seeded option 1070 var seeded bool 1071 if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState { 1072 return err 1073 } 1074 if !seeded { 1075 // wait for ensure again, this is fine because 1076 // doMarkSeeded will run "EnsureBefore(0)" 1077 return nil 1078 } 1079 1080 // Sync seeding with the configuration state. We need to 1081 // do this here to ensure that old systems which did not 1082 // set the configuration on seeding get the configuration 1083 // update too. 1084 if err := markSeededInConfig(m.state); err != nil { 1085 return err 1086 } 1087 m.ensureSeedInConfigRan = true 1088 } 1089 1090 return nil 1091 1092 } 1093 1094 func (m *DeviceManager) appendTriedRecoverySystem(label string) error { 1095 // state is locked by the caller 1096 1097 var triedSystems []string 1098 if err := m.state.Get("tried-systems", &triedSystems); err != nil && err != state.ErrNoState { 1099 return err 1100 } 1101 if strutil.ListContains(triedSystems, label) { 1102 // system already recorded as tried? 1103 return nil 1104 } 1105 triedSystems = append(triedSystems, label) 1106 m.state.Set("tried-systems", triedSystems) 1107 return nil 1108 } 1109 1110 func (m *DeviceManager) ensureTriedRecoverySystem() error { 1111 if release.OnClassic { 1112 return nil 1113 } 1114 // nothing to do if not UC20 and run mode 1115 if m.SystemMode(SysHasModeenv) != "run" { 1116 return nil 1117 } 1118 if m.ensureTriedRecoverySystemRan { 1119 return nil 1120 } 1121 1122 m.state.Lock() 1123 defer m.state.Unlock() 1124 1125 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1126 if err != nil && err != state.ErrNoState { 1127 return err 1128 } 1129 outcome, label, err := boot.InspectTryRecoverySystemOutcome(deviceCtx) 1130 if err != nil { 1131 if !boot.IsInconsistentRecoverySystemState(err) { 1132 return err 1133 } 1134 // boot variables state was inconsistent 1135 logger.Noticef("tried recovery system outcome error: %v", err) 1136 } 1137 switch outcome { 1138 case boot.TryRecoverySystemOutcomeSuccess: 1139 logger.Noticef("tried recovery system %q was successful", label) 1140 if err := m.appendTriedRecoverySystem(label); err != nil { 1141 return err 1142 } 1143 case boot.TryRecoverySystemOutcomeFailure: 1144 logger.Noticef("tried recovery system %q failed", label) 1145 case boot.TryRecoverySystemOutcomeInconsistent: 1146 logger.Noticef("inconsistent outcome of a tried recovery system") 1147 case boot.TryRecoverySystemOutcomeNoneTried: 1148 // no system was tried 1149 } 1150 if outcome != boot.TryRecoverySystemOutcomeNoneTried { 1151 if err := boot.ClearTryRecoverySystem(deviceCtx, label); err != nil { 1152 logger.Noticef("cannot clear tried recovery system status: %v", err) 1153 return err 1154 } 1155 } 1156 1157 m.ensureTriedRecoverySystemRan = true 1158 return nil 1159 } 1160 1161 type ensureError struct { 1162 errs []error 1163 } 1164 1165 func (e *ensureError) Error() string { 1166 if len(e.errs) == 1 { 1167 return fmt.Sprintf("devicemgr: %v", e.errs[0]) 1168 } 1169 parts := []string{"devicemgr:"} 1170 for _, e := range e.errs { 1171 parts = append(parts, e.Error()) 1172 } 1173 return strings.Join(parts, "\n - ") 1174 } 1175 1176 // no \n allowed in warnings 1177 var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.` 1178 1179 // Ensure implements StateManager.Ensure. 1180 func (m *DeviceManager) Ensure() error { 1181 var errs []error 1182 1183 if err := m.ensureSeeded(); err != nil { 1184 m.state.Lock() 1185 m.state.Warnf(seedFailureFmt, err) 1186 m.state.Unlock() 1187 errs = append(errs, fmt.Errorf("cannot seed: %v", err)) 1188 } 1189 1190 if !m.preseed { 1191 if err := m.ensureCloudInitRestricted(); err != nil { 1192 errs = append(errs, err) 1193 } 1194 1195 if err := m.ensureOperational(); err != nil { 1196 errs = append(errs, err) 1197 } 1198 1199 if err := m.ensureBootOk(); err != nil { 1200 errs = append(errs, err) 1201 } 1202 1203 if err := m.ensureSeedInConfig(); err != nil { 1204 errs = append(errs, err) 1205 } 1206 1207 if err := m.ensureInstalled(); err != nil { 1208 errs = append(errs, err) 1209 } 1210 1211 if err := m.ensureTriedRecoverySystem(); err != nil { 1212 errs = append(errs, err) 1213 } 1214 } 1215 1216 if len(errs) > 0 { 1217 return &ensureError{errs} 1218 } 1219 1220 return nil 1221 } 1222 1223 // ResetToPostBootState is only useful for integration testing. 1224 func (m *DeviceManager) ResetToPostBootState() { 1225 osutil.MustBeTestBinary("ResetToPostBootState can only be called from tests") 1226 m.bootOkRan = false 1227 m.bootRevisionsUpdated = false 1228 m.ensureTriedRecoverySystemRan = false 1229 } 1230 1231 var errNoSaveSupport = errors.New("no save directory before UC20") 1232 1233 // withSaveDir invokes a function making sure save dir is available. 1234 // Under UC16/18 it returns errNoSaveSupport 1235 // For UC20 it also checks that ubuntu-save is available/mounted. 1236 func (m *DeviceManager) withSaveDir(f func() error) error { 1237 // we use the model to check whether this is a UC20 device 1238 model, err := m.Model() 1239 if err == state.ErrNoState { 1240 return fmt.Errorf("internal error: cannot access save dir before a model is set") 1241 } 1242 if err != nil { 1243 return err 1244 } 1245 if model.Grade() == asserts.ModelGradeUnset { 1246 return errNoSaveSupport 1247 } 1248 // at this point we need save available 1249 if !m.saveAvailable { 1250 return fmt.Errorf("internal error: save dir is unavailable") 1251 } 1252 1253 return f() 1254 } 1255 1256 // withSaveAssertDB invokes a function making the save device assertion 1257 // backup database available to it. 1258 // Under UC16/18 it returns errNoSaveSupport 1259 // For UC20 it also checks that ubuntu-save is available/mounted. 1260 func (m *DeviceManager) withSaveAssertDB(f func(*asserts.Database) error) error { 1261 return m.withSaveDir(func() error { 1262 // open an ancillary backup assertion database in save/device 1263 assertDB, err := sysdb.OpenAt(dirs.SnapDeviceSaveDir) 1264 if err != nil { 1265 return err 1266 } 1267 return f(assertDB) 1268 }) 1269 } 1270 1271 // withKeypairMgr invokes a function making the device KeypairManager 1272 // available to it. 1273 // It uses the right location for the manager depending on UC16/18 vs 20, 1274 // the latter uses ubuntu-save. 1275 // For UC20 it also checks that ubuntu-save is available/mounted. 1276 func (m *DeviceManager) withKeypairMgr(f func(asserts.KeypairManager) error) error { 1277 // we use the model to check whether this is a UC20 device 1278 // TODO: during a theoretical UC18->20 remodel the location of 1279 // keypair manager keys would move, we will need dedicated code 1280 // to deal with that, this code typically will return the old location 1281 // until a restart 1282 model, err := m.Model() 1283 if err == state.ErrNoState { 1284 return fmt.Errorf("internal error: cannot access device keypair manager before a model is set") 1285 } 1286 if err != nil { 1287 return err 1288 } 1289 underSave := false 1290 if model.Grade() != asserts.ModelGradeUnset { 1291 // on UC20 the keys are kept under the save dir 1292 underSave = true 1293 } 1294 where := dirs.SnapDeviceDir 1295 if underSave { 1296 // at this point we need save available 1297 if !m.saveAvailable { 1298 return fmt.Errorf("internal error: cannot access device keypair manager if ubuntu-save is unavailable") 1299 } 1300 where = dirs.SnapDeviceSaveDir 1301 } 1302 keypairMgr := m.cachedKeypairMgr 1303 if keypairMgr == nil { 1304 var err error 1305 keypairMgr, err = asserts.OpenFSKeypairManager(where) 1306 if err != nil { 1307 return err 1308 } 1309 m.cachedKeypairMgr = keypairMgr 1310 } 1311 return f(keypairMgr) 1312 } 1313 1314 // TODO:UC20: we need proper encapsulated support to read 1315 // tpm-policy-auth-key from save if the latter can get unmounted on 1316 // demand 1317 1318 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 1319 device, err := m.device() 1320 if err != nil { 1321 return nil, err 1322 } 1323 1324 if device.KeyID == "" { 1325 return nil, state.ErrNoState 1326 } 1327 1328 var privKey asserts.PrivateKey 1329 err = m.withKeypairMgr(func(keypairMgr asserts.KeypairManager) (err error) { 1330 privKey, err = keypairMgr.Get(device.KeyID) 1331 if err != nil { 1332 return fmt.Errorf("cannot read device key pair: %v", err) 1333 } 1334 return nil 1335 }) 1336 if err != nil { 1337 return nil, err 1338 } 1339 return privKey, nil 1340 } 1341 1342 // Registered returns a channel that is closed when the device is known to have been registered. 1343 func (m *DeviceManager) Registered() <-chan struct{} { 1344 return m.reg 1345 } 1346 1347 // device returns current device state. 1348 func (m *DeviceManager) device() (*auth.DeviceState, error) { 1349 return internal.Device(m.state) 1350 } 1351 1352 // setDevice sets the device details in the state. 1353 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 1354 return internal.SetDevice(m.state, device) 1355 } 1356 1357 // Model returns the device model assertion. 1358 func (m *DeviceManager) Model() (*asserts.Model, error) { 1359 return findModel(m.state) 1360 } 1361 1362 // Serial returns the device serial assertion. 1363 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 1364 return findSerial(m.state, nil) 1365 } 1366 1367 type SystemModeInfo struct { 1368 Mode string 1369 HasModeenv bool 1370 Seeded bool 1371 BootFlags []string 1372 HostDataLocations []string 1373 } 1374 1375 // SystemModeInfo returns details about the current system mode the device is in. 1376 func (m *DeviceManager) SystemModeInfo() (*SystemModeInfo, error) { 1377 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1378 if err == state.ErrNoState { 1379 return nil, fmt.Errorf("cannot report system mode information before device model is acknowledged") 1380 } 1381 if err != nil { 1382 return nil, err 1383 } 1384 1385 var seeded bool 1386 err = m.state.Get("seeded", &seeded) 1387 if err != nil && err != state.ErrNoState { 1388 return nil, err 1389 } 1390 1391 mode := deviceCtx.SystemMode() 1392 smi := SystemModeInfo{ 1393 Mode: mode, 1394 HasModeenv: deviceCtx.HasModeenv(), 1395 Seeded: seeded, 1396 } 1397 if smi.HasModeenv { 1398 bootFlags, err := boot.BootFlags(deviceCtx) 1399 if err != nil { 1400 return nil, err 1401 } 1402 smi.BootFlags = bootFlags 1403 1404 hostDataLocs, err := boot.HostUbuntuDataForMode(mode) 1405 if err != nil { 1406 return nil, err 1407 } 1408 smi.HostDataLocations = hostDataLocs 1409 } 1410 return &smi, nil 1411 } 1412 1413 type SystemAction struct { 1414 Title string 1415 Mode string 1416 } 1417 1418 type System struct { 1419 // Current is true when the system running now was installed from that 1420 // seed 1421 Current bool 1422 // Label of the seed system 1423 Label string 1424 // Model assertion of the system 1425 Model *asserts.Model 1426 // Brand information 1427 Brand *asserts.Account 1428 // Actions available for this system 1429 Actions []SystemAction 1430 } 1431 1432 var defaultSystemActions = []SystemAction{ 1433 {Title: "Install", Mode: "install"}, 1434 } 1435 var currentSystemActions = []SystemAction{ 1436 {Title: "Reinstall", Mode: "install"}, 1437 {Title: "Recover", Mode: "recover"}, 1438 {Title: "Run normally", Mode: "run"}, 1439 } 1440 var recoverSystemActions = []SystemAction{ 1441 {Title: "Reinstall", Mode: "install"}, 1442 {Title: "Run normally", Mode: "run"}, 1443 } 1444 1445 var ErrNoSystems = errors.New("no systems seeds") 1446 1447 // Systems list the available recovery/seeding systems. Returns the list of 1448 // systems, ErrNoSystems when no systems seeds were found or other error. 1449 func (m *DeviceManager) Systems() ([]*System, error) { 1450 // it's tough luck when we cannot determine the current system seed 1451 systemMode := m.SystemMode(SysAny) 1452 currentSys, _ := currentSystemForMode(m.state, systemMode) 1453 1454 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 1455 if err != nil && !os.IsNotExist(err) { 1456 return nil, fmt.Errorf("cannot list available systems: %v", err) 1457 } 1458 if len(systemLabels) == 0 { 1459 // maybe not a UC20 system 1460 return nil, ErrNoSystems 1461 } 1462 1463 var systems []*System 1464 for _, fpLabel := range systemLabels { 1465 label := filepath.Base(fpLabel) 1466 system, err := systemFromSeed(label, currentSys) 1467 if err != nil { 1468 // TODO:UC20 add a Broken field to the seed system like 1469 // we do for snap.Info 1470 logger.Noticef("cannot load system %q seed: %v", label, err) 1471 continue 1472 } 1473 systems = append(systems, system) 1474 } 1475 return systems, nil 1476 } 1477 1478 var ErrUnsupportedAction = errors.New("unsupported action") 1479 1480 // Reboot triggers a reboot into the given systemLabel and mode. 1481 // 1482 // When called without a systemLabel and without a mode it will just 1483 // trigger a regular reboot. 1484 // 1485 // When called without a systemLabel but with a mode it will use 1486 // the current system to enter the given mode. 1487 // 1488 // Note that "recover" and "run" modes are only available for the 1489 // current system. 1490 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1491 rebootCurrent := func() { 1492 logger.Noticef("rebooting system") 1493 m.state.RequestRestart(state.RestartSystemNow) 1494 } 1495 1496 // most simple case: just reboot 1497 if systemLabel == "" && mode == "" { 1498 m.state.Lock() 1499 defer m.state.Unlock() 1500 1501 rebootCurrent() 1502 return nil 1503 } 1504 1505 // no systemLabel means "current" so get the current system label 1506 if systemLabel == "" { 1507 systemMode := m.SystemMode(SysAny) 1508 currentSys, err := currentSystemForMode(m.state, systemMode) 1509 if err != nil { 1510 return fmt.Errorf("cannot get current system: %v", err) 1511 } 1512 systemLabel = currentSys.System 1513 } 1514 1515 switched := func(systemLabel string, sysAction *SystemAction) { 1516 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1517 m.state.RequestRestart(state.RestartSystemNow) 1518 } 1519 // even if we are already in the right mode we restart here by 1520 // passing rebootCurrent as this is what the user requested 1521 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1522 } 1523 1524 // RequestSystemAction requests the provided system to be run in a 1525 // given mode as specified by action. 1526 // A system reboot will be requested when the request can be 1527 // successfully carried out. 1528 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1529 if systemLabel == "" { 1530 return fmt.Errorf("internal error: system label is unset") 1531 } 1532 1533 nop := func() {} 1534 switched := func(systemLabel string, sysAction *SystemAction) { 1535 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1536 m.state.RequestRestart(state.RestartSystemNow) 1537 } 1538 // we do nothing (nop) if the mode and system are the same 1539 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1540 } 1541 1542 // switchToSystemAndMode switches to given systemLabel and mode. 1543 // If the systemLabel and mode are the same as current, it calls 1544 // sameSystemAndMode. If successful otherwise it calls switched. Both 1545 // are called with the state lock held. 1546 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1547 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1548 return err 1549 } 1550 1551 systemMode := m.SystemMode(SysAny) 1552 // ignore the error to be robust in scenarios that 1553 // dont' stricly require currentSys to be carried through. 1554 // make sure that currentSys == nil does not break 1555 // the code below! 1556 // TODO: should we log the error? 1557 currentSys, _ := currentSystemForMode(m.state, systemMode) 1558 1559 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1560 if _, err := os.Stat(systemSeedDir); err != nil { 1561 // XXX: should we wrap this instead return a naked stat error? 1562 return err 1563 } 1564 system, err := systemFromSeed(systemLabel, currentSys) 1565 if err != nil { 1566 return fmt.Errorf("cannot load seed system: %v", err) 1567 } 1568 1569 var sysAction *SystemAction 1570 for _, act := range system.Actions { 1571 if mode == act.Mode { 1572 sysAction = &act 1573 break 1574 } 1575 } 1576 if sysAction == nil { 1577 // XXX: provide more context here like what mode was requested? 1578 return ErrUnsupportedAction 1579 } 1580 1581 // XXX: requested mode is valid; only current system has 'run' and 1582 // recover 'actions' 1583 1584 switch systemMode { 1585 case "recover", "run": 1586 // if going from recover to recover or from run to run and the systems 1587 // are the same do nothing 1588 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1589 m.state.Lock() 1590 defer m.state.Unlock() 1591 sameSystemAndMode() 1592 return nil 1593 } 1594 case "install": 1595 // requesting system actions in install mode does not make sense atm 1596 // 1597 // TODO:UC20: maybe factory hooks will be able to something like 1598 // this? 1599 return ErrUnsupportedAction 1600 default: 1601 // probably test device manager mocking problem, or also potentially 1602 // missing modeenv 1603 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1604 } 1605 1606 m.state.Lock() 1607 defer m.state.Unlock() 1608 1609 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1610 if err != nil { 1611 return err 1612 } 1613 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1614 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1615 } 1616 1617 switched(systemLabel, sysAction) 1618 return nil 1619 } 1620 1621 // implement storecontext.Backend 1622 1623 type storeContextBackend struct { 1624 *DeviceManager 1625 } 1626 1627 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1628 return scb.DeviceManager.device() 1629 } 1630 1631 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1632 return scb.DeviceManager.setDevice(device) 1633 } 1634 1635 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1636 st := scb.DeviceManager.state 1637 return proxyStore(st, config.NewTransaction(st)) 1638 } 1639 1640 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1641 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1642 if serial == nil { 1643 // shouldn't happen, but be safe 1644 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1645 } 1646 1647 privKey, err := scb.DeviceManager.keyPair() 1648 if err == state.ErrNoState { 1649 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1650 } 1651 if err != nil { 1652 return nil, err 1653 } 1654 1655 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1656 "brand-id": serial.BrandID(), 1657 "model": serial.Model(), 1658 "serial": serial.Serial(), 1659 "nonce": nonce, 1660 "timestamp": time.Now().UTC().Format(time.RFC3339), 1661 }, nil, privKey) 1662 if err != nil { 1663 return nil, err 1664 } 1665 1666 return a.(*asserts.DeviceSessionRequest), nil 1667 } 1668 1669 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1670 return storeContextBackend{m} 1671 } 1672 1673 func (m *DeviceManager) hasFDESetupHook() (bool, error) { 1674 // state must be locked 1675 st := m.state 1676 1677 deviceCtx, err := DeviceCtx(st, nil, nil) 1678 if err != nil { 1679 return false, fmt.Errorf("cannot get device context: %v", err) 1680 } 1681 1682 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1683 if err != nil { 1684 return false, fmt.Errorf("cannot get kernel info: %v", err) 1685 } 1686 return hasFDESetupHookInKernel(kernelInfo), nil 1687 } 1688 1689 func (m *DeviceManager) runFDESetupHook(req *fde.SetupRequest) ([]byte, error) { 1690 // TODO:UC20: when this runs on refresh we need to be very careful 1691 // that we never run this when the kernel is not fully configured 1692 // i.e. when there are no security profiles for the hook 1693 1694 // state must be locked 1695 st := m.state 1696 1697 deviceCtx, err := DeviceCtx(st, nil, nil) 1698 if err != nil { 1699 return nil, fmt.Errorf("cannot get device context to run fde-setup hook: %v", err) 1700 } 1701 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1702 if err != nil { 1703 return nil, fmt.Errorf("cannot get kernel info to run fde-setup hook: %v", err) 1704 } 1705 hooksup := &hookstate.HookSetup{ 1706 Snap: kernelInfo.InstanceName(), 1707 Revision: kernelInfo.Revision, 1708 Hook: "fde-setup", 1709 // XXX: should this be configurable somehow? 1710 Timeout: 5 * time.Minute, 1711 } 1712 contextData := map[string]interface{}{ 1713 "fde-setup-request": req, 1714 } 1715 st.Unlock() 1716 defer st.Lock() 1717 context, err := m.hookMgr.EphemeralRunHook(context.Background(), hooksup, contextData) 1718 if err != nil { 1719 return nil, fmt.Errorf("cannot run hook for %q: %v", req.Op, err) 1720 } 1721 // the hook is expected to call "snapctl fde-setup-result" which 1722 // will set the "fde-setup-result" value on the task 1723 var hookOutput []byte 1724 context.Lock() 1725 err = context.Get("fde-setup-result", &hookOutput) 1726 context.Unlock() 1727 if err != nil { 1728 return nil, fmt.Errorf("cannot get result from fde-setup hook %q: %v", req.Op, err) 1729 } 1730 return hookOutput, nil 1731 } 1732 1733 func (m *DeviceManager) checkFDEFeatures(st *state.State) error { 1734 // TODO: move most of this to kernel/fde.Features 1735 // Run fde-setup hook with "op":"features". If the hook 1736 // returns any {"features":[...]} reply we consider the 1737 // hardware supported. If the hook errors or if it returns 1738 // {"error":"hardware-unsupported"} we don't. 1739 req := &fde.SetupRequest{ 1740 Op: "features", 1741 } 1742 output, err := m.runFDESetupHook(req) 1743 if err != nil { 1744 return err 1745 } 1746 var res struct { 1747 Features []string `json:"features"` 1748 Error string `json:"error"` 1749 } 1750 if err := json.Unmarshal(output, &res); err != nil { 1751 return fmt.Errorf("cannot parse hook output %q: %v", output, err) 1752 } 1753 if res.Features == nil && res.Error == "" { 1754 return fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`) 1755 } 1756 if res.Error != "" { 1757 return fmt.Errorf("cannot use hook: it returned error: %v", res.Error) 1758 } 1759 return nil 1760 } 1761 1762 func hasFDESetupHookInKernel(kernelInfo *snap.Info) bool { 1763 _, ok := kernelInfo.Hooks["fde-setup"] 1764 return ok 1765 } 1766 1767 type fdeSetupHandler struct { 1768 context *hookstate.Context 1769 } 1770 1771 func newFdeSetupHandler(ctx *hookstate.Context) hookstate.Handler { 1772 return fdeSetupHandler{context: ctx} 1773 } 1774 1775 func (h fdeSetupHandler) Before() error { 1776 return nil 1777 } 1778 1779 func (h fdeSetupHandler) Done() error { 1780 return nil 1781 } 1782 1783 func (h fdeSetupHandler) Error(err error) (bool, error) { 1784 return false, nil 1785 }