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