github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/overlord/devicestate/devicemgr.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 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 "errors" 24 "fmt" 25 "os" 26 "path/filepath" 27 "regexp" 28 "strings" 29 "time" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/asserts/sysdb" 33 "github.com/snapcore/snapd/boot" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/i18n" 36 "github.com/snapcore/snapd/logger" 37 "github.com/snapcore/snapd/overlord/assertstate" 38 "github.com/snapcore/snapd/overlord/auth" 39 "github.com/snapcore/snapd/overlord/configstate/config" 40 "github.com/snapcore/snapd/overlord/devicestate/internal" 41 "github.com/snapcore/snapd/overlord/hookstate" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/state" 44 "github.com/snapcore/snapd/overlord/storecontext" 45 "github.com/snapcore/snapd/release" 46 "github.com/snapcore/snapd/snapdenv" 47 "github.com/snapcore/snapd/sysconfig" 48 "github.com/snapcore/snapd/timings" 49 ) 50 51 var ( 52 cloudInitStatus = sysconfig.CloudInitStatus 53 restrictCloudInit = sysconfig.RestrictCloudInit 54 ) 55 56 // DeviceManager is responsible for managing the device identity and device 57 // policies. 58 type DeviceManager struct { 59 systemMode string 60 61 state *state.State 62 keypairMgr asserts.KeypairManager 63 64 // newStore can make new stores for remodeling 65 newStore func(storecontext.DeviceBackend) snapstate.StoreService 66 67 bootOkRan bool 68 bootRevisionsUpdated bool 69 70 ensureSeedInConfigRan bool 71 72 ensureInstalledRan bool 73 74 cloudInitAlreadyRestricted bool 75 cloudInitErrorAttemptStart *time.Time 76 cloudInitEnabledInactiveAttemptStart *time.Time 77 78 lastBecomeOperationalAttempt time.Time 79 becomeOperationalBackoff time.Duration 80 registered bool 81 reg chan struct{} 82 83 preseed bool 84 } 85 86 // Manager returns a new device manager. 87 func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, newStore func(storecontext.DeviceBackend) snapstate.StoreService) (*DeviceManager, error) { 88 delayedCrossMgrInit() 89 90 keypairMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir) 91 if err != nil { 92 return nil, err 93 } 94 95 m := &DeviceManager{ 96 state: s, 97 keypairMgr: keypairMgr, 98 newStore: newStore, 99 reg: make(chan struct{}), 100 preseed: snapdenv.Preseeding(), 101 } 102 103 modeEnv, err := maybeReadModeenv() 104 if err != nil { 105 return nil, err 106 } 107 if modeEnv != nil { 108 m.systemMode = modeEnv.Mode 109 } 110 111 s.Lock() 112 s.Cache(deviceMgrKey{}, m) 113 s.Unlock() 114 115 if err := m.confirmRegistered(); err != nil { 116 return nil, err 117 } 118 119 hookManager.Register(regexp.MustCompile("^prepare-device$"), newPrepareDeviceHandler) 120 121 runner.AddHandler("generate-device-key", m.doGenerateDeviceKey, nil) 122 runner.AddHandler("request-serial", m.doRequestSerial, nil) 123 runner.AddHandler("mark-preseeded", m.doMarkPreseeded, nil) 124 runner.AddHandler("mark-seeded", m.doMarkSeeded, nil) 125 runner.AddHandler("setup-run-system", m.doSetupRunSystem, nil) 126 runner.AddHandler("prepare-remodeling", m.doPrepareRemodeling, nil) 127 runner.AddCleanup("prepare-remodeling", m.cleanupRemodel) 128 // this *must* always run last and finalizes a remodel 129 runner.AddHandler("set-model", m.doSetModel, nil) 130 runner.AddCleanup("set-model", m.cleanupRemodel) 131 // There is no undo for successful gadget updates. The system is 132 // rebooted during update, if it boots up to the point where snapd runs 133 // we deem the new assets (be it bootloader or firmware) functional. The 134 // deployed boot assets must be backward compatible with reverted kernel 135 // or gadget snaps. There are no further changes to the boot assets, 136 // unless a new gadget update is deployed. 137 runner.AddHandler("update-gadget-assets", m.doUpdateGadgetAssets, nil) 138 139 runner.AddBlocked(gadgetUpdateBlocked) 140 141 return m, nil 142 } 143 144 func maybeReadModeenv() (*boot.Modeenv, error) { 145 modeEnv, err := boot.ReadModeenv("") 146 if err != nil && !os.IsNotExist(err) { 147 return nil, fmt.Errorf("cannot read modeenv: %v", err) 148 } 149 return modeEnv, nil 150 } 151 152 type deviceMgrKey struct{} 153 154 func deviceMgr(st *state.State) *DeviceManager { 155 mgr := st.Cached(deviceMgrKey{}) 156 if mgr == nil { 157 panic("internal error: device manager is not yet associated with state") 158 } 159 return mgr.(*DeviceManager) 160 } 161 162 func (m *DeviceManager) CanStandby() bool { 163 var seeded bool 164 if err := m.state.Get("seeded", &seeded); err != nil { 165 return false 166 } 167 return seeded 168 } 169 170 func (m *DeviceManager) confirmRegistered() error { 171 m.state.Lock() 172 defer m.state.Unlock() 173 174 device, err := m.device() 175 if err != nil { 176 return err 177 } 178 179 if device.Serial != "" { 180 m.markRegistered() 181 } 182 return nil 183 } 184 185 func (m *DeviceManager) markRegistered() { 186 if m.registered { 187 return 188 } 189 m.registered = true 190 close(m.reg) 191 } 192 193 func gadgetUpdateBlocked(cand *state.Task, running []*state.Task) bool { 194 if cand.Kind() == "update-gadget-assets" && len(running) != 0 { 195 // update-gadget-assets must be the only task running 196 return true 197 } else { 198 for _, other := range running { 199 if other.Kind() == "update-gadget-assets" { 200 // no other task can be started when 201 // update-gadget-assets is running 202 return true 203 } 204 } 205 } 206 207 return false 208 } 209 210 type prepareDeviceHandler struct{} 211 212 func newPrepareDeviceHandler(context *hookstate.Context) hookstate.Handler { 213 return prepareDeviceHandler{} 214 } 215 216 func (h prepareDeviceHandler) Before() error { 217 return nil 218 } 219 220 func (h prepareDeviceHandler) Done() error { 221 return nil 222 } 223 224 func (h prepareDeviceHandler) Error(err error) error { 225 return nil 226 } 227 228 func (m *DeviceManager) changeInFlight(kind string) bool { 229 for _, chg := range m.state.Changes() { 230 if chg.Kind() == kind && !chg.Status().Ready() { 231 // change already in motion 232 return true 233 } 234 } 235 return false 236 } 237 238 // helpers to keep count of attempts to get a serial, useful to decide 239 // to give up holding off trying to auto-refresh 240 241 type ensureOperationalAttemptsKey struct{} 242 243 func incEnsureOperationalAttempts(st *state.State) { 244 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 245 st.Cache(ensureOperationalAttemptsKey{}, cur+1) 246 } 247 248 func ensureOperationalAttempts(st *state.State) int { 249 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 250 return cur 251 } 252 253 // ensureOperationalShouldBackoff returns whether we should abstain from 254 // further become-operational tentatives while its backoff interval is 255 // not expired. 256 func (m *DeviceManager) ensureOperationalShouldBackoff(now time.Time) bool { 257 if !m.lastBecomeOperationalAttempt.IsZero() && m.lastBecomeOperationalAttempt.Add(m.becomeOperationalBackoff).After(now) { 258 return true 259 } 260 if m.becomeOperationalBackoff == 0 { 261 m.becomeOperationalBackoff = 5 * time.Minute 262 } else { 263 newBackoff := m.becomeOperationalBackoff * 2 264 if newBackoff > (12 * time.Hour) { 265 newBackoff = 24 * time.Hour 266 } 267 m.becomeOperationalBackoff = newBackoff 268 } 269 m.lastBecomeOperationalAttempt = now 270 return false 271 } 272 273 func setClassicFallbackModel(st *state.State, device *auth.DeviceState) error { 274 err := assertstate.Add(st, sysdb.GenericClassicModel()) 275 if err != nil && !asserts.IsUnaccceptedUpdate(err) { 276 return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err) 277 } 278 device.Brand = "generic" 279 device.Model = "generic-classic" 280 if err := internal.SetDevice(st, device); err != nil { 281 return err 282 } 283 return nil 284 } 285 286 func (m *DeviceManager) SystemMode() string { 287 if m.systemMode == "" { 288 return "run" 289 } 290 return m.systemMode 291 } 292 293 func (m *DeviceManager) ensureOperational() error { 294 m.state.Lock() 295 defer m.state.Unlock() 296 297 if m.SystemMode() != "run" { 298 // avoid doing registration in ephemeral mode 299 // note: this also stop auto-refreshes indirectly 300 return nil 301 } 302 303 device, err := m.device() 304 if err != nil { 305 return err 306 } 307 308 if device.Serial != "" { 309 // serial is set, we are all set 310 return nil 311 } 312 313 perfTimings := timings.New(map[string]string{"ensure": "become-operational"}) 314 315 // conditions to trigger device registration 316 // 317 // * have a model assertion with a gadget (core and 318 // device-like classic) in which case we need also to wait 319 // for the gadget to have been installed though 320 // TODO: consider a way to support lazy registration on classic 321 // even with a gadget and some preseeded snaps 322 // 323 // * classic with a model assertion with a non-default store specified 324 // * lazy classic case (might have a model with no gadget nor store 325 // or no model): we wait to have some snaps installed or be 326 // in the process to install some 327 328 var seeded bool 329 err = m.state.Get("seeded", &seeded) 330 if err != nil && err != state.ErrNoState { 331 return err 332 } 333 334 if device.Brand == "" || device.Model == "" { 335 if !release.OnClassic || !seeded { 336 return nil 337 } 338 // we are on classic and seeded but there is no model: 339 // use a fallback model! 340 err := setClassicFallbackModel(m.state, device) 341 if err != nil { 342 return err 343 } 344 } 345 346 if m.changeInFlight("become-operational") { 347 return nil 348 } 349 350 var storeID, gadget string 351 model, err := m.Model() 352 if err != nil && err != state.ErrNoState { 353 return err 354 } 355 if err == nil { 356 gadget = model.Gadget() 357 storeID = model.Store() 358 } else { 359 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 360 } 361 362 if gadget == "" && storeID == "" { 363 // classic: if we have no gadget and no non-default store 364 // wait to have snaps or snap installation 365 366 n, err := snapstate.NumSnaps(m.state) 367 if err != nil { 368 return err 369 } 370 if n == 0 && !snapstate.Installing(m.state) { 371 return nil 372 } 373 } 374 375 var hasPrepareDeviceHook bool 376 // if there's a gadget specified wait for it 377 if gadget != "" { 378 // if have a gadget wait until seeded to proceed 379 if !seeded { 380 // this will be run again, so eventually when the system is 381 // seeded the code below runs 382 return nil 383 384 } 385 386 gadgetInfo, err := snapstate.CurrentInfo(m.state, gadget) 387 if err != nil { 388 return err 389 } 390 hasPrepareDeviceHook = (gadgetInfo.Hooks["prepare-device"] != nil) 391 } 392 393 // have some backoff between full retries 394 if m.ensureOperationalShouldBackoff(time.Now()) { 395 return nil 396 } 397 // increment attempt count 398 incEnsureOperationalAttempts(m.state) 399 400 // XXX: some of these will need to be split and use hooks 401 // retries might need to embrace more than one "task" then, 402 // need to be careful 403 404 tasks := []*state.Task{} 405 406 var prepareDevice *state.Task 407 if hasPrepareDeviceHook { 408 summary := i18n.G("Run prepare-device hook") 409 hooksup := &hookstate.HookSetup{ 410 Snap: gadget, 411 Hook: "prepare-device", 412 } 413 prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil) 414 tasks = append(tasks, prepareDevice) 415 // hooks are under a different manager, make sure we consider 416 // it immediately 417 m.state.EnsureBefore(0) 418 } 419 420 genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key")) 421 if prepareDevice != nil { 422 genKey.WaitFor(prepareDevice) 423 } 424 tasks = append(tasks, genKey) 425 requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial")) 426 requestSerial.WaitFor(genKey) 427 tasks = append(tasks, requestSerial) 428 429 chg := m.state.NewChange("become-operational", i18n.G("Initialize device")) 430 chg.AddAll(state.NewTaskSet(tasks...)) 431 432 state.TagTimingsWithChange(perfTimings, chg) 433 perfTimings.Save(m.state) 434 435 return nil 436 } 437 438 var startTime time.Time 439 440 func init() { 441 startTime = time.Now() 442 } 443 444 func (m *DeviceManager) setTimeOnce(name string, t time.Time) error { 445 var prev time.Time 446 err := m.state.Get(name, &prev) 447 if err != nil && err != state.ErrNoState { 448 return err 449 } 450 if !prev.IsZero() { 451 // already set 452 return nil 453 } 454 m.state.Set(name, t) 455 return nil 456 } 457 458 var populateStateFromSeed = populateStateFromSeedImpl 459 460 // ensureSeeded makes sure that the snaps from seed.yaml get installed 461 // with the matching assertions 462 func (m *DeviceManager) ensureSeeded() error { 463 m.state.Lock() 464 defer m.state.Unlock() 465 466 var seeded bool 467 err := m.state.Get("seeded", &seeded) 468 if err != nil && err != state.ErrNoState { 469 return err 470 } 471 if seeded { 472 return nil 473 } 474 475 perfTimings := timings.New(map[string]string{"ensure": "seed"}) 476 477 if m.changeInFlight("seed") { 478 return nil 479 } 480 481 var recordedStart string 482 var start time.Time 483 if m.preseed { 484 recordedStart = "preseed-start-time" 485 start = timeNow() 486 } else { 487 recordedStart = "seed-start-time" 488 start = startTime 489 } 490 if err := m.setTimeOnce(recordedStart, start); err != nil { 491 return err 492 } 493 494 var opts *populateStateFromSeedOptions 495 if m.preseed { 496 opts = &populateStateFromSeedOptions{Preseed: true} 497 } else { 498 modeEnv, err := maybeReadModeenv() 499 if err != nil { 500 return err 501 } 502 if modeEnv != nil { 503 opts = &populateStateFromSeedOptions{ 504 Mode: m.systemMode, 505 Label: modeEnv.RecoverySystem, 506 } 507 } 508 } 509 510 var tsAll []*state.TaskSet 511 timings.Run(perfTimings, "state-from-seed", "populate state from seed", func(tm timings.Measurer) { 512 tsAll, err = populateStateFromSeed(m.state, opts, tm) 513 }) 514 if err != nil { 515 return err 516 } 517 if len(tsAll) == 0 { 518 return nil 519 } 520 521 chg := m.state.NewChange("seed", "Initialize system state") 522 for _, ts := range tsAll { 523 chg.AddAll(ts) 524 } 525 m.state.EnsureBefore(0) 526 527 state.TagTimingsWithChange(perfTimings, chg) 528 perfTimings.Save(m.state) 529 return nil 530 } 531 532 // ResetBootOk is only useful for integration testing 533 func (m *DeviceManager) ResetBootOk() { 534 m.bootOkRan = false 535 m.bootRevisionsUpdated = false 536 } 537 538 func (m *DeviceManager) ensureBootOk() error { 539 m.state.Lock() 540 defer m.state.Unlock() 541 542 if release.OnClassic { 543 return nil 544 } 545 546 // boot-ok/update-boot-revision is only relevant in run-mode 547 if m.SystemMode() != "run" { 548 return nil 549 } 550 551 if !m.bootOkRan { 552 deviceCtx, err := DeviceCtx(m.state, nil, nil) 553 if err != nil && err != state.ErrNoState { 554 return err 555 } 556 if err == nil { 557 if err := boot.MarkBootSuccessful(deviceCtx); err != nil { 558 return err 559 } 560 } 561 m.bootOkRan = true 562 } 563 564 if !m.bootRevisionsUpdated { 565 if err := snapstate.UpdateBootRevisions(m.state); err != nil { 566 return err 567 } 568 m.bootRevisionsUpdated = true 569 } 570 571 return nil 572 } 573 574 func (m *DeviceManager) ensureCloudInitRestricted() error { 575 m.state.Lock() 576 defer m.state.Unlock() 577 578 if m.cloudInitAlreadyRestricted { 579 return nil 580 } 581 582 var seeded bool 583 err := m.state.Get("seeded", &seeded) 584 if err != nil && err != state.ErrNoState { 585 return err 586 } 587 588 // On Ubuntu Core devices that have been seeded, we want to restrict 589 // cloud-init so that its more dangerous (for an IoT device at least) 590 // features are not exploitable after a device has been seeded. This allows 591 // device administrators and other tools (such as multipass) to still 592 // configure an Ubuntu Core device on first boot, and also allows cloud 593 // vendors to run cloud-init with only a specific data-source on subsequent 594 // boots but disallows arbitrary cloud-init {user,meta,vendor}-data to be 595 // attached to a device via a USB drive and inject code onto the device. 596 597 if seeded && !release.OnClassic { 598 opts := &sysconfig.CloudInitRestrictOptions{} 599 600 // check the current state of cloud-init, if it is disabled or already 601 // restricted then we have nothing to do 602 cloudInitStatus, err := cloudInitStatus() 603 if err != nil { 604 return err 605 } 606 statusMsg := "" 607 608 switch cloudInitStatus { 609 case sysconfig.CloudInitDisabledPermanently, sysconfig.CloudInitRestrictedBySnapd: 610 // already been permanently disabled, nothing to do 611 m.cloudInitAlreadyRestricted = true 612 return nil 613 case sysconfig.CloudInitUntriggered: 614 // hasn't been used 615 statusMsg = "reported to be in disabled state" 616 case sysconfig.CloudInitDone: 617 // is done being used 618 statusMsg = "reported to be done" 619 case sysconfig.CloudInitErrored: 620 // cloud-init errored, so we give the device admin / developer a few 621 // minutes to reboot the machine to re-run cloud-init and try again, 622 // otherwise we will disable cloud-init permanently 623 624 // initialize the time we first saw cloud-init in error state 625 if m.cloudInitErrorAttemptStart == nil { 626 // save the time we started the attempt to restrict 627 now := timeNow() 628 m.cloudInitErrorAttemptStart = &now 629 logger.Noticef("System initialized, cloud-init reported to be in error state, will disable in 3 minutes") 630 } 631 632 // check if 3 minutes have elapsed since we first saw cloud-init in 633 // error state 634 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitErrorAttemptStart) 635 if timeSinceFirstAttempt <= 3*time.Minute { 636 // we need to keep waiting for cloud-init, up to 3 minutes 637 nextCheck := 3*time.Minute - timeSinceFirstAttempt 638 m.state.EnsureBefore(nextCheck) 639 return nil 640 } 641 // otherwise, we timed out waiting for cloud-init to be fixed or 642 // rebooted and should restrict cloud-init 643 // we will restrict cloud-init below, but we need to force the 644 // disable, as by default RestrictCloudInit will error on state 645 // CloudInitErrored 646 opts.ForceDisable = true 647 statusMsg = "reported to be in error state after 3 minutes" 648 default: 649 // in unknown states we are conservative and let the device run for 650 // a while to see if it transitions to a known state, but eventually 651 // will disable anyways 652 fallthrough 653 case sysconfig.CloudInitEnabled: 654 // we will give cloud-init up to 5 minutes to try and run, if it 655 // still has not transitioned to some other known state, then we 656 // will give up waiting for it and disable it anyways 657 658 // initialize the first time we saw cloud-init in enabled state 659 if m.cloudInitEnabledInactiveAttemptStart == nil { 660 // save the time we started the attempt to restrict 661 now := timeNow() 662 m.cloudInitEnabledInactiveAttemptStart = &now 663 } 664 665 // keep re-scheduling again in 10 seconds until we hit 5 minutes 666 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitEnabledInactiveAttemptStart) 667 if timeSinceFirstAttempt <= 5*time.Minute { 668 // TODO: should we log a message here about waiting for cloud-init 669 // to be in a "known state"? 670 m.state.EnsureBefore(10 * time.Second) 671 return nil 672 } 673 674 // otherwise, we gave cloud-init 5 minutes to run, if it's still not 675 // done disable it anyways 676 // note we we need to force the disable, as by default 677 // RestrictCloudInit will error on state CloudInitEnabled 678 opts.ForceDisable = true 679 statusMsg = "failed to transition to done or error state after 5 minutes" 680 } 681 682 // we should always have a model if we are seeded and are not on classic 683 model, err := m.Model() 684 if err != nil { 685 return err 686 } 687 688 // For UC20, we want to always disable cloud-init after it has run on 689 // first boot unless we are in a "real cloud", i.e. not using NoCloud, 690 // or if we installed cloud-init configuration from the gadget 691 if model.Grade() != asserts.ModelGradeUnset { 692 // always disable NoCloud/local datasources after first boot on 693 // uc20, this is because even if the gadget has a cloud.conf 694 // configuring NoCloud, the config installed by cloud-init should 695 // not work differently for later boots, so it's sufficient that 696 // NoCloud runs on first-boot and never again 697 opts.DisableAfterLocalDatasourcesRun = true 698 } 699 700 // now restrict/disable cloud-init 701 res, err := restrictCloudInit(cloudInitStatus, opts) 702 if err != nil { 703 return err 704 } 705 706 // log a message about what we did 707 actionMsg := "" 708 switch res.Action { 709 case "disable": 710 actionMsg = "disabled permanently" 711 case "restrict": 712 // log different messages depending on what datasource was used 713 if res.DataSource == "NoCloud" { 714 actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label" 715 } else { 716 // all other datasources just log that we limited it to that datasource 717 actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource) 718 } 719 default: 720 return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action) 721 } 722 logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg) 723 724 m.cloudInitAlreadyRestricted = true 725 } 726 727 return nil 728 } 729 730 func (m *DeviceManager) ensureInstalled() error { 731 m.state.Lock() 732 defer m.state.Unlock() 733 734 if release.OnClassic { 735 return nil 736 } 737 738 if m.ensureInstalledRan { 739 return nil 740 } 741 742 if m.SystemMode() != "install" { 743 return nil 744 } 745 746 var seeded bool 747 err := m.state.Get("seeded", &seeded) 748 if err != nil && err != state.ErrNoState { 749 return err 750 } 751 if !seeded { 752 return nil 753 } 754 755 if m.changeInFlight("install-system") { 756 return nil 757 } 758 759 m.ensureInstalledRan = true 760 761 tasks := []*state.Task{} 762 setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode")) 763 tasks = append(tasks, setupRunSystem) 764 765 chg := m.state.NewChange("install-system", i18n.G("Install the system")) 766 chg.AddAll(state.NewTaskSet(tasks...)) 767 768 return nil 769 } 770 771 var timeNow = time.Now 772 773 // StartOfOperationTime returns the time when snapd started operating, 774 // and sets it in the state when called for the first time. 775 // The StartOfOperationTime time is seed-time if available, 776 // or current time otherwise. 777 func (m *DeviceManager) StartOfOperationTime() (time.Time, error) { 778 var opTime time.Time 779 if m.preseed { 780 return opTime, fmt.Errorf("internal error: unexpected call to StartOfOperationTime in preseed mode") 781 } 782 err := m.state.Get("start-of-operation-time", &opTime) 783 if err == nil { 784 return opTime, nil 785 } 786 if err != nil && err != state.ErrNoState { 787 return opTime, err 788 } 789 790 // start-of-operation-time not set yet, use seed-time if available 791 var seedTime time.Time 792 err = m.state.Get("seed-time", &seedTime) 793 if err != nil && err != state.ErrNoState { 794 return opTime, err 795 } 796 if err == nil { 797 opTime = seedTime 798 } else { 799 opTime = timeNow() 800 } 801 m.state.Set("start-of-operation-time", opTime) 802 return opTime, nil 803 } 804 805 func markSeededInConfig(st *state.State) error { 806 var seedDone bool 807 tr := config.NewTransaction(st) 808 if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) { 809 return err 810 } 811 if !seedDone { 812 if err := tr.Set("core", "seed.loaded", true); err != nil { 813 return err 814 } 815 tr.Commit() 816 } 817 return nil 818 } 819 820 func (m *DeviceManager) ensureSeedInConfig() error { 821 m.state.Lock() 822 defer m.state.Unlock() 823 824 if !m.ensureSeedInConfigRan { 825 // get global seeded option 826 var seeded bool 827 if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState { 828 return err 829 } 830 if !seeded { 831 // wait for ensure again, this is fine because 832 // doMarkSeeded will run "EnsureBefore(0)" 833 return nil 834 } 835 836 // Sync seeding with the configuration state. We need to 837 // do this here to ensure that old systems which did not 838 // set the configuration on seeding get the configuration 839 // update too. 840 if err := markSeededInConfig(m.state); err != nil { 841 return err 842 } 843 m.ensureSeedInConfigRan = true 844 } 845 846 return nil 847 848 } 849 850 type ensureError struct { 851 errs []error 852 } 853 854 func (e *ensureError) Error() string { 855 if len(e.errs) == 1 { 856 return fmt.Sprintf("devicemgr: %v", e.errs[0]) 857 } 858 parts := []string{"devicemgr:"} 859 for _, e := range e.errs { 860 parts = append(parts, e.Error()) 861 } 862 return strings.Join(parts, "\n - ") 863 } 864 865 // no \n allowed in warnings 866 var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.` 867 868 // Ensure implements StateManager.Ensure. 869 func (m *DeviceManager) Ensure() error { 870 var errs []error 871 872 if err := m.ensureSeeded(); err != nil { 873 m.state.Lock() 874 m.state.Warnf(seedFailureFmt, err) 875 m.state.Unlock() 876 errs = append(errs, fmt.Errorf("cannot seed: %v", err)) 877 } 878 879 if !m.preseed { 880 if err := m.ensureCloudInitRestricted(); err != nil { 881 errs = append(errs, err) 882 } 883 884 if err := m.ensureOperational(); err != nil { 885 errs = append(errs, err) 886 } 887 888 if err := m.ensureBootOk(); err != nil { 889 errs = append(errs, err) 890 } 891 892 if err := m.ensureSeedInConfig(); err != nil { 893 errs = append(errs, err) 894 } 895 896 if err := m.ensureInstalled(); err != nil { 897 errs = append(errs, err) 898 } 899 } 900 901 if len(errs) > 0 { 902 return &ensureError{errs} 903 } 904 905 return nil 906 } 907 908 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 909 device, err := m.device() 910 if err != nil { 911 return nil, err 912 } 913 914 if device.KeyID == "" { 915 return nil, state.ErrNoState 916 } 917 918 privKey, err := m.keypairMgr.Get(device.KeyID) 919 if err != nil { 920 return nil, fmt.Errorf("cannot read device key pair: %v", err) 921 } 922 return privKey, nil 923 } 924 925 // Registered returns a channel that is closed when the device is known to have been registered. 926 func (m *DeviceManager) Registered() <-chan struct{} { 927 return m.reg 928 } 929 930 // device returns current device state. 931 func (m *DeviceManager) device() (*auth.DeviceState, error) { 932 return internal.Device(m.state) 933 } 934 935 // setDevice sets the device details in the state. 936 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 937 return internal.SetDevice(m.state, device) 938 } 939 940 // Model returns the device model assertion. 941 func (m *DeviceManager) Model() (*asserts.Model, error) { 942 return findModel(m.state) 943 } 944 945 // Serial returns the device serial assertion. 946 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 947 return findSerial(m.state, nil) 948 } 949 950 type SystemAction struct { 951 Title string 952 Mode string 953 } 954 955 type System struct { 956 // Current is true when the system running now was installed from that 957 // seed 958 Current bool 959 // Label of the seed system 960 Label string 961 // Model assertion of the system 962 Model *asserts.Model 963 // Brand information 964 Brand *asserts.Account 965 // Actions available for this system 966 Actions []SystemAction 967 } 968 969 var defaultSystemActions = []SystemAction{ 970 {Title: "Install", Mode: "install"}, 971 } 972 var currentSystemActions = []SystemAction{ 973 {Title: "Reinstall", Mode: "install"}, 974 {Title: "Recover", Mode: "recover"}, 975 {Title: "Run normally", Mode: "run"}, 976 } 977 var recoverSystemActions = []SystemAction{ 978 {Title: "Reinstall", Mode: "install"}, 979 {Title: "Run normally", Mode: "run"}, 980 } 981 982 var ErrNoSystems = errors.New("no systems seeds") 983 984 // Systems list the available recovery/seeding systems. Returns the list of 985 // systems, ErrNoSystems when no systems seeds were found or other error. 986 func (m *DeviceManager) Systems() ([]*System, error) { 987 // it's tough luck when we cannot determine the current system seed 988 systemMode := m.SystemMode() 989 currentSys, _ := currentSystemForMode(m.state, systemMode) 990 991 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 992 if err != nil && !os.IsNotExist(err) { 993 return nil, fmt.Errorf("cannot list available systems: %v", err) 994 } 995 if len(systemLabels) == 0 { 996 // maybe not a UC20 system 997 return nil, ErrNoSystems 998 } 999 1000 var systems []*System 1001 for _, fpLabel := range systemLabels { 1002 label := filepath.Base(fpLabel) 1003 system, err := systemFromSeed(label, currentSys) 1004 if err != nil { 1005 // TODO:UC20 add a Broken field to the seed system like 1006 // we do for snap.Info 1007 logger.Noticef("cannot load system %q seed: %v", label, err) 1008 continue 1009 } 1010 systems = append(systems, system) 1011 } 1012 return systems, nil 1013 } 1014 1015 var ErrUnsupportedAction = errors.New("unsupported action") 1016 1017 // Reboot triggers a reboot into the given systemLabel and mode. 1018 // 1019 // When called without a systemLabel and without a mode it will just 1020 // trigger a regular reboot. 1021 // 1022 // When called without a systemLabel but with a mode it will use 1023 // the current system to enter the given mode. 1024 // 1025 // Note that "recover" and "run" modes are only available for the 1026 // current system. 1027 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1028 rebootCurrent := func() { 1029 logger.Noticef("rebooting system") 1030 m.state.RequestRestart(state.RestartSystemNow) 1031 } 1032 1033 // most simple case: just reboot 1034 if systemLabel == "" && mode == "" { 1035 m.state.Lock() 1036 defer m.state.Unlock() 1037 1038 rebootCurrent() 1039 return nil 1040 } 1041 1042 // no systemLabel means "current" so get the current system label 1043 if systemLabel == "" { 1044 systemMode := m.SystemMode() 1045 currentSys, err := currentSystemForMode(m.state, systemMode) 1046 if err != nil { 1047 return fmt.Errorf("cannot get curent system: %v", err) 1048 } 1049 systemLabel = currentSys.System 1050 } 1051 1052 switched := func(systemLabel string, sysAction *SystemAction) { 1053 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1054 m.state.RequestRestart(state.RestartSystemNow) 1055 } 1056 // even if we are already in the right mode we restart here by 1057 // passing rebootCurrent as this is what the user requested 1058 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1059 } 1060 1061 // RequestSystemAction requests the provided system to be run in a 1062 // given mode as specified by action. 1063 // A system reboot will be requested when the request can be 1064 // successfully carried out. 1065 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1066 if systemLabel == "" { 1067 return fmt.Errorf("internal error: system label is unset") 1068 } 1069 1070 nop := func() {} 1071 switched := func(systemLabel string, sysAction *SystemAction) { 1072 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1073 m.state.RequestRestart(state.RestartSystemNow) 1074 } 1075 // we do nothing (nop) if the mode and system are the same 1076 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1077 } 1078 1079 // switchToSystemAndMode switches to given systemLabel and mode. 1080 // If the systemLabel and mode are the same as current, it calls 1081 // sameSystemAndMode. If successful otherwise it calls switched. Both 1082 // are called with the state lock held. 1083 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1084 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1085 return err 1086 } 1087 1088 systemMode := m.SystemMode() 1089 // ignore the error to be robust in scenarios that 1090 // dont' stricly require currentSys to be carried through. 1091 // make sure that currentSys == nil does not break 1092 // the code below! 1093 // TODO: should we log the error? 1094 currentSys, _ := currentSystemForMode(m.state, systemMode) 1095 1096 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1097 if _, err := os.Stat(systemSeedDir); err != nil { 1098 // XXX: should we wrap this instead return a naked stat error? 1099 return err 1100 } 1101 system, err := systemFromSeed(systemLabel, currentSys) 1102 if err != nil { 1103 return fmt.Errorf("cannot load seed system: %v", err) 1104 } 1105 1106 var sysAction *SystemAction 1107 for _, act := range system.Actions { 1108 if mode == act.Mode { 1109 sysAction = &act 1110 break 1111 } 1112 } 1113 if sysAction == nil { 1114 // XXX: provide more context here like what mode was requested? 1115 return ErrUnsupportedAction 1116 } 1117 1118 // XXX: requested mode is valid; only current system has 'run' and 1119 // recover 'actions' 1120 1121 switch systemMode { 1122 case "recover", "run": 1123 // if going from recover to recover or from run to run and the systems 1124 // are the same do nothing 1125 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1126 m.state.Lock() 1127 defer m.state.Unlock() 1128 sameSystemAndMode() 1129 return nil 1130 } 1131 case "install": 1132 // requesting system actions in install mode does not make sense atm 1133 // 1134 // TODO:UC20: maybe factory hooks will be able to something like 1135 // this? 1136 return ErrUnsupportedAction 1137 default: 1138 // probably test device manager mocking problem, or also potentially 1139 // missing modeenv 1140 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1141 } 1142 1143 m.state.Lock() 1144 defer m.state.Unlock() 1145 1146 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1147 if err != nil { 1148 return err 1149 } 1150 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1151 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1152 } 1153 1154 switched(systemLabel, sysAction) 1155 return nil 1156 } 1157 1158 // implement storecontext.Backend 1159 1160 type storeContextBackend struct { 1161 *DeviceManager 1162 } 1163 1164 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1165 return scb.DeviceManager.device() 1166 } 1167 1168 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1169 return scb.DeviceManager.setDevice(device) 1170 } 1171 1172 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1173 st := scb.DeviceManager.state 1174 return proxyStore(st, config.NewTransaction(st)) 1175 } 1176 1177 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1178 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1179 if serial == nil { 1180 // shouldn't happen, but be safe 1181 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1182 } 1183 1184 privKey, err := scb.DeviceManager.keyPair() 1185 if err == state.ErrNoState { 1186 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1187 } 1188 if err != nil { 1189 return nil, err 1190 } 1191 1192 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1193 "brand-id": serial.BrandID(), 1194 "model": serial.Model(), 1195 "serial": serial.Serial(), 1196 "nonce": nonce, 1197 "timestamp": time.Now().UTC().Format(time.RFC3339), 1198 }, nil, privKey) 1199 if err != nil { 1200 return nil, err 1201 } 1202 1203 return a.(*asserts.DeviceSessionRequest), nil 1204 } 1205 1206 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1207 return storeContextBackend{m} 1208 }