Files
snapd/bootloader/bootloadertest/bootloadertest.go
Valentin David 2034c7edb2 boot,bootloader: add support for shim fallback and setting EFI boot variables on install (#13511)
* boot: added function to set EFI variables

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: renamed trustedShimFallbackBinary to seedShimPath

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: refactored setting EFI boot variables at install

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: adjusted variable names and fixed variable initialization

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: improve setting Boot#### EFI variable

Notably, splits off the process of reading a Boot#### variable and
extracting its DevicePath into its own function `readBootVariable` which
can be mocked and otherwise simplifies the `setBootNumberVariable`
function.

Also, fixes behavior around the final BootFFFF variable.  Previously, it
was not possible to select the BootFFFF variable if it was unused, due
to overflow concerns on uint16.  Now, the behavior around BootFFFF is
identical to that of any other boot variable, by using an int internally
instead of uint16, which also allows a more robust check for whether
there were no matching variables.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: added unit tests for setting EFI Boot#### variable

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: refactored setting EFI boot variables

Rewrote EFI boot variable functions to more closely match the behavior
of shim fallback: https://github.com/rhboot/shim/blob/main/fallback.c

In particular, the following have changed:

1. Existing Boot#### variables must fully match the new load option to
   be considered a match.  In particular, the load option attributes,
   label, and device path must all be byte-for-byte identical.
   Previously, only the device paths were compared.
2. Matching Boot#### variables are no longer overwritten.  Since the
   variable data must now byte-for-byte match the new load option, there
   is no need to overwrite the existing variable.
3. Since existing Boot#### variables are no longer overwritten, the
   variable attributes are no longer checked for those variables.
   Instead, it is assumed that the Boot#### variable attributes are
   viable for it to be used as a boot option.  This matches the behavior
   of `rhboot/shim/fallback.c`, for better or for worse.
4. When modifying the BootOrder variable, boot option numbers are no
   longer pruned if there is no matching Boot#### variable.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot,bootloader: introduce UefiBootloader to build EFI load options

Previously, the path of the shim binary relative to the EFI partition
was passed into `SetEfiBootVariables`. However, different bootloaders
may wish to set up `OptionalData` in the load option.

Additionally, not all `TrustedAssetBootloaders` will attempt to set
EFI boot variables, and not all bootloaders which should set EFI boot
variables necessarily support secure boot. Thus, these should be
decoupled.

This commit adds a new `UefiBootloader` interface with the
`ConstructShimEfiLoadOption` method, which builds an EFI load option
from the shim path for the given bootloader.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot,bootloader: fixed linting errors and improved EFI boot variable test clarity

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

bootloader: improved unit test for grub EFI load option creation

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: set EFI boot variables in `MakeRunnableSystem`

Previously, attempted to set boot variables in
`MakeRecoverySystemBootable`, which is called by `MakeBootableImage`,
which is called when building the image file, rather than during install
mode.

`MakeRunnableSystem` is called on first boot during install mode, and
thus should be responsible for setting EFI boot variables.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: use seed bootloader when setting EFI variables

In install mode, the bootloader located in ubuntu-seed should be used
when setting the EFI boot variables. Previously, the bootloader in
ubuntu-boot was accidentally re-used.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: added simple test to execute setefibootvar.go code

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: fixed standalone set EFI vars code test to work with different layouts

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: moved simple setefibootvar.go check to nested test

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: added check for idempotence when setting EFI boot variables

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

bootloader: adjust comments, organization, and add TODO

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot,bootloader: fix setting EFI boot variables

Make function to search for EFI asset device path and construct load
option common so each UefiBootloader does not have to re-implement it.
Instead, the bootloader returns the description, asset file path, and
optional data, which can then be used to create the EFI load option.

Also, in `makeRunnableSystem`, the bootloader in ubuntu-seed must have
`NoSlashBoot` in order to correctly find the grub.cfg file and thus the
grub bootloader. This commit fixes this bug, and refactors a bit to
account for the changes in responsibilities between the bootloader and
the setefibootvars.go code.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

bootloader: fixed grub EFI load option test with tmp rootdir

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

go.mod: move golang.org/x/text import next to other golang.org/x/ imports

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: adjust opts to look for recovery bootloader when setting EFI variables

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: do not overwrite BootOrder if unchanged, and unexport EFI variable helper functions

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: unexport `setEfiBootOrderVariable`

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: move code to detect bootloader and set EFI variables accordingly into dedicated function

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: unexport `setUbuntuSeedEfiBootVariables` and accompanying error

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot,bootloader: ensure nil optionalData for EFI variable is equivalent to 0-length slice

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: handle empty boot order and other boot var improvements

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

boot: make setefibootvars functions linux-only

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

* tests: add nested spread test for setting EFI vars

The test checks that EFI boot variables exist for the following:
1. A Boot#### variable pointing to the shim file path.
2. A BootOrder variable with the #### from the above Boot#### as first.

Since the layout of EFI assets is dependent on the gadget snap, the test
downloads and unpacks the gadget, then modifies the contents so that one
variant has the shim and grub binaries in `EFI/boot/` and another
variant has the shim and grub binaries in `EFI/ubuntu/` and the fallback
binary in `EFI/boot/`.

After building a core image around that modified gadget, the VM is
booted and the test checks that the EFI variables are set correctly.
Then, the test modifies the gadget to match the other variant's initial
layout, and then installs the newly modified gadget. This should trigger
re-setting EFI boot variables as well.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: fix problems in spread test for setting EFI boot variables

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: disabled TPM on EFI boot vars test and separated gadget script

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: fixed EFI vars test to use correct toolbox and include all EFI assets

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: modify-gadget.sh re-use existing gadget so edition is incremented

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: fix mangled EFI var search string and other improvements

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

tests: polish tests for setting EFI boot variables

Notably, allow tests/nested/core/core20-set-efi-boot-variables to run on
arm64 as well as amd64, simplify setefivars.go to search for multiple
assets on multiple architectures, and allow
tests/nested/manual/core20-set-efi-boot-vars to run on any ubuntu-2*.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>

* bootloader/grub.go: only consider new shim asset in boot entry for now

* tests/nested/core/core20-set-efi-boot-variables: fix details

* boot: update uefi variables on gadget update

* tests/nested/manual/core20-set-efi-boot-vars: work-around file not deleted

* tests/nested/manual/core20-set-efi-boot-vars: use fb.efi like other tests

* tests/nested/manual/core20-set-efi-boot-vars: drop use of toolbox snap

* tests/nested/manual/core20-set-efi-boot-vars: drop work-around for not deleted files

* tests/nested/manual/core20-set-efi-boot-vars: verify install does add a boot entry

* tests/nested/manual/core20-set-efi-boot-vars: run only on version that have UC

* tests/nested/manual/core20-set-efi-boot-vars: obey GADGET_CHANNEL

* tests/nested/manual/core20-set-efi-boot-vars: move get_boot_entry.py to libs

* tests/nested/manual/core20-set-efi-boot-vars: factorize copy of variables

... so we can reuse the script in other tests

* tests/nested/core/core20-set-efi-boot-variables: stop using toolbox snap

* tests/nested/core/core20-set-efi-boot-variables: only run on versions with UC available

* overlord/devicestate: test using EfiLoadOptionParameters

* boot: test that variables are set

* boot: test observers' UpdateBootEntry

* tests/nested/manual/core20-set-efi-boot-vars: also test without secure boot

* many: use trusted install observer when UEFI variables are supported

* boot/makebootable.go: rename sealer to observer

* boot/grub.go: fix function name in doc

* cmd/snap-bootstrap: verify that ObserveExistingTrustedRecoveryAssets is called

* boot: add tests for SetEfiBootVariables

* many: comment on calls to ObserveExistingTrustedRecoveryAssets

---------

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
Co-authored-by: Oliver Calder <oliver.calder@canonical.com>
2024-06-03 10:03:50 +02:00

640 lines
20 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2014-2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package bootloadertest
import (
"fmt"
"strings"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/snap"
)
// MockBootloader mocks the bootloader interface and records all
// set/get calls.
type MockBootloader struct {
MockedPresent bool
PresentErr error
BootVars map[string]string
SetBootVarsCalls int
SetErr error
SetErrFunc func() error
GetErr error
name string
bootdir string
ExtractKernelAssetsCalls []snap.PlaceInfo
RemoveKernelAssetsCalls []snap.PlaceInfo
InstallBootConfigCalled []string
InstallBootConfigErr error
enabledKernel snap.PlaceInfo
enabledTryKernel snap.PlaceInfo
panicMethods map[string]bool
}
// ensure MockBootloader(s) implement the Bootloader interface
var _ bootloader.Bootloader = (*MockBootloader)(nil)
var _ bootloader.RecoveryAwareBootloader = (*MockRecoveryAwareBootloader)(nil)
var _ bootloader.TrustedAssetsBootloader = (*MockTrustedAssetsBootloader)(nil)
var _ bootloader.ExtractedRunKernelImageBootloader = (*MockExtractedRunKernelImageBootloader)(nil)
var _ bootloader.ExtractedRecoveryKernelImageBootloader = (*MockExtractedRecoveryKernelImageBootloader)(nil)
var _ bootloader.RecoveryAwareBootloader = (*MockRecoveryAwareTrustedAssetsBootloader)(nil)
var _ bootloader.TrustedAssetsBootloader = (*MockRecoveryAwareTrustedAssetsBootloader)(nil)
var _ bootloader.NotScriptableBootloader = (*MockNotScriptableBootloader)(nil)
var _ bootloader.NotScriptableBootloader = (*MockExtractedRecoveryKernelNotScriptableBootloader)(nil)
var _ bootloader.ExtractedRecoveryKernelImageBootloader = (*MockExtractedRecoveryKernelNotScriptableBootloader)(nil)
var _ bootloader.RebootBootloader = (*MockRebootBootloader)(nil)
func Mock(name, bootdir string) *MockBootloader {
return &MockBootloader{
name: name,
bootdir: bootdir,
BootVars: make(map[string]string),
panicMethods: make(map[string]bool),
}
}
func (b *MockBootloader) maybePanic(which string) {
if b.panicMethods[which] {
panic(fmt.Sprintf("mocked reboot panic in %s", which))
}
}
func (b *MockBootloader) SetBootVars(values map[string]string) error {
b.maybePanic("SetBootVars")
b.SetBootVarsCalls++
for k, v := range values {
b.BootVars[k] = v
}
if b.SetErrFunc != nil {
return b.SetErrFunc()
}
return b.SetErr
}
func (b *MockBootloader) GetBootVars(keys ...string) (map[string]string, error) {
b.maybePanic("GetBootVars")
out := map[string]string{}
for _, k := range keys {
out[k] = b.BootVars[k]
}
return out, b.GetErr
}
func (b *MockBootloader) Name() string {
return b.name
}
func (b *MockBootloader) Present() (bool, error) {
return b.MockedPresent, b.PresentErr
}
func (b *MockBootloader) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
b.ExtractKernelAssetsCalls = append(b.ExtractKernelAssetsCalls, s)
return nil
}
func (b *MockBootloader) RemoveKernelAssets(s snap.PlaceInfo) error {
b.RemoveKernelAssetsCalls = append(b.RemoveKernelAssetsCalls, s)
return nil
}
func (b *MockBootloader) SetEnabledKernel(s snap.PlaceInfo) (restore func()) {
oldSn := b.enabledTryKernel
oldVar := b.BootVars["snap_kernel"]
b.enabledKernel = s
b.BootVars["snap_kernel"] = s.Filename()
return func() {
b.BootVars["snap_kernel"] = oldVar
b.enabledKernel = oldSn
}
}
func (b *MockBootloader) SetEnabledTryKernel(s snap.PlaceInfo) (restore func()) {
oldSn := b.enabledTryKernel
oldVar := b.BootVars["snap_try_kernel"]
b.enabledTryKernel = s
b.BootVars["snap_try_kernel"] = s.Filename()
return func() {
b.BootVars["snap_try_kernel"] = oldVar
b.enabledTryKernel = oldSn
}
}
// InstallBootConfig installs the boot config in the gadget directory to the
// mock bootloader's root directory.
func (b *MockBootloader) InstallBootConfig(gadgetDir string, opts *bootloader.Options) error {
b.InstallBootConfigCalled = append(b.InstallBootConfigCalled, gadgetDir)
return b.InstallBootConfigErr
}
// SetMockToPanic allows setting any method in the Bootloader interface or derived
// interface to panic instead of returning. This allows one to test what would
// happen if the system was rebooted during execution of a particular function.
// Specifically, the panic will be done immediately entering the function so
// setting SetBootVars to panic will emulate a reboot before any boot vars are
// set persistently
func (b *MockBootloader) SetMockToPanic(f string) (restore func()) {
switch f {
// XXX: update this list as more calls in this interface or derived ones
// are added
case "SetBootVars", "GetBootVars",
"EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel":
old := b.panicMethods[f]
b.panicMethods[f] = true
return func() {
b.panicMethods[f] = old
}
default:
panic(fmt.Sprintf("unknown bootloader method %q to mock reboot via panic for", f))
}
}
// MockRecoveryAwareMixin implements the RecoveryAware interface.
type MockRecoveryAwareMixin struct {
RecoverySystemDir string
RecoverySystemBootVars map[string]string
}
// MockRecoveryAwareBootloader mocks a bootloader implementing the
// RecoveryAware interface.
type MockRecoveryAwareBootloader struct {
*MockBootloader
MockRecoveryAwareMixin
}
// RecoveryAware derives a MockRecoveryAwareBootloader from a base
// MockBootloader.
func (b *MockBootloader) RecoveryAware() *MockRecoveryAwareBootloader {
return &MockRecoveryAwareBootloader{MockBootloader: b}
}
// SetRecoverySystemEnv sets the recovery system environment bootloader
// variables; part of RecoveryAwareBootloader.
func (b *MockRecoveryAwareMixin) SetRecoverySystemEnv(recoverySystemDir string, blVars map[string]string) error {
if recoverySystemDir == "" {
panic("MockBootloader.SetRecoverySystemEnv called without recoverySystemDir")
}
b.RecoverySystemDir = recoverySystemDir
b.RecoverySystemBootVars = blVars
return nil
}
// GetRecoverySystemEnv gets the recovery system environment bootloader
// variables; part of RecoveryAwareBootloader.
func (b *MockRecoveryAwareMixin) GetRecoverySystemEnv(recoverySystemDir, key string) (string, error) {
if recoverySystemDir == "" {
panic("MockBootloader.GetRecoverySystemEnv called without recoverySystemDir")
}
b.RecoverySystemDir = recoverySystemDir
return b.RecoverySystemBootVars[key], nil
}
type ExtractedRecoveryKernelCall struct {
RecoverySystemDir string
S snap.PlaceInfo
}
// MockExtractedRecoveryKernelImageBootloader mocks a bootloader implementing
// the ExtractedRecoveryKernelImage interface.
type MockExtractedRecoveryKernelImageBootloader struct {
*MockBootloader
ExtractRecoveryKernelAssetsCalls []ExtractedRecoveryKernelCall
}
// ExtractedRecoveryKernelImage derives a MockRecoveryAwareBootloader from a base
// MockBootloader.
func (b *MockBootloader) ExtractedRecoveryKernelImage() *MockExtractedRecoveryKernelImageBootloader {
return &MockExtractedRecoveryKernelImageBootloader{MockBootloader: b}
}
// ExtractRecoveryKernelAssets extracts the kernel assets for the provided
// kernel snap into the specified recovery system dir; part of
// RecoveryAwareBootloader.
func (b *MockExtractedRecoveryKernelImageBootloader) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error {
if recoverySystemDir == "" {
panic("MockBootloader.ExtractRecoveryKernelAssets called without recoverySystemDir")
}
b.ExtractRecoveryKernelAssetsCalls = append(
b.ExtractRecoveryKernelAssetsCalls,
ExtractedRecoveryKernelCall{
S: s,
RecoverySystemDir: recoverySystemDir},
)
return nil
}
// MockExtractedRunKernelImageMixin implements the
// ExtractedRunKernelImageBootloader interface.
type MockExtractedRunKernelImageMixin struct {
runKernelImageEnableKernelCalls []snap.PlaceInfo
runKernelImageEnableTryKernelCalls []snap.PlaceInfo
runKernelImageEnabledKernel snap.PlaceInfo
runKernelImageEnabledTryKernel snap.PlaceInfo
runKernelImageMockedErrs map[string]error
runKernelImageMockedNumCalls map[string]int
maybePanic func(name string)
}
// MockExtractedRunKernelImageBootloader mocks a bootloader
// implementing the ExtractedRunKernelImageBootloader interface.
type MockExtractedRunKernelImageBootloader struct {
*MockBootloader
MockExtractedRunKernelImageMixin
}
func (b *MockExtractedRunKernelImageBootloader) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) {
// pick the right implementation
return b.MockExtractedRunKernelImageMixin.SetEnabledKernel(kernel)
}
func (b *MockExtractedRunKernelImageBootloader) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) {
// pick the right implementation
return b.MockExtractedRunKernelImageMixin.SetEnabledTryKernel(kernel)
}
// WithExtractedRunKernelImage derives a MockExtractedRunKernelImageBootloader
// from a base MockBootloader.
func (b *MockBootloader) WithExtractedRunKernelImage() *MockExtractedRunKernelImageBootloader {
return &MockExtractedRunKernelImageBootloader{
MockBootloader: b,
MockExtractedRunKernelImageMixin: MockExtractedRunKernelImageMixin{
runKernelImageMockedErrs: make(map[string]error),
runKernelImageMockedNumCalls: make(map[string]int),
maybePanic: b.maybePanic,
},
}
}
// SetEnabledKernel sets the current kernel "symlink" as returned
// by Kernel(); returns' a restore function to set it back to what it was
// before.
func (b *MockExtractedRunKernelImageMixin) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) {
old := b.runKernelImageEnabledKernel
b.runKernelImageEnabledKernel = kernel
return func() {
b.runKernelImageEnabledKernel = old
}
}
// SetEnabledTryKernel sets the current try-kernel "symlink" as
// returned by TryKernel(). If set to nil, TryKernel()'s second return value
// will be false; returns' a restore function to set it back to what it was
// before.
func (b *MockExtractedRunKernelImageMixin) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) {
old := b.runKernelImageEnabledTryKernel
b.runKernelImageEnabledTryKernel = kernel
return func() {
b.runKernelImageEnabledTryKernel = old
}
}
// SetRunKernelImageFunctionError allows setting an error to be returned for the
// specified function; it returns a restore function to set it back to what it
// was before.
func (b *MockExtractedRunKernelImageMixin) SetRunKernelImageFunctionError(f string, err error) (restore func()) {
// check the function
switch f {
case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel":
old := b.runKernelImageMockedErrs[f]
b.runKernelImageMockedErrs[f] = err
return func() {
b.runKernelImageMockedErrs[f] = old
}
default:
panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock error for", f))
}
}
// GetRunKernelImageFunctionSnapCalls returns which snaps were specified during
// execution, in order of calls, as well as the number of calls for methods that
// don't take a snap to set.
func (b *MockExtractedRunKernelImageMixin) GetRunKernelImageFunctionSnapCalls(f string) ([]snap.PlaceInfo, int) {
switch f {
case "EnableKernel":
l := b.runKernelImageEnableKernelCalls
return l, len(l)
case "EnableTryKernel":
l := b.runKernelImageEnableTryKernelCalls
return l, len(l)
case "Kernel", "TryKernel", "DisableTryKernel":
return nil, b.runKernelImageMockedNumCalls[f]
default:
panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to return snap args for", f))
}
}
// EnableKernel enables the kernel; part of ExtractedRunKernelImageBootloader.
func (b *MockExtractedRunKernelImageMixin) EnableKernel(s snap.PlaceInfo) error {
b.maybePanic("EnableKernel")
b.runKernelImageEnableKernelCalls = append(b.runKernelImageEnableKernelCalls, s)
b.runKernelImageEnabledKernel = s
return b.runKernelImageMockedErrs["EnableKernel"]
}
// EnableTryKernel enables a try-kernel; part of
// ExtractedRunKernelImageBootloader.
func (b *MockExtractedRunKernelImageMixin) EnableTryKernel(s snap.PlaceInfo) error {
b.maybePanic("EnableTryKernel")
b.runKernelImageEnableTryKernelCalls = append(b.runKernelImageEnableTryKernelCalls, s)
b.runKernelImageEnabledTryKernel = s
return b.runKernelImageMockedErrs["EnableTryKernel"]
}
// Kernel returns the current kernel set in the bootloader; part of
// ExtractedRunKernelImageBootloader.
func (b *MockExtractedRunKernelImageMixin) Kernel() (snap.PlaceInfo, error) {
b.maybePanic("Kernel")
b.runKernelImageMockedNumCalls["Kernel"]++
err := b.runKernelImageMockedErrs["Kernel"]
if err != nil {
return nil, err
}
return b.runKernelImageEnabledKernel, nil
}
// TryKernel returns the current kernel set in the bootloader; part of
// ExtractedRunKernelImageBootloader.
func (b *MockExtractedRunKernelImageMixin) TryKernel() (snap.PlaceInfo, error) {
b.maybePanic("TryKernel")
b.runKernelImageMockedNumCalls["TryKernel"]++
err := b.runKernelImageMockedErrs["TryKernel"]
if err != nil {
return nil, err
}
if b.runKernelImageEnabledTryKernel == nil {
return nil, bootloader.ErrNoTryKernelRef
}
return b.runKernelImageEnabledTryKernel, nil
}
// DisableTryKernel removes the current try-kernel "symlink" set in the
// bootloader; part of ExtractedRunKernelImageBootloader.
func (b *MockExtractedRunKernelImageMixin) DisableTryKernel() error {
b.maybePanic("DisableTryKernel")
b.runKernelImageMockedNumCalls["DisableTryKernel"]++
b.runKernelImageEnabledTryKernel = nil
return b.runKernelImageMockedErrs["DisableTryKernel"]
}
// MockTrustedAssetsMixin implements the bootloader.TrustedAssetsBootloader
// interface.
type MockTrustedAssetsMixin struct {
TrustedAssetsMap map[string]string
TrustedAssetsErr error
TrustedAssetsCalls int
RecoveryBootChainList []bootloader.BootFile
RecoveryBootChainErr error
BootChainList []bootloader.BootFile
BootChainErr error
RecoveryBootChainCalls []string
BootChainRunBl []bootloader.Bootloader
BootChainKernelPath []string
UpdateErr error
UpdateCalls int
Updated bool
ManagedAssetsList []string
StaticCommandLine string
CandidateStaticCommandLine string
CommandLineErr error
}
type MockEfiLoadOptionMixin struct {
EfiLoadOptionErr error
EfiLoadOptionDesc string
EfiLoadOptionPath string
EfiLoadOptionData []byte
SeenUpdatedAssets [][]string
}
// MockTrustedAssetsBootloader mocks a bootloader implementing the
// bootloader.TrustedAssetsBootloader interface.
type MockTrustedAssetsBootloader struct {
*MockBootloader
MockTrustedAssetsMixin
}
type MockTrustedAssetsBootloaderWithEfi struct {
*MockBootloader
MockTrustedAssetsMixin
MockEfiLoadOptionMixin
}
func (b *MockBootloader) WithTrustedAssets() *MockTrustedAssetsBootloader {
return &MockTrustedAssetsBootloader{
MockBootloader: b,
}
}
func (b *MockBootloader) WithTrustedAssetsAndEfi() *MockTrustedAssetsBootloaderWithEfi {
return &MockTrustedAssetsBootloaderWithEfi{
MockBootloader: b,
}
}
func (b *MockEfiLoadOptionMixin) ParametersForEfiLoadOption(updatedAssets []string) (string, string, []byte, error) {
b.SeenUpdatedAssets = append(b.SeenUpdatedAssets, updatedAssets)
if b.EfiLoadOptionErr != nil {
return "", "", nil, b.EfiLoadOptionErr
} else {
return b.EfiLoadOptionDesc, b.EfiLoadOptionPath, b.EfiLoadOptionData, nil
}
}
func (b *MockTrustedAssetsMixin) ManagedAssets() []string {
return b.ManagedAssetsList
}
func (b *MockTrustedAssetsMixin) UpdateBootConfig() (bool, error) {
b.UpdateCalls++
return b.Updated, b.UpdateErr
}
func glueCommandLine(pieces bootloader.CommandLineComponents, staticArgs string) (string, error) {
if err := pieces.Validate(); err != nil {
return "", err
}
args := []string(nil)
extraOrFull := []string{staticArgs, pieces.ExtraArgs}
if pieces.FullArgs != "" {
extraOrFull = []string{pieces.FullArgs}
}
for _, argSet := range append([]string{pieces.ModeArg, pieces.SystemArg}, extraOrFull...) {
if argSet != "" {
args = append(args, argSet)
}
}
line := strings.Join(args, " ")
return strings.TrimSpace(line), nil
}
func (b *MockTrustedAssetsMixin) CommandLine(pieces bootloader.CommandLineComponents) (string, error) {
if b.CommandLineErr != nil {
return "", b.CommandLineErr
}
return glueCommandLine(pieces, b.StaticCommandLine)
}
func (b *MockTrustedAssetsMixin) CandidateCommandLine(pieces bootloader.CommandLineComponents) (string, error) {
if b.CommandLineErr != nil {
return "", b.CommandLineErr
}
return glueCommandLine(pieces, b.CandidateStaticCommandLine)
}
func (b *MockTrustedAssetsMixin) DefaultCommandLine(candidate bool) (string, error) {
if b.CommandLineErr != nil {
return "", b.CommandLineErr
}
if candidate {
return b.CandidateStaticCommandLine, nil
}
return b.StaticCommandLine, nil
}
func (b *MockTrustedAssetsMixin) TrustedAssets() (map[string]string, error) {
b.TrustedAssetsCalls++
return b.TrustedAssetsMap, b.TrustedAssetsErr
}
func (b *MockTrustedAssetsMixin) RecoveryBootChains(kernelPath string) ([][]bootloader.BootFile, error) {
b.RecoveryBootChainCalls = append(b.RecoveryBootChainCalls, kernelPath)
return [][]bootloader.BootFile{b.RecoveryBootChainList}, b.RecoveryBootChainErr
}
func (b *MockTrustedAssetsMixin) BootChains(runBl bootloader.Bootloader, kernelPath string) ([][]bootloader.BootFile, error) {
b.BootChainRunBl = append(b.BootChainRunBl, runBl)
b.BootChainKernelPath = append(b.BootChainKernelPath, kernelPath)
return [][]bootloader.BootFile{b.BootChainList}, b.BootChainErr
}
// MockRecoveryAwareTrustedAssetsBootloader implements the
// bootloader.RecoveryAwareBootloader and bootloader.TrustedAssetsBootloader
// interfaces.
type MockRecoveryAwareTrustedAssetsBootloader struct {
*MockBootloader
MockRecoveryAwareMixin
MockTrustedAssetsMixin
}
func (b *MockBootloader) WithRecoveryAwareTrustedAssets() *MockRecoveryAwareTrustedAssetsBootloader {
return &MockRecoveryAwareTrustedAssetsBootloader{
MockBootloader: b,
}
}
// MockNotScriptableBootloader implements the
// bootloader.NotScriptableBootloader interface.
type MockNotScriptableBootloader struct {
*MockBootloader
}
func (b *MockBootloader) WithNotScriptable() *MockNotScriptableBootloader {
return &MockNotScriptableBootloader{
MockBootloader: b,
}
}
func (b *MockNotScriptableBootloader) SetBootVarsFromInitramfs(values map[string]string) error {
for k, v := range values {
b.BootVars[k] = v
}
return nil
}
// MockExtractedRecoveryKernelNotScriptableBootloader implements the
// bootloader.ExtractedRecoveryKernelImageBootloader interface and
// includes MockNotScriptableBootloader
type MockExtractedRecoveryKernelNotScriptableBootloader struct {
*MockNotScriptableBootloader
ExtractRecoveryKernelAssetsCalls []ExtractedRecoveryKernelCall
}
func (b *MockNotScriptableBootloader) WithExtractedRecoveryKernel() *MockExtractedRecoveryKernelNotScriptableBootloader {
return &MockExtractedRecoveryKernelNotScriptableBootloader{
MockNotScriptableBootloader: b,
}
}
// ExtractRecoveryKernelAssets extracts the kernel assets for the provided
// kernel snap into the specified recovery system dir; part of
// RecoveryAwareBootloader.
func (b *MockExtractedRecoveryKernelNotScriptableBootloader) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error {
if recoverySystemDir == "" {
panic("MockBootloader.ExtractRecoveryKernelAssets called without recoverySystemDir")
}
b.ExtractRecoveryKernelAssetsCalls = append(
b.ExtractRecoveryKernelAssetsCalls,
ExtractedRecoveryKernelCall{
S: s,
RecoverySystemDir: recoverySystemDir},
)
return nil
}
// MockRebootBootloaderMixin implements the bootloader.RebootBootloader
// interface.
type MockRebootBootloaderMixin struct {
RebootArgs string
}
// MockRebootBootloader mocks a bootloader implementing the
// bootloader.RebootBootloader interface.
type MockRebootBootloader struct {
*MockBootloader
MockRebootBootloaderMixin
}
func (b *MockRebootBootloaderMixin) GetRebootArguments() (string, error) {
return b.RebootArgs, nil
}
func (b *MockBootloader) WithRebootBootloader() *MockRebootBootloader {
return &MockRebootBootloader{
MockBootloader: b,
}
}