mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
* 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>
483 lines
16 KiB
Go
483 lines
16 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 bootloader
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/snapcore/snapd/bootloader/assets"
|
|
"github.com/snapcore/snapd/dirs"
|
|
"github.com/snapcore/snapd/osutil"
|
|
"github.com/snapcore/snapd/osutil/kcmdline"
|
|
"github.com/snapcore/snapd/snap"
|
|
)
|
|
|
|
var (
|
|
// ErrBootloader is returned if the bootloader can not be determined.
|
|
ErrBootloader = errors.New("cannot determine bootloader")
|
|
|
|
// ErrNoTryKernelRef is returned if the bootloader finds no enabled
|
|
// try-kernel.
|
|
ErrNoTryKernelRef = errors.New("no try-kernel referenced")
|
|
|
|
// ErrNoBootChainFound is returned by ParametersForEfiLoadOption if no valid bootchain was found
|
|
ErrNoBootChainFound = errors.New("no valid bootchain found")
|
|
)
|
|
|
|
// Role indicates whether the bootloader is used for recovery or run mode.
|
|
type Role string
|
|
|
|
const (
|
|
// RoleSole applies to the sole bootloader used by UC16/18.
|
|
RoleSole Role = ""
|
|
// RoleRunMode applies to the run mode booloader.
|
|
RoleRunMode Role = "run-mode"
|
|
// RoleRecovery apllies to the recovery bootloader.
|
|
RoleRecovery Role = "recovery"
|
|
)
|
|
|
|
// Options carries bootloader options.
|
|
type Options struct {
|
|
// PrepareImageTime indicates whether the booloader is being
|
|
// used at prepare-image time, that means not on a runtime
|
|
// system.
|
|
PrepareImageTime bool `json:"prepare-image-time,omitempty"`
|
|
|
|
// Role specifies to use the bootloader for the given role.
|
|
Role Role `json:"role,omitempty"`
|
|
|
|
// NoSlashBoot indicates to use the native layout of the
|
|
// bootloader partition and not the /boot mount.
|
|
// It applies only for RoleRunMode.
|
|
// It is implied and ignored for RoleRecovery.
|
|
// It is an error to set it for RoleSole.
|
|
NoSlashBoot bool `json:"no-slash-boot,omitempty"`
|
|
}
|
|
|
|
func (o *Options) validate() error {
|
|
if o == nil {
|
|
return nil
|
|
}
|
|
if o.NoSlashBoot && o.Role == RoleSole {
|
|
return fmt.Errorf("internal error: bootloader.RoleSole doesn't expect NoSlashBoot set")
|
|
}
|
|
if o.PrepareImageTime && o.Role == RoleRunMode {
|
|
return fmt.Errorf("internal error: cannot use run mode bootloader at prepare-image time")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bootloader provides an interface to interact with the system
|
|
// bootloader.
|
|
type Bootloader interface {
|
|
// Return the value of the specified bootloader variable.
|
|
GetBootVars(names ...string) (map[string]string, error)
|
|
|
|
// Set the value of the specified bootloader variable.
|
|
SetBootVars(values map[string]string) error
|
|
|
|
// Name returns the bootloader name.
|
|
Name() string
|
|
|
|
// Present returns whether the bootloader is currently present on the
|
|
// system - in other words whether this bootloader has been installed to the
|
|
// current system. Implementations should only return non-nil error if they
|
|
// can positively identify that the bootloader is installed, but there is
|
|
// actually an error with the installation.
|
|
Present() (bool, error)
|
|
|
|
// InstallBootConfig will try to install the boot config in the
|
|
// given gadgetDir to rootdir.
|
|
InstallBootConfig(gadgetDir string, opts *Options) error
|
|
|
|
// ExtractKernelAssets extracts kernel assets from the given kernel snap.
|
|
ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error
|
|
|
|
// RemoveKernelAssets removes the assets for the given kernel snap.
|
|
RemoveKernelAssets(s snap.PlaceInfo) error
|
|
}
|
|
|
|
type RecoveryAwareBootloader interface {
|
|
Bootloader
|
|
SetRecoverySystemEnv(recoverySystemDir string, values map[string]string) error
|
|
GetRecoverySystemEnv(recoverySystemDir string, key string) (string, error)
|
|
}
|
|
|
|
type ExtractedRecoveryKernelImageBootloader interface {
|
|
Bootloader
|
|
ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error
|
|
}
|
|
|
|
// ExtractedRunKernelImageBootloader is a Bootloader that also supports specific
|
|
// methods needed to setup booting from an extracted kernel, which is needed to
|
|
// implement encryption and/or secure boot. Prototypical implementation is UC20
|
|
// grub implementation with FDE.
|
|
type ExtractedRunKernelImageBootloader interface {
|
|
Bootloader
|
|
|
|
// EnableKernel enables the specified kernel on ubuntu-boot to be used
|
|
// during normal boots. The specified kernel should already have been
|
|
// extracted. This is usually implemented with a "kernel.efi" symlink
|
|
// pointing to the extracted kernel image.
|
|
EnableKernel(snap.PlaceInfo) error
|
|
|
|
// EnableTryKernel enables the specified kernel on ubuntu-boot to be
|
|
// tried by the bootloader on a reboot, to be used in conjunction with
|
|
// setting "kernel_status" to "try". The specified kernel should already
|
|
// have been extracted. This is usually implemented with a
|
|
// "try-kernel.efi" symlink pointing to the extracted kernel image.
|
|
EnableTryKernel(snap.PlaceInfo) error
|
|
|
|
// Kernel returns the current enabled kernel on the bootloader, not
|
|
// necessarily the kernel that was used to boot the current session, but the
|
|
// kernel that is enabled to boot on "normal" boots.
|
|
// If error is not nil, the first argument shall be non-nil.
|
|
Kernel() (snap.PlaceInfo, error)
|
|
|
|
// TryKernel returns the current enabled try-kernel on the bootloader, if
|
|
// there is no such enabled try-kernel, then ErrNoTryKernelRef is returned.
|
|
// If error is not nil, the first argument shall be non-nil.
|
|
TryKernel() (snap.PlaceInfo, error)
|
|
|
|
// DisableTryKernel disables the current enabled try-kernel on the
|
|
// bootloader, if it exists. It does not need to return an error if the
|
|
// enabled try-kernel does not exist or is in an inconsistent state before
|
|
// disabling it, errors should only be returned when the implementation
|
|
// fails to disable the try-kernel.
|
|
DisableTryKernel() error
|
|
}
|
|
|
|
// ComamndLineComponents carries the components of the kernel command line. The
|
|
// bootloader is expected to combine the provided components, optionally
|
|
// including its built-in static set of arguments, and produce a command line
|
|
// that will be passed to the kernel during boot.
|
|
type CommandLineComponents struct {
|
|
// Argument related to mode selection.
|
|
ModeArg string
|
|
// Argument related to recovery system selection, relevant for given
|
|
// mode argument.
|
|
SystemArg string
|
|
// Extra arguments requested by the system.
|
|
ExtraArgs string
|
|
// A complete set of arguments that overrides both the built-in static
|
|
// set and ExtraArgs. Note that, it is an error if extra and full
|
|
// arguments are non-empty.
|
|
FullArgs string
|
|
// A list of patterns to remove arguments from the default command line
|
|
RemoveArgs []kcmdline.ArgumentPattern
|
|
}
|
|
|
|
func (c *CommandLineComponents) Validate() error {
|
|
if c.ExtraArgs != "" && c.FullArgs != "" {
|
|
return fmt.Errorf("cannot use both full and extra components of command line")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TrustedAssetsBootloader has boot assets that take part in the secure boot
|
|
// process and need to be tracked, while other boot assets (typically boot
|
|
// config) are managed by snapd.
|
|
type TrustedAssetsBootloader interface {
|
|
Bootloader
|
|
|
|
// ManagedAssets returns a list of boot assets managed by the bootloader
|
|
// in the boot filesystem. Does not require rootdir to be set.
|
|
ManagedAssets() []string
|
|
// UpdateBootConfig attempts to update the boot config assets used by
|
|
// the bootloader. Returns true when assets were updated.
|
|
UpdateBootConfig() (bool, error)
|
|
// CommandLine returns the kernel command line composed of mode and
|
|
// system arguments, followed by either a built-in bootloader specific
|
|
// static arguments corresponding to the on-disk boot asset edition, and
|
|
// any extra arguments or a separate set of arguments provided in the
|
|
// components. The command line may be different when using a recovery
|
|
// bootloader.
|
|
CommandLine(pieces CommandLineComponents) (string, error)
|
|
// CandidateCommandLine is similar to CommandLine, but uses the current
|
|
// edition of managed built-in boot assets as reference.
|
|
CandidateCommandLine(pieces CommandLineComponents) (string, error)
|
|
|
|
// DefaultCommandLine returns the default kernel command-line
|
|
// used by the bootloader excluding the recovery mode and
|
|
// system parameters.
|
|
DefaultCommandLine(candidate bool) (string, error)
|
|
|
|
// TrustedAssets returns a map of relative paths to asset
|
|
// identifers. The paths are inside the bootloader's rootdir
|
|
// that are measured in the boot process. The asset
|
|
// identifiers correspond to the backward compatible names
|
|
// recorded in the modeenv (CurrentTrustedBootAssets and
|
|
// CurrentTrustedRecoveryBootAssets).
|
|
TrustedAssets() (map[string]string, error)
|
|
|
|
// RecoveryBootChains returns the possible load chains for
|
|
// recovery modes. It should be called on a RoleRecovery
|
|
// bootloader.
|
|
RecoveryBootChains(kernelPath string) ([][]BootFile, error)
|
|
|
|
// BootChains returns the possible load chains for run mode.
|
|
// It should be called on a RoleRecovery bootloader passing
|
|
// the RoleRunMode bootloader.
|
|
BootChains(runBl Bootloader, kernelPath string) ([][]BootFile, error)
|
|
}
|
|
|
|
// NotScriptableBootloader cannot change the bootloader environment
|
|
// because it supports no scripting or cannot do any writes. This
|
|
// applies to piboot for the moment.
|
|
type NotScriptableBootloader interface {
|
|
Bootloader
|
|
|
|
// Sets boot variables from initramfs - this is needed in
|
|
// addition to SetBootVars() to prevent side effects like
|
|
// re-writing the bootloader configuration.
|
|
SetBootVarsFromInitramfs(values map[string]string) error
|
|
}
|
|
|
|
// RebootBootloader needs arguments to the reboot syscall when snaps
|
|
// are being updated.
|
|
type RebootBootloader interface {
|
|
Bootloader
|
|
|
|
// GetRebootArguments returns the needed reboot arguments
|
|
GetRebootArguments() (string, error)
|
|
}
|
|
|
|
// UefiBootloader provides data for setting EFI boot variables.
|
|
type UefiBootloader interface {
|
|
Bootloader
|
|
|
|
// ParametersForEfiLoadOption returns the data which may be used to construct
|
|
// an EFI load option.
|
|
ParametersForEfiLoadOption(updatedAssets []string) (description string, assetPath string, optionalData []byte, err error)
|
|
}
|
|
|
|
func genericInstallBootConfig(gadgetFile, systemFile string) error {
|
|
if err := os.MkdirAll(filepath.Dir(systemFile), 0755); err != nil {
|
|
return err
|
|
}
|
|
return osutil.CopyFile(gadgetFile, systemFile, osutil.CopyFlagOverwrite)
|
|
}
|
|
|
|
func genericSetBootConfigFromAsset(systemFile, assetName string) error {
|
|
bootConfig := assets.Internal(assetName)
|
|
if bootConfig == nil {
|
|
return fmt.Errorf("internal error: no boot asset for %q", assetName)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(systemFile), 0755); err != nil {
|
|
return err
|
|
}
|
|
return osutil.AtomicWriteFile(systemFile, bootConfig, 0644, 0)
|
|
}
|
|
|
|
func genericUpdateBootConfigFromAssets(systemFile string, assetName string) (updated bool, err error) {
|
|
currentBootConfigEdition, err := editionFromDiskConfigAsset(systemFile)
|
|
if err != nil && err != errNoEdition {
|
|
return false, err
|
|
}
|
|
if err == errNoEdition {
|
|
return false, nil
|
|
}
|
|
newBootConfig := assets.Internal(assetName)
|
|
if len(newBootConfig) == 0 {
|
|
return false, fmt.Errorf("no boot config asset with name %q", assetName)
|
|
}
|
|
bc, err := configAssetFrom(newBootConfig)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if bc.Edition() <= currentBootConfigEdition {
|
|
// edition of the candidate boot config is lower than or equal
|
|
// to one currently installed
|
|
return false, nil
|
|
}
|
|
if err := osutil.AtomicWriteFile(systemFile, bc.Raw(), 0644, 0); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// InstallBootConfig installs the bootloader config from the gadget
|
|
// snap dir into the right place.
|
|
func InstallBootConfig(gadgetDir, rootDir string, opts *Options) error {
|
|
if err := opts.validate(); err != nil {
|
|
return err
|
|
}
|
|
bl, err := ForGadget(gadgetDir, rootDir, opts)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find boot config in %q", gadgetDir)
|
|
}
|
|
return bl.InstallBootConfig(gadgetDir, opts)
|
|
}
|
|
|
|
type bootloaderNewFunc func(rootdir string, opts *Options) Bootloader
|
|
|
|
var (
|
|
// bootloaders list all possible bootloaders by their constructor
|
|
// function.
|
|
bootloaders = []bootloaderNewFunc{
|
|
newUboot,
|
|
newGrub,
|
|
newAndroidBoot,
|
|
newLk,
|
|
newPiboot,
|
|
}
|
|
)
|
|
|
|
var (
|
|
forcedBootloader Bootloader
|
|
forcedError error
|
|
)
|
|
|
|
// Find returns the bootloader for the system
|
|
// or an error if no bootloader is found.
|
|
//
|
|
// The rootdir option is useful for image creation operations. It
|
|
// can also be used to find the recovery bootloader, e.g. on uc20:
|
|
//
|
|
// bootloader.Find("/run/mnt/ubuntu-seed")
|
|
func Find(rootdir string, opts *Options) (Bootloader, error) {
|
|
if err := opts.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if forcedBootloader != nil || forcedError != nil {
|
|
return forcedBootloader, forcedError
|
|
}
|
|
|
|
if rootdir == "" {
|
|
rootdir = dirs.GlobalRootDir
|
|
}
|
|
if opts == nil {
|
|
opts = &Options{}
|
|
}
|
|
|
|
// note that the order of this is not deterministic
|
|
for _, blNew := range bootloaders {
|
|
bl := blNew(rootdir, opts)
|
|
present, err := bl.Present()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bootloader %q found but not usable: %v", bl.Name(), err)
|
|
}
|
|
if present {
|
|
return bl, nil
|
|
}
|
|
}
|
|
// no, weeeee
|
|
return nil, ErrBootloader
|
|
}
|
|
|
|
// Force can be used to force Find to always find the specified bootloader; use
|
|
// nil to reset to normal lookup.
|
|
func Force(booloader Bootloader) {
|
|
forcedBootloader = booloader
|
|
forcedError = nil
|
|
}
|
|
|
|
// ForceError can be used to force Find to return an error; use nil to
|
|
// reset to normal lookup.
|
|
func ForceError(err error) {
|
|
forcedBootloader = nil
|
|
forcedError = err
|
|
}
|
|
|
|
func extractKernelAssetsToBootDir(dstDir string, snapf snap.Container, assets []string) error {
|
|
// now do the kernel specific bits
|
|
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
dir, err := os.Open(dstDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dir.Close()
|
|
|
|
for _, src := range assets {
|
|
if err := snapf.Unpack(src, dstDir); err != nil {
|
|
return err
|
|
}
|
|
if err := dir.Sync(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func removeKernelAssetsFromBootDir(bootDir string, s snap.PlaceInfo) error {
|
|
// remove the kernel blob
|
|
blobName := s.Filename()
|
|
dstDir := filepath.Join(bootDir, blobName)
|
|
if err := os.RemoveAll(dstDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ForGadget returns a bootloader matching a given gadget by inspecting the
|
|
// contents of gadget directory or an error if no matching bootloader is found.
|
|
func ForGadget(gadgetDir, rootDir string, opts *Options) (Bootloader, error) {
|
|
if err := opts.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if forcedBootloader != nil || forcedError != nil {
|
|
return forcedBootloader, forcedError
|
|
}
|
|
for _, blNew := range bootloaders {
|
|
bl := blNew(rootDir, opts)
|
|
markerConf := filepath.Join(gadgetDir, bl.Name()+".conf")
|
|
// do we have a marker file?
|
|
if osutil.FileExists(markerConf) {
|
|
return bl, nil
|
|
}
|
|
}
|
|
return nil, ErrBootloader
|
|
}
|
|
|
|
// BootFile represents each file in the chains of trusted assets and
|
|
// kernels used in the boot process. For example a boot file can be an
|
|
// EFI binary or a snap file containing an EFI binary.
|
|
type BootFile struct {
|
|
// Path is the path to the file in the filesystem or, if Snap
|
|
// is set, the relative path inside the snap file.
|
|
Path string
|
|
// Snap contains the path to the snap file if a snap file is used.
|
|
Snap string
|
|
// Role is set to the role of the bootloader this boot file
|
|
// originates from.
|
|
Role Role
|
|
}
|
|
|
|
func NewBootFile(snap, path string, role Role) BootFile {
|
|
return BootFile{
|
|
Snap: snap,
|
|
Path: path,
|
|
Role: role,
|
|
}
|
|
}
|
|
|
|
// WithPath returns a copy of the BootFile with path updated to the
|
|
// specified value.
|
|
func (b BootFile) WithPath(path string) BootFile {
|
|
b.Path = path
|
|
return b
|
|
}
|