github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 after first boot on uc20, this is because 693 // even if the gadget has a cloud.conf configuring NoCloud, the 694 // config installed by cloud-init should not work differently for 695 // later boots, so it's sufficient that NoCloud runs on first-boot 696 // and never again 697 // note that the name DisableNoCloud is slightly misleading, it's 698 // more specifically "disable cloud-init after first boot if 699 // NoCloud, but just restrict after first boot if not NoCloud" 700 opts.DisableNoCloud = true 701 } 702 703 // now restrict/disable cloud-init 704 res, err := restrictCloudInit(cloudInitStatus, opts) 705 if err != nil { 706 return err 707 } 708 709 // log a message about what we did 710 actionMsg := "" 711 switch res.Action { 712 case "disable": 713 actionMsg = "disabled permanently" 714 case "restrict": 715 // log different messages depending on what datasource was used 716 if res.DataSource == "NoCloud" { 717 actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label" 718 } else { 719 // all other datasources just log that we limited it to that datasource 720 actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource) 721 } 722 default: 723 return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action) 724 } 725 logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg) 726 727 m.cloudInitAlreadyRestricted = true 728 } 729 730 return nil 731 } 732 733 func (m *DeviceManager) ensureInstalled() error { 734 m.state.Lock() 735 defer m.state.Unlock() 736 737 if release.OnClassic { 738 return nil 739 } 740 741 if m.ensureInstalledRan { 742 return nil 743 } 744 745 if m.SystemMode() != "install" { 746 return nil 747 } 748 749 var seeded bool 750 err := m.state.Get("seeded", &seeded) 751 if err != nil && err != state.ErrNoState { 752 return err 753 } 754 if !seeded { 755 return nil 756 } 757 758 if m.changeInFlight("install-system") { 759 return nil 760 } 761 762 m.ensureInstalledRan = true 763 764 tasks := []*state.Task{} 765 setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode")) 766 tasks = append(tasks, setupRunSystem) 767 768 chg := m.state.NewChange("install-system", i18n.G("Install the system")) 769 chg.AddAll(state.NewTaskSet(tasks...)) 770 771 return nil 772 } 773 774 var timeNow = time.Now 775 776 // StartOfOperationTime returns the time when snapd started operating, 777 // and sets it in the state when called for the first time. 778 // The StartOfOperationTime time is seed-time if available, 779 // or current time otherwise. 780 func (m *DeviceManager) StartOfOperationTime() (time.Time, error) { 781 var opTime time.Time 782 if m.preseed { 783 return opTime, fmt.Errorf("internal error: unexpected call to StartOfOperationTime in preseed mode") 784 } 785 err := m.state.Get("start-of-operation-time", &opTime) 786 if err == nil { 787 return opTime, nil 788 } 789 if err != nil && err != state.ErrNoState { 790 return opTime, err 791 } 792 793 // start-of-operation-time not set yet, use seed-time if available 794 var seedTime time.Time 795 err = m.state.Get("seed-time", &seedTime) 796 if err != nil && err != state.ErrNoState { 797 return opTime, err 798 } 799 if err == nil { 800 opTime = seedTime 801 } else { 802 opTime = timeNow() 803 } 804 m.state.Set("start-of-operation-time", opTime) 805 return opTime, nil 806 } 807 808 func markSeededInConfig(st *state.State) error { 809 var seedDone bool 810 tr := config.NewTransaction(st) 811 if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) { 812 return err 813 } 814 if !seedDone { 815 if err := tr.Set("core", "seed.loaded", true); err != nil { 816 return err 817 } 818 tr.Commit() 819 } 820 return nil 821 } 822 823 func (m *DeviceManager) ensureSeedInConfig() error { 824 m.state.Lock() 825 defer m.state.Unlock() 826 827 if !m.ensureSeedInConfigRan { 828 // get global seeded option 829 var seeded bool 830 if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState { 831 return err 832 } 833 if !seeded { 834 // wait for ensure again, this is fine because 835 // doMarkSeeded will run "EnsureBefore(0)" 836 return nil 837 } 838 839 // Sync seeding with the configuration state. We need to 840 // do this here to ensure that old systems which did not 841 // set the configuration on seeding get the configuration 842 // update too. 843 if err := markSeededInConfig(m.state); err != nil { 844 return err 845 } 846 m.ensureSeedInConfigRan = true 847 } 848 849 return nil 850 851 } 852 853 type ensureError struct { 854 errs []error 855 } 856 857 func (e *ensureError) Error() string { 858 if len(e.errs) == 1 { 859 return fmt.Sprintf("devicemgr: %v", e.errs[0]) 860 } 861 parts := []string{"devicemgr:"} 862 for _, e := range e.errs { 863 parts = append(parts, e.Error()) 864 } 865 return strings.Join(parts, "\n - ") 866 } 867 868 // no \n allowed in warnings 869 var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.` 870 871 // Ensure implements StateManager.Ensure. 872 func (m *DeviceManager) Ensure() error { 873 var errs []error 874 875 if err := m.ensureSeeded(); err != nil { 876 m.state.Lock() 877 m.state.Warnf(seedFailureFmt, err) 878 m.state.Unlock() 879 errs = append(errs, fmt.Errorf("cannot seed: %v", err)) 880 } 881 882 if !m.preseed { 883 if err := m.ensureCloudInitRestricted(); err != nil { 884 errs = append(errs, err) 885 } 886 887 if err := m.ensureOperational(); err != nil { 888 errs = append(errs, err) 889 } 890 891 if err := m.ensureBootOk(); err != nil { 892 errs = append(errs, err) 893 } 894 895 if err := m.ensureSeedInConfig(); err != nil { 896 errs = append(errs, err) 897 } 898 899 if err := m.ensureInstalled(); err != nil { 900 errs = append(errs, err) 901 } 902 } 903 904 if len(errs) > 0 { 905 return &ensureError{errs} 906 } 907 908 return nil 909 } 910 911 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 912 device, err := m.device() 913 if err != nil { 914 return nil, err 915 } 916 917 if device.KeyID == "" { 918 return nil, state.ErrNoState 919 } 920 921 privKey, err := m.keypairMgr.Get(device.KeyID) 922 if err != nil { 923 return nil, fmt.Errorf("cannot read device key pair: %v", err) 924 } 925 return privKey, nil 926 } 927 928 // Registered returns a channel that is closed when the device is known to have been registered. 929 func (m *DeviceManager) Registered() <-chan struct{} { 930 return m.reg 931 } 932 933 // device returns current device state. 934 func (m *DeviceManager) device() (*auth.DeviceState, error) { 935 return internal.Device(m.state) 936 } 937 938 // setDevice sets the device details in the state. 939 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 940 return internal.SetDevice(m.state, device) 941 } 942 943 // Model returns the device model assertion. 944 func (m *DeviceManager) Model() (*asserts.Model, error) { 945 return findModel(m.state) 946 } 947 948 // Serial returns the device serial assertion. 949 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 950 return findSerial(m.state, nil) 951 } 952 953 type SystemAction struct { 954 Title string 955 Mode string 956 } 957 958 type System struct { 959 // Current is true when the system running now was installed from that 960 // seed 961 Current bool 962 // Label of the seed system 963 Label string 964 // Model assertion of the system 965 Model *asserts.Model 966 // Brand information 967 Brand *asserts.Account 968 // Actions available for this system 969 Actions []SystemAction 970 } 971 972 var defaultSystemActions = []SystemAction{ 973 {Title: "Install", Mode: "install"}, 974 } 975 var currentSystemActions = []SystemAction{ 976 {Title: "Reinstall", Mode: "install"}, 977 {Title: "Recover", Mode: "recover"}, 978 {Title: "Run normally", Mode: "run"}, 979 } 980 var recoverSystemActions = []SystemAction{ 981 {Title: "Reinstall", Mode: "install"}, 982 {Title: "Run normally", Mode: "run"}, 983 } 984 985 var ErrNoSystems = errors.New("no systems seeds") 986 987 // Systems list the available recovery/seeding systems. Returns the list of 988 // systems, ErrNoSystems when no systems seeds were found or other error. 989 func (m *DeviceManager) Systems() ([]*System, error) { 990 // it's tough luck when we cannot determine the current system seed 991 systemMode := m.SystemMode() 992 currentSys, _ := currentSystemForMode(m.state, systemMode) 993 994 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 995 if err != nil && !os.IsNotExist(err) { 996 return nil, fmt.Errorf("cannot list available systems: %v", err) 997 } 998 if len(systemLabels) == 0 { 999 // maybe not a UC20 system 1000 return nil, ErrNoSystems 1001 } 1002 1003 var systems []*System 1004 for _, fpLabel := range systemLabels { 1005 label := filepath.Base(fpLabel) 1006 system, err := systemFromSeed(label, currentSys) 1007 if err != nil { 1008 // TODO:UC20 add a Broken field to the seed system like 1009 // we do for snap.Info 1010 logger.Noticef("cannot load system %q seed: %v", label, err) 1011 continue 1012 } 1013 systems = append(systems, system) 1014 } 1015 return systems, nil 1016 } 1017 1018 var ErrUnsupportedAction = errors.New("unsupported action") 1019 1020 // Reboot triggers a reboot into the given systemLabel and mode. 1021 // 1022 // When called without a systemLabel and without a mode it will just 1023 // trigger a regular reboot. 1024 // 1025 // When called without a systemLabel but with a mode it will use 1026 // the current system to enter the given mode. 1027 // 1028 // Note that "recover" and "run" modes are only available for the 1029 // current system. 1030 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1031 rebootCurrent := func() { 1032 logger.Noticef("rebooting system") 1033 m.state.RequestRestart(state.RestartSystemNow) 1034 } 1035 1036 // most simple case: just reboot 1037 if systemLabel == "" && mode == "" { 1038 m.state.Lock() 1039 defer m.state.Unlock() 1040 1041 rebootCurrent() 1042 return nil 1043 } 1044 1045 // no systemLabel means "current" so get the current system label 1046 if systemLabel == "" { 1047 systemMode := m.SystemMode() 1048 currentSys, err := currentSystemForMode(m.state, systemMode) 1049 if err != nil { 1050 return fmt.Errorf("cannot get curent system: %v", err) 1051 } 1052 systemLabel = currentSys.System 1053 } 1054 1055 switched := func(systemLabel string, sysAction *SystemAction) { 1056 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1057 m.state.RequestRestart(state.RestartSystemNow) 1058 } 1059 // even if we are already in the right mode we restart here by 1060 // passing rebootCurrent as this is what the user requested 1061 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1062 } 1063 1064 // RequestSystemAction requests the provided system to be run in a 1065 // given mode as specified by action. 1066 // A system reboot will be requested when the request can be 1067 // successfully carried out. 1068 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1069 if systemLabel == "" { 1070 return fmt.Errorf("internal error: system label is unset") 1071 } 1072 1073 nop := func() {} 1074 switched := func(systemLabel string, sysAction *SystemAction) { 1075 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1076 m.state.RequestRestart(state.RestartSystemNow) 1077 } 1078 // we do nothing (nop) if the mode and system are the same 1079 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1080 } 1081 1082 // switchToSystemAndMode switches to given systemLabel and mode. 1083 // If the systemLabel and mode are the same as current, it calls 1084 // sameSystemAndMode. If successful otherwise it calls switched. Both 1085 // are called with the state lock held. 1086 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1087 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1088 return err 1089 } 1090 1091 systemMode := m.SystemMode() 1092 // ignore the error to be robust in scenarios that 1093 // dont' stricly require currentSys to be carried through. 1094 // make sure that currentSys == nil does not break 1095 // the code below! 1096 // TODO: should we log the error? 1097 currentSys, _ := currentSystemForMode(m.state, systemMode) 1098 1099 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1100 if _, err := os.Stat(systemSeedDir); err != nil { 1101 // XXX: should we wrap this instead return a naked stat error? 1102 return err 1103 } 1104 system, err := systemFromSeed(systemLabel, currentSys) 1105 if err != nil { 1106 return fmt.Errorf("cannot load seed system: %v", err) 1107 } 1108 1109 var sysAction *SystemAction 1110 for _, act := range system.Actions { 1111 if mode == act.Mode { 1112 sysAction = &act 1113 break 1114 } 1115 } 1116 if sysAction == nil { 1117 // XXX: provide more context here like what mode was requested? 1118 return ErrUnsupportedAction 1119 } 1120 1121 // XXX: requested mode is valid; only current system has 'run' and 1122 // recover 'actions' 1123 1124 switch systemMode { 1125 case "recover", "run": 1126 // if going from recover to recover or from run to run and the systems 1127 // are the same do nothing 1128 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1129 m.state.Lock() 1130 defer m.state.Unlock() 1131 sameSystemAndMode() 1132 return nil 1133 } 1134 case "install": 1135 // requesting system actions in install mode does not make sense atm 1136 // 1137 // TODO:UC20: maybe factory hooks will be able to something like 1138 // this? 1139 return ErrUnsupportedAction 1140 default: 1141 // probably test device manager mocking problem, or also potentially 1142 // missing modeenv 1143 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1144 } 1145 1146 m.state.Lock() 1147 defer m.state.Unlock() 1148 1149 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1150 if err != nil { 1151 return err 1152 } 1153 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1154 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1155 } 1156 1157 switched(systemLabel, sysAction) 1158 return nil 1159 } 1160 1161 // implement storecontext.Backend 1162 1163 type storeContextBackend struct { 1164 *DeviceManager 1165 } 1166 1167 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1168 return scb.DeviceManager.device() 1169 } 1170 1171 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1172 return scb.DeviceManager.setDevice(device) 1173 } 1174 1175 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1176 st := scb.DeviceManager.state 1177 return proxyStore(st, config.NewTransaction(st)) 1178 } 1179 1180 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1181 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1182 if serial == nil { 1183 // shouldn't happen, but be safe 1184 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1185 } 1186 1187 privKey, err := scb.DeviceManager.keyPair() 1188 if err == state.ErrNoState { 1189 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1190 } 1191 if err != nil { 1192 return nil, err 1193 } 1194 1195 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1196 "brand-id": serial.BrandID(), 1197 "model": serial.Model(), 1198 "serial": serial.Serial(), 1199 "nonce": nonce, 1200 "timestamp": time.Now().UTC().Format(time.RFC3339), 1201 }, nil, privKey) 1202 if err != nil { 1203 return nil, err 1204 } 1205 1206 return a.(*asserts.DeviceSessionRequest), nil 1207 } 1208 1209 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1210 return storeContextBackend{m} 1211 }