github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/boot/bootstate20.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-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 boot 21 22 import ( 23 "fmt" 24 "path/filepath" 25 26 "github.com/snapcore/snapd/bootloader" 27 "github.com/snapcore/snapd/dirs" 28 "github.com/snapcore/snapd/logger" 29 "github.com/snapcore/snapd/osutil" 30 "github.com/snapcore/snapd/snap" 31 "github.com/snapcore/snapd/strutil" 32 ) 33 34 func newBootState20(typ snap.Type, dev Device) bootState { 35 switch typ { 36 case snap.TypeBase: 37 return &bootState20Base{} 38 case snap.TypeKernel: 39 return &bootState20Kernel{ 40 dev: dev, 41 } 42 default: 43 panic(fmt.Sprintf("cannot make a bootState20 for snap type %q", typ)) 44 } 45 } 46 47 func loadModeenv() (*Modeenv, error) { 48 modeenv, err := ReadModeenv("") 49 if err != nil { 50 return nil, fmt.Errorf("cannot get snap revision: unable to read modeenv: %v", err) 51 } 52 return modeenv, nil 53 } 54 55 // 56 // bootloaderKernelState20 methods 57 // 58 59 type bootloaderKernelState20 interface { 60 // load will setup any state / actors needed to use other methods 61 load() error 62 // kernelStatus returns the current status of the kernel, i.e. the 63 // kernel_status bootenv 64 kernelStatus() string 65 // kernel returns the current non-try kernel 66 kernel() snap.PlaceInfo 67 // kernel returns the current try kernel if it exists on the bootloader 68 tryKernel() (snap.PlaceInfo, error) 69 70 // setNextKernel marks the kernel as the next, if it's not the currently 71 // booted kernel, then the specified kernel is setup as a try-kernel 72 setNextKernel(sn snap.PlaceInfo, status string) error 73 // markSuccessfulKernel marks the specified kernel as having booted 74 // successfully, whether that kernel is the current kernel or the try-kernel 75 markSuccessfulKernel(sn snap.PlaceInfo) error 76 } 77 78 // 79 // bootStateUpdate for 20 methods 80 // 81 82 type bootCommitTask func() error 83 84 // bootStateUpdate20 implements the bootStateUpdate interface for both kernel 85 // and base snaps on UC20. 86 type bootStateUpdate20 struct { 87 // tasks to run before the modeenv has been written 88 preModeenvTasks []bootCommitTask 89 90 // the modeenv that was read from disk 91 modeenv *Modeenv 92 93 // the modeenv that will be written out in commit 94 writeModeenv *Modeenv 95 96 // tasks to run after the modeenv has been written 97 postModeenvTasks []bootCommitTask 98 } 99 100 func (u20 *bootStateUpdate20) preModeenv(task bootCommitTask) { 101 u20.preModeenvTasks = append(u20.preModeenvTasks, task) 102 } 103 104 func (u20 *bootStateUpdate20) postModeenv(task bootCommitTask) { 105 u20.postModeenvTasks = append(u20.postModeenvTasks, task) 106 } 107 108 func newBootStateUpdate20(m *Modeenv) (*bootStateUpdate20, error) { 109 u20 := &bootStateUpdate20{} 110 if m == nil { 111 var err error 112 m, err = loadModeenv() 113 if err != nil { 114 return nil, err 115 } 116 } 117 // copy the modeenv for the write object 118 u20.modeenv = m 119 var err error 120 u20.writeModeenv, err = m.Copy() 121 if err != nil { 122 return nil, err 123 } 124 return u20, nil 125 } 126 127 // commit will write out boot state persistently to disk. 128 func (u20 *bootStateUpdate20) commit() error { 129 // The actual actions taken here will depend on what things were called 130 // before commit(), either setNextBoot for a single type of kernel snap, or 131 // markSuccessful for kernel and/or base snaps. 132 // It is expected that the caller code is carefully analyzed to avoid 133 // critical points where a hard system reset during that critical point 134 // would brick a device or otherwise severely fail an update. 135 // There are three things that callers can do before calling commit(), 136 // 1. modify writeModeenv to specify new values for things that will be 137 // written to disk in the modeenv. 138 // 2. Add tasks to run before writing the modeenv. 139 // 3. Add tasks to run after writing the modeenv. 140 141 // first handle any pre-modeenv writing tasks 142 for _, t := range u20.preModeenvTasks { 143 if err := t(); err != nil { 144 return err 145 } 146 } 147 148 modeenvRewritten := false 149 // next write the modeenv if it changed 150 if !u20.writeModeenv.deepEqual(u20.modeenv) { 151 if err := u20.writeModeenv.Write(); err != nil { 152 return err 153 } 154 modeenvRewritten = true 155 } 156 157 // next reseal using the modeenv values, we do this before any 158 // post-modeenv tasks so if we are rebooted at any point after 159 // the reseal even before the post tasks are completed, we 160 // still boot properly 161 162 // if there is ambiguity whether the boot chains have 163 // changed because of unasserted kernels, then pass a 164 // flag as hint whether to reseal based on whether we 165 // wrote the modeenv 166 expectReseal := modeenvRewritten 167 if err := resealKeyToModeenv(dirs.GlobalRootDir, u20.writeModeenv, expectReseal); err != nil { 168 return err 169 } 170 171 // finally handle any post-modeenv writing tasks 172 for _, t := range u20.postModeenvTasks { 173 if err := t(); err != nil { 174 return err 175 } 176 } 177 178 return nil 179 } 180 181 // 182 // kernel snap methods 183 // 184 185 // bootState20Kernel implements the bootState interface for kernel snaps on 186 // UC20. It is used for both setNext() and markSuccessful(), with both of those 187 // methods returning bootStateUpdate20 to be used with bootStateUpdate. 188 type bootState20Kernel struct { 189 bks bootloaderKernelState20 190 191 // used to find the bootloader to manipulate the enabled kernel, etc. 192 blOpts *bootloader.Options 193 blDir string 194 195 dev Device 196 } 197 198 func (ks20 *bootState20Kernel) loadBootenv() error { 199 // don't setup multiple times 200 if ks20.bks != nil { 201 return nil 202 } 203 204 // find the run-mode bootloader 205 var opts *bootloader.Options 206 if ks20.blOpts != nil { 207 opts = ks20.blOpts 208 } else { 209 opts = &bootloader.Options{ 210 Role: bootloader.RoleRunMode, 211 } 212 } 213 bl, err := bootloader.Find(ks20.blDir, opts) 214 if err != nil { 215 return err 216 } 217 ebl, ok := bl.(bootloader.ExtractedRunKernelImageBootloader) 218 if ok { 219 // use the new 20-style ExtractedRunKernelImage implementation 220 ks20.bks = &extractedRunKernelImageBootloaderKernelState{ebl: ebl} 221 } else { 222 // use fallback pure bootloader env implementation 223 ks20.bks = &envRefExtractedKernelBootloaderKernelState{bl: bl} 224 } 225 226 // setup the bootloaderKernelState20 227 if err := ks20.bks.load(); err != nil { 228 return err 229 } 230 231 return nil 232 } 233 234 func (ks20 *bootState20Kernel) revisions() (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) { 235 var tryBootSn snap.PlaceInfo 236 err = ks20.loadBootenv() 237 if err != nil { 238 return nil, nil, "", err 239 } 240 241 status := ks20.bks.kernelStatus() 242 kern := ks20.bks.kernel() 243 244 tryKernel, err := ks20.bks.tryKernel() 245 // if err is ErrNoTryKernelRef, then we will just return nil as the trySnap 246 if err != nil && err != bootloader.ErrNoTryKernelRef { 247 return kern, nil, "", newTrySnapErrorf("cannot identify try kernel snap: %v", err) 248 } 249 250 if err == nil { 251 tryBootSn = tryKernel 252 } 253 254 return kern, tryBootSn, status, nil 255 } 256 257 func (ks20 *bootState20Kernel) revisionsFromModeenv(*Modeenv) (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) { 258 // the kernel snap doesn't use modeenv at all for getting their revisions 259 return ks20.revisions() 260 } 261 262 func (ks20 *bootState20Kernel) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 263 // call the generic method with this object to do most of the legwork 264 u20, sn, err := selectSuccessfulBootSnap(ks20, update) 265 if err != nil { 266 return nil, err 267 } 268 269 // XXX: this if arises because some unit tests rely on not setting up kernel 270 // details and just operating on the base snap but this situation would 271 // never happen in reality 272 if sn != nil { 273 // On commit, mark the kernel successful before rewriting the modeenv 274 // because if we first rewrote the modeenv then got rebooted before 275 // marking the kernel successful, the bootloader would see that the boot 276 // failed to mark it successful and then fall back to the original 277 // kernel, but that kernel would no longer be in the modeenv, so we 278 // would die in the initramfs 279 u20.preModeenv(func() error { return ks20.bks.markSuccessfulKernel(sn) }) 280 281 // On commit, set CurrentKernels as just this kernel because that is the 282 // successful kernel we booted 283 u20.writeModeenv.CurrentKernels = []string{sn.Filename()} 284 } 285 286 return u20, nil 287 } 288 289 func (ks20 *bootState20Kernel) setNext(next snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) { 290 u20, nextStatus, err := genericSetNext(ks20, next) 291 if err != nil { 292 return false, nil, err 293 } 294 295 // if we are setting a snap as a try snap, then we need to reboot 296 rebootRequired = false 297 if nextStatus == TryStatus { 298 rebootRequired = true 299 } 300 301 currentKernel := ks20.bks.kernel() 302 if next.Filename() != currentKernel.Filename() { 303 // on commit, add this kernel to the modeenv 304 u20.writeModeenv.CurrentKernels = append( 305 u20.writeModeenv.CurrentKernels, 306 next.Filename(), 307 ) 308 } 309 310 // On commit, if we are about to try an update, and need to set the next 311 // kernel before rebooting, we need to do that after updating the modeenv, 312 // because if we did it before and got rebooted in between setting the next 313 // kernel and updating the modeenv, the initramfs would fail the boot 314 // because the modeenv doesn't "trust" or expect the new kernel that booted. 315 // As such, set the next kernel as a post modeenv task. 316 u20.postModeenv(func() error { return ks20.bks.setNextKernel(next, nextStatus) }) 317 318 return rebootRequired, u20, nil 319 } 320 321 // selectAndCommitSnapInitramfsMount chooses which snap should be mounted 322 // during the initramfs, and commits that choice if it needs state updated. 323 // Choosing to boot/mount the base snap needs to be committed to the 324 // modeenv, but no state needs to be committed when choosing to mount a 325 // kernel snap. 326 func (ks20 *bootState20Kernel) selectAndCommitSnapInitramfsMount(modeenv *Modeenv) (sn snap.PlaceInfo, err error) { 327 // first do the generic choice of which snap to use 328 first, second, err := genericInitramfsSelectSnap(ks20, modeenv, TryingStatus, "kernel") 329 if err != nil && err != errTrySnapFallback { 330 return nil, err 331 } 332 333 if err == errTrySnapFallback { 334 // this should not actually return, it should immediately reboot 335 return nil, initramfsReboot() 336 } 337 338 // now validate the chosen kernel snap against the modeenv CurrentKernel's 339 // setting 340 if strutil.ListContains(modeenv.CurrentKernels, first.Filename()) { 341 return first, nil 342 } 343 344 // if we didn't trust the first kernel in the modeenv, and second is set as 345 // a fallback, that means we booted a try kernel which is the first kernel, 346 // but we need to fallback to the second kernel, but we can't do that in the 347 // initramfs, we need to reboot so the bootloader boots the fallback kernel 348 // for us 349 350 if second != nil { 351 // this should not actually return, it should immediately reboot 352 return nil, initramfsReboot() 353 } 354 355 // no fallback expected, so first snap _is_ the only kernel and isn't 356 // trusted! 357 // since we have nothing to fallback to, we don't issue a reboot and will 358 // instead just fail the systemd unit in the initramfs for an operator to 359 // debug/fix 360 return nil, fmt.Errorf("fallback kernel snap %q is not trusted in the modeenv", first.Filename()) 361 } 362 363 // 364 // base snap methods 365 // 366 367 // bootState20Base implements the bootState interface for base snaps on UC20. 368 // It is used for both setNext() and markSuccessful(), with both of those 369 // methods returning bootStateUpdate20 to be used with bootStateUpdate. 370 type bootState20Base struct{} 371 372 // revisions returns the current boot snap and optional try boot snap for the 373 // type specified in bsgeneric. 374 func (bs20 *bootState20Base) revisions() (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) { 375 modeenv, err := loadModeenv() 376 if err != nil { 377 return nil, nil, "", err 378 } 379 return bs20.revisionsFromModeenv(modeenv) 380 } 381 382 func (bs20 *bootState20Base) revisionsFromModeenv(modeenv *Modeenv) (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) { 383 var bootSn, tryBootSn snap.PlaceInfo 384 385 if modeenv.Base == "" { 386 return nil, nil, "", fmt.Errorf("cannot get snap revision: modeenv base boot variable is empty") 387 } 388 389 bootSn, err = snap.ParsePlaceInfoFromSnapFileName(modeenv.Base) 390 if err != nil { 391 return nil, nil, "", fmt.Errorf("cannot get snap revision: modeenv base boot variable is invalid: %v", err) 392 } 393 394 if modeenv.BaseStatus != DefaultStatus && modeenv.TryBase != "" { 395 tryBootSn, err = snap.ParsePlaceInfoFromSnapFileName(modeenv.TryBase) 396 if err != nil { 397 return bootSn, nil, "", newTrySnapErrorf("cannot get snap revision: modeenv try base boot variable is invalid: %v", err) 398 } 399 } 400 401 return bootSn, tryBootSn, modeenv.BaseStatus, nil 402 } 403 404 func (bs20 *bootState20Base) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 405 // call the generic method with this object to do most of the legwork 406 u20, sn, err := selectSuccessfulBootSnap(bs20, update) 407 if err != nil { 408 return nil, err 409 } 410 411 // on commit, always clear the base_status and try_base when marking 412 // successful, this has the useful side-effect of cleaning up if we have 413 // base_status=trying but no try_base set, or if we had an issue with 414 // try_base being invalid 415 u20.writeModeenv.BaseStatus = DefaultStatus 416 u20.writeModeenv.TryBase = "" 417 418 // set the base 419 u20.writeModeenv.Base = sn.Filename() 420 421 return u20, nil 422 } 423 424 func (bs20 *bootState20Base) setNext(next snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) { 425 u20, nextStatus, err := genericSetNext(bs20, next) 426 if err != nil { 427 return false, nil, err 428 } 429 430 // if we are setting a snap as a try snap, then we need to reboot 431 rebootRequired = false 432 if nextStatus == TryStatus { 433 // only update the try base if we are actually in try status 434 u20.writeModeenv.TryBase = next.Filename() 435 rebootRequired = true 436 } 437 438 // always update the base status 439 u20.writeModeenv.BaseStatus = nextStatus 440 441 return rebootRequired, u20, nil 442 } 443 444 // selectAndCommitSnapInitramfsMount chooses which snap should be mounted 445 // during the early boot sequence, i.e. the initramfs, and commits that 446 // choice if it needs state updated. 447 // Choosing to boot/mount the base snap needs to be committed to the 448 // modeenv, but no state needs to be committed when choosing to mount a 449 // kernel snap. 450 func (bs20 *bootState20Base) selectAndCommitSnapInitramfsMount(modeenv *Modeenv) (sn snap.PlaceInfo, err error) { 451 // first do the generic choice of which snap to use 452 // the logic in that function is sufficient to pick the base snap entirely, 453 // so we don't ever need to look at the fallback snap, we just need to know 454 // whether the chosen snap is a try snap or not, if it is then we process 455 // the modeenv in the "try" -> "trying" case 456 first, second, err := genericInitramfsSelectSnap(bs20, modeenv, TryStatus, "base") 457 // errTrySnapFallback is handled manually by inspecting second below 458 if err != nil && err != errTrySnapFallback { 459 return nil, err 460 } 461 462 modeenvChanged := false 463 464 // apply the update logic to the choices modeenv 465 switch modeenv.BaseStatus { 466 case TryStatus: 467 // if we were in try status and we have a fallback, then we are in a 468 // normal try state and we change status to TryingStatus now 469 // all other cleanup of state is left to user space snapd 470 if second != nil { 471 modeenv.BaseStatus = TryingStatus 472 modeenvChanged = true 473 } 474 case TryingStatus: 475 // we tried to boot a try base snap and failed, so we need to reset 476 // BaseStatus 477 modeenv.BaseStatus = DefaultStatus 478 modeenvChanged = true 479 case DefaultStatus: 480 // nothing to do 481 default: 482 // log a message about invalid setting 483 logger.Noticef("invalid setting for \"base_status\" in modeenv : %q", modeenv.BaseStatus) 484 } 485 486 if modeenvChanged { 487 err = modeenv.Write() 488 if err != nil { 489 return nil, err 490 } 491 } 492 493 return first, nil 494 } 495 496 // 497 // generic methods 498 // 499 500 type bootState20 interface { 501 bootState 502 // revisionsFromModeenv implements bootState.revisions but starting 503 // from an already loaded Modeenv. 504 revisionsFromModeenv(*Modeenv) (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) 505 } 506 507 // genericSetNext implements the generic logic for setting up a snap to be tried 508 // for boot and works for both kernel and base snaps (though not 509 // simultaneously). 510 func genericSetNext(b bootState20, next snap.PlaceInfo) (u20 *bootStateUpdate20, setStatus string, err error) { 511 u20, err = newBootStateUpdate20(nil) 512 if err != nil { 513 return nil, "", err 514 } 515 516 // get the current snap 517 current, _, _, err := b.revisionsFromModeenv(u20.modeenv) 518 if err != nil { 519 return nil, "", err 520 } 521 522 // check if the next snap is really the same as the current snap, in which 523 // case we either do nothing or just clear the status (and not reboot) 524 if current.SnapName() == next.SnapName() && next.SnapRevision() == current.SnapRevision() { 525 // if we are setting the next snap as the current snap, don't need to 526 // change any snaps, just reset the status to default 527 return u20, DefaultStatus, nil 528 } 529 530 // by default we will set the status as "try" to prepare for an update, 531 // which also by default will require a reboot 532 return u20, TryStatus, nil 533 } 534 535 func toBootStateUpdate20(update bootStateUpdate) (u20 *bootStateUpdate20, err error) { 536 // try to extract bootStateUpdate20 out of update 537 if update != nil { 538 var ok bool 539 if u20, ok = update.(*bootStateUpdate20); !ok { 540 return nil, fmt.Errorf("internal error, cannot thread %T with update for UC20", update) 541 } 542 } 543 if u20 == nil { 544 // make a new one, also loading modeenv 545 u20, err = newBootStateUpdate20(nil) 546 if err != nil { 547 return nil, err 548 } 549 } 550 return u20, nil 551 } 552 553 // selectSuccessfulBootSnap inspects the specified boot state to pick what 554 // boot snap should be marked as successful and use as a valid rollback target. 555 // If the first return value is non-nil, the second return value will be the 556 // snap that was booted and should be marked as successful. 557 func selectSuccessfulBootSnap(b bootState20, update bootStateUpdate) ( 558 u20 *bootStateUpdate20, 559 bootedSnap snap.PlaceInfo, 560 err error, 561 ) { 562 u20, err = toBootStateUpdate20(update) 563 if err != nil { 564 return nil, nil, err 565 } 566 567 // get the try snap and the current status 568 sn, trySnap, status, err := b.revisionsFromModeenv(u20.modeenv) 569 if err != nil { 570 return nil, nil, err 571 } 572 573 // kernel_status and base_status go from "" -> "try" (set by snapd), to 574 // "try" -> "trying" (set by the boot script) 575 // so if we are in "trying" mode, then we should choose the try snap 576 if status == TryingStatus && trySnap != nil { 577 return u20, trySnap, nil 578 } 579 580 // if we are not in trying then choose the normal snap 581 return u20, sn, nil 582 } 583 584 // genericInitramfsSelectSnap will run the logic to choose which snap should be 585 // mounted during the initramfs using the given bootState and the expected try 586 // status. The try status is needed because during the initramfs we will have 587 // different statuses for kernel vs base snaps, where base snap is expected to 588 // be in "try" mode, but kernel is expected to be in "trying" mode. It returns 589 // the first and second choice for what snaps to mount. If there is a second 590 // snap, then that snap is the fallback or non-trying snap and the first snap is 591 // the try snap. 592 func genericInitramfsSelectSnap(bs bootState20, modeenv *Modeenv, expectedTryStatus, typeString string) ( 593 firstChoice, secondChoice snap.PlaceInfo, 594 err error, 595 ) { 596 curSnap, trySnap, snapTryStatus, err := bs.revisionsFromModeenv(modeenv) 597 598 if err != nil && !isTrySnapError(err) { 599 // we have no fallback snap! 600 return nil, nil, fmt.Errorf("fallback %s snap unusable: %v", typeString, err) 601 } 602 603 // check that the current snap actually exists 604 file := curSnap.Filename() 605 snapPath := filepath.Join(dirs.SnapBlobDirUnder(InitramfsWritableDir), file) 606 if !osutil.FileExists(snapPath) { 607 // somehow the boot snap doesn't exist in ubuntu-data 608 // for a kernel, this could happen if we have some bug where ubuntu-boot 609 // isn't properly updated and never changes, but snapd thinks it was 610 // updated and eventually snapd garbage collects old revisions of 611 // the kernel snap as it is "refreshed" 612 // for a base, this could happen if the modeenv is manipulated 613 // out-of-band from snapd 614 return nil, nil, fmt.Errorf("%s snap %q does not exist on ubuntu-data", typeString, file) 615 } 616 617 if err != nil && isTrySnapError(err) { 618 // just log that we had issues with the try snap and continue with 619 // using the normal snap 620 logger.Noticef("unable to process try %s snap: %v", typeString, err) 621 return curSnap, nil, errTrySnapFallback 622 } 623 if snapTryStatus != expectedTryStatus { 624 // the status is unexpected, log if its value is invalid and continue 625 // with the normal snap 626 fallbackErr := errTrySnapFallback 627 switch snapTryStatus { 628 case DefaultStatus: 629 fallbackErr = nil 630 case TryStatus, TryingStatus: 631 default: 632 logger.Noticef("\"%s_status\" has an invalid setting: %q", typeString, snapTryStatus) 633 } 634 return curSnap, nil, fallbackErr 635 } 636 // then we are trying a snap update and there should be a try snap 637 if trySnap == nil { 638 // it is unexpected when there isn't one 639 logger.Noticef("try-%[1]s snap is empty, but \"%[1]s_status\" is \"trying\"", typeString) 640 return curSnap, nil, errTrySnapFallback 641 } 642 trySnapPath := filepath.Join(dirs.SnapBlobDirUnder(InitramfsWritableDir), trySnap.Filename()) 643 if !osutil.FileExists(trySnapPath) { 644 // or when the snap file does not exist 645 logger.Noticef("try-%s snap %q does not exist", typeString, trySnap.Filename()) 646 return curSnap, nil, errTrySnapFallback 647 } 648 649 // we have a try snap and everything appears in order 650 return trySnap, curSnap, nil 651 } 652 653 // 654 // non snap boot resources 655 // 656 657 // bootState20BootAssets implements the successfulBootState interface for trusted 658 // boot assets UC20. 659 type bootState20BootAssets struct { 660 dev Device 661 } 662 663 func (ba20 *bootState20BootAssets) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 664 u20, err := toBootStateUpdate20(update) 665 if err != nil { 666 return nil, err 667 } 668 669 if len(u20.modeenv.CurrentTrustedBootAssets) == 0 && len(u20.modeenv.CurrentTrustedRecoveryBootAssets) == 0 { 670 // not using trusted boot assets, nothing more to do 671 return update, nil 672 } 673 674 newM, dropAssets, err := observeSuccessfulBootAssets(u20.writeModeenv) 675 if err != nil { 676 return nil, fmt.Errorf("cannot mark successful boot assets: %v", err) 677 } 678 // update modeenv 679 u20.writeModeenv = newM 680 681 if len(dropAssets) == 0 { 682 // nothing to drop, we're done 683 return u20, nil 684 } 685 686 u20.postModeenv(func() error { 687 cache := newTrustedAssetsCache(dirs.SnapBootAssetsDir) 688 // drop listed assets from cache 689 for _, ta := range dropAssets { 690 err := cache.Remove(ta.blName, ta.name, ta.hash) 691 if err != nil { 692 // XXX: should this be a log instead? 693 return fmt.Errorf("cannot remove unused boot asset %v:%v: %v", ta.name, ta.hash, err) 694 } 695 } 696 return nil 697 }) 698 return u20, nil 699 } 700 701 func trustedAssetsBootState(dev Device) *bootState20BootAssets { 702 return &bootState20BootAssets{ 703 dev: dev, 704 } 705 } 706 707 // bootState20CommandLine implements the successfulBootState interface for 708 // kernel command line 709 type bootState20CommandLine struct { 710 dev Device 711 } 712 713 func (bcl20 *bootState20CommandLine) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 714 u20, err := toBootStateUpdate20(update) 715 if err != nil { 716 return nil, err 717 } 718 newM, err := observeSuccessfulCommandLine(bcl20.dev.Model(), u20.writeModeenv) 719 if err != nil { 720 return nil, fmt.Errorf("cannot mark successful boot command line: %v", err) 721 } 722 u20.writeModeenv = newM 723 return u20, nil 724 } 725 726 func trustedCommandLineBootState(dev Device) *bootState20CommandLine { 727 return &bootState20CommandLine{ 728 dev: dev, 729 } 730 } 731 732 // bootState20RecoverySystem implements the successfulBootState interface for 733 // tried recovery systems 734 type bootState20RecoverySystem struct { 735 dev Device 736 } 737 738 func (brs20 *bootState20RecoverySystem) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 739 u20, err := toBootStateUpdate20(update) 740 if err != nil { 741 return nil, err 742 } 743 744 newM, err := observeSuccessfulSystems(brs20.dev.Model(), u20.writeModeenv) 745 if err != nil { 746 return nil, fmt.Errorf("cannot mark successful recovery system: %v", err) 747 } 748 u20.writeModeenv = newM 749 return u20, nil 750 } 751 752 func recoverySystemsBootState(dev Device) *bootState20RecoverySystem { 753 return &bootState20RecoverySystem{dev: dev} 754 } 755 756 // bootState20Model implements the successfulBootState interface for device 757 // model related bookkeeping 758 type bootState20Model struct { 759 dev Device 760 } 761 762 func (brs20 *bootState20Model) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 763 u20, err := toBootStateUpdate20(update) 764 if err != nil { 765 return nil, err 766 } 767 768 // sign key ID was not being populated in earlier versions of snapd, try 769 // to remedy that 770 if u20.modeenv.ModelSignKeyID == "" { 771 if err != nil { 772 return nil, err 773 } 774 u20.writeModeenv.ModelSignKeyID = brs20.dev.Model().SignKeyID() 775 } 776 return u20, nil 777 } 778 779 func modelBootState(dev Device) *bootState20Model { 780 return &bootState20Model{dev: dev} 781 }