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