Files
snapd/bootloader/bootloader_test.go

367 lines
12 KiB
Go
Raw Permalink Normal View History

2015-05-15 09:33:27 -04:00
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2014-2015 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/>.
*
*/
2015-03-26 10:12:58 +01:00
package bootloader_test
2015-01-23 22:21:41 +01:00
import (
"errors"
"fmt"
"os"
"path/filepath"
2015-01-23 22:21:41 +01:00
"testing"
2015-06-02 17:46:07 -03:00
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/assets"
"github.com/snapcore/snapd/bootloader/bootloadertest"
bootloader/lk: add support for UC20 lk bootloader with V2 lkenv structs (#9695) This commit adds support for the UC20 LittleKernel bootloader to snapd. The big changes for using V2 are that we now use a secure bootloader set kernel command line parameter to identify what disk to look for bootloader environment structures, and that we use different lkenv structs for different role bootloaders. Eventually we should make V1 also use a secure bootloader set kernel command line parameter to identify what disk to look for bootloader environment structures on too, as using the partition label like this is vulnerable to attack by attaching a USB disk with the same partition label to the system. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk{,env}: use xerrors to make Load() err observable as ErrNotExist Previously we would never continue in SetBootVars if the file doesn't exist because we used fmt.Errorf which does not preserve the error type such that os.IsNotExist would return true. Now, we can continue to provide a customized error message from Load(), as well as make SetBootVars continue and just write out a new file even if we can't read the lkenv file in Load(). Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * many: rename disks.FindMatching... to FindMatching...WithFsLabel and err types This is to accommodate a future FindMatching...WithPartLabel. Additionally, an internal refactor to support storing additional properties for partitions when we search a disk is implemented to allow for the additional future method. The error type now is also more versatile to allow specifying what part failed, searching for a partition by filesystem label or by partition label. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks: add FindMatchingPartitionUUIDFromPartLabel to Disk iface This will enable searching for a partition with a given label on the provided disk. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: search for partition labels on the bootloader disk, not fs labels The partitions with the bootloader structures we care about do not have filesystems on them, and as such they will not have filesystem labels. Instead, they will have partition labels, and as such we should search using the partition label instead. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: rename inRuntimeMode to prepareImageTime This simplifies numerous functions to be more clear with the simpler prepare-image time implementation happening first, then the complex runtime cases happening afterwards are not indented as much. Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: rename var Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: fix handling of os.ErrNotExist everywhere, add comments We should be using xerrors.Is() everywhere here, since the error returned is wrapped. However, for specifically RemoveKernelAssets, it's not a bug that we would skip over os.ErrNotExist, but it would result in a more confusing error message - we should try to give better error messages, and returning early with os.ErrNotExist is more useful to the user than the existing error around not being able to find the specified kernel blob. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: move prepare-image check earlier in ExtractRecoveryKernelAssets Thanks to @bboozzoo for the recommendation. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: adjust comment about optional partition labels Thanks to @bboozzoo for clarifying this for me. Partition labels are optional on GPT partitions. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks.go: fix typo Thanks to @bboozzoo for catching this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: add some TODOs about things to cleanup/simplify Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: add comments about the encoding of the udev props Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/mockdisk.go: fix typo Thanks to @bboozzoo again for spotting all my typos :-) Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks: add FindMatchingPartitionUUIDFromPartLabel to Disk iface This will enable searching for a partition with a given label on the provided disk. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * many: fix rename of disks pkg function name from rebase Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: don't use l.rootdir for the returned partition file Using l.rootdir here was wrong in that it would produce for RoleRunMode bootloaders paths like /run/mnt/ubuntu-boot/dev/disk/by-partuuid/1234, instead of the correct path of /dev/disk/by-partuuid/... at runtime. We didn't catch this in tests because we pass in the same rootdir as what we test for, we didn't test passing in boot.InitramfsUbuntuBootDir for example, so change the tests to account for this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv: add compatibility error impl for Go 1.9 Go 1.9 os.PathError does not implement Unwrap(), so using xerrors.Is(err,os.ErrNotExist) does not work, as Is() calls Unwrap() in a loop, waiting for either the error returned to be equal to the target error or to be nil, and in the case of Go 1.9, where os.PathError does not implement Unwrap(), then nil is returned and Is() returns false. We can work around this by implementing Unwrap() on our own error type and specifically returning os.ErrNotExist on Go 1.9 when we know the error is actually os.ErrNotExist. This will make the unit tests with Go 1.9 pass. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: use specific backup file for lkenv For UC20 runtime, the backup file will be a different partition, and since we are using partition uuid symlinks instead of partition labels, we can't just append "bak" to the primary env file, and instead need to manually provide the backup file. Also add a test which validates that the backup env file for run mode on uc20 is handled appropriately and updated. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: add test scaffolding and TODO about recovery system add/remove Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: adjust comments Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: change XXX to TODO Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: inspect the err in compatErrNotExist for Unwrap() Instead of checking the version of Go we were compiled with which is weird, we can instead just check for the exact thing we know doesn't work, which is that os.PathError doesn't implement Unwrap(), so if it doesn't implement Unwrap(), we know we are in a legacy version of Go and thus should do the manual unwrapping to return ErrNotExist directly. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: refactor helper methods This refactors some helper methods to be more clear, namely that envFile becomes envBackstore, and it takes an argument for whether or not it is returning the primary or the backup, and eliminating the specific method for getting the backup file, as well as combining some codepaths in the other helpers to be more easily readable if slightly redundant in terms of actual code execution. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: move some function definitions around for conventions' sake Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: adjust comment to explain we should try to use kernel cmdline It might not be there on older gadget bootloaders which don't set that kernel command line value and as such we will have to fallback to our current behavior. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: fix for gofmt 1.9 Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: update handling of os.ErrNotExist for various cases The only case where we can really proceed without having an initial env file is at prepare image time when the bootloader env file doesn't exist, as we could be writing an empty/new one. All other cases we need the file to exist as a pre-requisite, if only for accurate error reporting. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: rename variable, simplify return statement Thanks to @pedronis for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: implement TODO about checking the backup file in Present() Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: re-word awkwardly phrased TODO about using kernel cmdline Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk_test.go: add unit test for Present() RoleSole checking backup Signed-off-by: Ian Johnson <ian.johnson@canonical.com> Co-authored-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
2020-12-04 13:50:28 -06:00
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
2015-01-23 22:21:41 +01:00
)
2015-06-02 17:53:10 -03:00
// Hook up check.v1 into the "go test" runner
2015-01-23 22:21:41 +01:00
func Test(t *testing.T) { TestingT(t) }
const packageKernel = `
name: ubuntu-kernel
version: 4.0-1
type: kernel
vendor: Someone
`
type baseBootenvTestSuite struct {
testutil.BaseTest
rootdir string
}
2015-01-23 22:21:41 +01:00
func (s *baseBootenvTestSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
s.rootdir = c.MkDir()
bootloader/lk: add support for UC20 lk bootloader with V2 lkenv structs (#9695) This commit adds support for the UC20 LittleKernel bootloader to snapd. The big changes for using V2 are that we now use a secure bootloader set kernel command line parameter to identify what disk to look for bootloader environment structures, and that we use different lkenv structs for different role bootloaders. Eventually we should make V1 also use a secure bootloader set kernel command line parameter to identify what disk to look for bootloader environment structures on too, as using the partition label like this is vulnerable to attack by attaching a USB disk with the same partition label to the system. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk{,env}: use xerrors to make Load() err observable as ErrNotExist Previously we would never continue in SetBootVars if the file doesn't exist because we used fmt.Errorf which does not preserve the error type such that os.IsNotExist would return true. Now, we can continue to provide a customized error message from Load(), as well as make SetBootVars continue and just write out a new file even if we can't read the lkenv file in Load(). Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * many: rename disks.FindMatching... to FindMatching...WithFsLabel and err types This is to accommodate a future FindMatching...WithPartLabel. Additionally, an internal refactor to support storing additional properties for partitions when we search a disk is implemented to allow for the additional future method. The error type now is also more versatile to allow specifying what part failed, searching for a partition by filesystem label or by partition label. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks: add FindMatchingPartitionUUIDFromPartLabel to Disk iface This will enable searching for a partition with a given label on the provided disk. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: search for partition labels on the bootloader disk, not fs labels The partitions with the bootloader structures we care about do not have filesystems on them, and as such they will not have filesystem labels. Instead, they will have partition labels, and as such we should search using the partition label instead. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: rename inRuntimeMode to prepareImageTime This simplifies numerous functions to be more clear with the simpler prepare-image time implementation happening first, then the complex runtime cases happening afterwards are not indented as much. Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: rename var Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: fix handling of os.ErrNotExist everywhere, add comments We should be using xerrors.Is() everywhere here, since the error returned is wrapped. However, for specifically RemoveKernelAssets, it's not a bug that we would skip over os.ErrNotExist, but it would result in a more confusing error message - we should try to give better error messages, and returning early with os.ErrNotExist is more useful to the user than the existing error around not being able to find the specified kernel blob. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: move prepare-image check earlier in ExtractRecoveryKernelAssets Thanks to @bboozzoo for the recommendation. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: adjust comment about optional partition labels Thanks to @bboozzoo for clarifying this for me. Partition labels are optional on GPT partitions. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks.go: fix typo Thanks to @bboozzoo for catching this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: add some TODOs about things to cleanup/simplify Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: add comments about the encoding of the udev props Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/mockdisk.go: fix typo Thanks to @bboozzoo again for spotting all my typos :-) Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks: add FindMatchingPartitionUUIDFromPartLabel to Disk iface This will enable searching for a partition with a given label on the provided disk. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * many: fix rename of disks pkg function name from rebase Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: don't use l.rootdir for the returned partition file Using l.rootdir here was wrong in that it would produce for RoleRunMode bootloaders paths like /run/mnt/ubuntu-boot/dev/disk/by-partuuid/1234, instead of the correct path of /dev/disk/by-partuuid/... at runtime. We didn't catch this in tests because we pass in the same rootdir as what we test for, we didn't test passing in boot.InitramfsUbuntuBootDir for example, so change the tests to account for this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv: add compatibility error impl for Go 1.9 Go 1.9 os.PathError does not implement Unwrap(), so using xerrors.Is(err,os.ErrNotExist) does not work, as Is() calls Unwrap() in a loop, waiting for either the error returned to be equal to the target error or to be nil, and in the case of Go 1.9, where os.PathError does not implement Unwrap(), then nil is returned and Is() returns false. We can work around this by implementing Unwrap() on our own error type and specifically returning os.ErrNotExist on Go 1.9 when we know the error is actually os.ErrNotExist. This will make the unit tests with Go 1.9 pass. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: use specific backup file for lkenv For UC20 runtime, the backup file will be a different partition, and since we are using partition uuid symlinks instead of partition labels, we can't just append "bak" to the primary env file, and instead need to manually provide the backup file. Also add a test which validates that the backup env file for run mode on uc20 is handled appropriately and updated. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: add test scaffolding and TODO about recovery system add/remove Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: adjust comments Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: change XXX to TODO Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: inspect the err in compatErrNotExist for Unwrap() Instead of checking the version of Go we were compiled with which is weird, we can instead just check for the exact thing we know doesn't work, which is that os.PathError doesn't implement Unwrap(), so if it doesn't implement Unwrap(), we know we are in a legacy version of Go and thus should do the manual unwrapping to return ErrNotExist directly. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: refactor helper methods This refactors some helper methods to be more clear, namely that envFile becomes envBackstore, and it takes an argument for whether or not it is returning the primary or the backup, and eliminating the specific method for getting the backup file, as well as combining some codepaths in the other helpers to be more easily readable if slightly redundant in terms of actual code execution. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: move some function definitions around for conventions' sake Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: adjust comment to explain we should try to use kernel cmdline It might not be there on older gadget bootloaders which don't set that kernel command line value and as such we will have to fallback to our current behavior. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: fix for gofmt 1.9 Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: update handling of os.ErrNotExist for various cases The only case where we can really proceed without having an initial env file is at prepare image time when the bootloader env file doesn't exist, as we could be writing an empty/new one. All other cases we need the file to exist as a pre-requisite, if only for accurate error reporting. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: rename variable, simplify return statement Thanks to @pedronis for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: implement TODO about checking the backup file in Present() Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: re-word awkwardly phrased TODO about using kernel cmdline Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk_test.go: add unit test for Present() RoleSole checking backup Signed-off-by: Ian Johnson <ian.johnson@canonical.com> Co-authored-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
2020-12-04 13:50:28 -06:00
dirs.SetRootDir(s.rootdir)
s.AddCleanup(func() { dirs.SetRootDir("") })
}
type bootenvTestSuite struct {
baseBootenvTestSuite
b *bootloadertest.MockBootloader
}
2015-01-23 22:21:41 +01:00
var _ = Suite(&bootenvTestSuite{})
2015-01-23 22:21:41 +01:00
func (s *bootenvTestSuite) SetUpTest(c *C) {
s.baseBootenvTestSuite.SetUpTest(c)
s.b = bootloadertest.Mock("mocky", c.MkDir())
}
func (s *bootenvTestSuite) TestForceBootloader(c *C) {
bootloader.Force(s.b)
defer bootloader.Force(nil)
got, err := bootloader.Find("", nil)
c.Assert(err, IsNil)
c.Check(got, Equals, s.b)
}
func (s *bootenvTestSuite) TestForceBootloaderError(c *C) {
myErr := errors.New("zap")
bootloader.ForceError(myErr)
defer bootloader.ForceError(nil)
got, err := bootloader.Find("", nil)
c.Assert(err, Equals, myErr)
c.Check(got, IsNil)
}
func (s *bootenvTestSuite) TestInstallBootloaderConfigNoConfig(c *C) {
err := bootloader.InstallBootConfig(c.MkDir(), s.rootdir, nil)
c.Assert(err, ErrorMatches, `cannot find boot config in.*`)
}
func (s *bootenvTestSuite) TestInstallBootloaderConfigFromGadget(c *C) {
for _, t := range []struct {
name string
gadgetFile, sysFile string
gadgetFileContent []byte
opts *bootloader.Options
}{
{name: "grub", gadgetFile: "grub.conf", sysFile: "/boot/grub/grub.cfg"},
// traditional uboot.env - the uboot.env file needs to be non-empty
{name: "uboot.env", gadgetFile: "uboot.conf", sysFile: "/boot/uboot/uboot.env", gadgetFileContent: []byte{1}},
// boot.scr in place of uboot.env means we create the boot.sel file
{
name: "uboot boot.scr",
gadgetFile: "uboot.conf",
sysFile: "/uboot/ubuntu/boot.sel",
opts: &bootloader.Options{Role: bootloader.RoleRecovery},
},
{name: "androidboot", gadgetFile: "androidboot.conf", sysFile: "/boot/androidboot/androidboot.env"},
bootloader/lk: add support for UC20 lk bootloader with V2 lkenv structs (#9695) This commit adds support for the UC20 LittleKernel bootloader to snapd. The big changes for using V2 are that we now use a secure bootloader set kernel command line parameter to identify what disk to look for bootloader environment structures, and that we use different lkenv structs for different role bootloaders. Eventually we should make V1 also use a secure bootloader set kernel command line parameter to identify what disk to look for bootloader environment structures on too, as using the partition label like this is vulnerable to attack by attaching a USB disk with the same partition label to the system. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk{,env}: use xerrors to make Load() err observable as ErrNotExist Previously we would never continue in SetBootVars if the file doesn't exist because we used fmt.Errorf which does not preserve the error type such that os.IsNotExist would return true. Now, we can continue to provide a customized error message from Load(), as well as make SetBootVars continue and just write out a new file even if we can't read the lkenv file in Load(). Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * many: rename disks.FindMatching... to FindMatching...WithFsLabel and err types This is to accommodate a future FindMatching...WithPartLabel. Additionally, an internal refactor to support storing additional properties for partitions when we search a disk is implemented to allow for the additional future method. The error type now is also more versatile to allow specifying what part failed, searching for a partition by filesystem label or by partition label. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks: add FindMatchingPartitionUUIDFromPartLabel to Disk iface This will enable searching for a partition with a given label on the provided disk. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: search for partition labels on the bootloader disk, not fs labels The partitions with the bootloader structures we care about do not have filesystems on them, and as such they will not have filesystem labels. Instead, they will have partition labels, and as such we should search using the partition label instead. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: rename inRuntimeMode to prepareImageTime This simplifies numerous functions to be more clear with the simpler prepare-image time implementation happening first, then the complex runtime cases happening afterwards are not indented as much. Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: rename var Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: fix handling of os.ErrNotExist everywhere, add comments We should be using xerrors.Is() everywhere here, since the error returned is wrapped. However, for specifically RemoveKernelAssets, it's not a bug that we would skip over os.ErrNotExist, but it would result in a more confusing error message - we should try to give better error messages, and returning early with os.ErrNotExist is more useful to the user than the existing error around not being able to find the specified kernel blob. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: move prepare-image check earlier in ExtractRecoveryKernelAssets Thanks to @bboozzoo for the recommendation. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: adjust comment about optional partition labels Thanks to @bboozzoo for clarifying this for me. Partition labels are optional on GPT partitions. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks.go: fix typo Thanks to @bboozzoo for catching this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: add some TODOs about things to cleanup/simplify Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/disks_linux.go: add comments about the encoding of the udev props Thanks to @bboozzoo for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks/mockdisk.go: fix typo Thanks to @bboozzoo again for spotting all my typos :-) Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * osutil/disks: add FindMatchingPartitionUUIDFromPartLabel to Disk iface This will enable searching for a partition with a given label on the provided disk. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * many: fix rename of disks pkg function name from rebase Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: don't use l.rootdir for the returned partition file Using l.rootdir here was wrong in that it would produce for RoleRunMode bootloaders paths like /run/mnt/ubuntu-boot/dev/disk/by-partuuid/1234, instead of the correct path of /dev/disk/by-partuuid/... at runtime. We didn't catch this in tests because we pass in the same rootdir as what we test for, we didn't test passing in boot.InitramfsUbuntuBootDir for example, so change the tests to account for this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv: add compatibility error impl for Go 1.9 Go 1.9 os.PathError does not implement Unwrap(), so using xerrors.Is(err,os.ErrNotExist) does not work, as Is() calls Unwrap() in a loop, waiting for either the error returned to be equal to the target error or to be nil, and in the case of Go 1.9, where os.PathError does not implement Unwrap(), then nil is returned and Is() returns false. We can work around this by implementing Unwrap() on our own error type and specifically returning os.ErrNotExist on Go 1.9 when we know the error is actually os.ErrNotExist. This will make the unit tests with Go 1.9 pass. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: use specific backup file for lkenv For UC20 runtime, the backup file will be a different partition, and since we are using partition uuid symlinks instead of partition labels, we can't just append "bak" to the primary env file, and instead need to manually provide the backup file. Also add a test which validates that the backup env file for run mode on uc20 is handled appropriately and updated. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: add test scaffolding and TODO about recovery system add/remove Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: adjust comments Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: change XXX to TODO Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: inspect the err in compatErrNotExist for Unwrap() Instead of checking the version of Go we were compiled with which is weird, we can instead just check for the exact thing we know doesn't work, which is that os.PathError doesn't implement Unwrap(), so if it doesn't implement Unwrap(), we know we are in a legacy version of Go and thus should do the manual unwrapping to return ErrNotExist directly. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: refactor helper methods This refactors some helper methods to be more clear, namely that envFile becomes envBackstore, and it takes an argument for whether or not it is returning the primary or the backup, and eliminating the specific method for getting the backup file, as well as combining some codepaths in the other helpers to be more easily readable if slightly redundant in terms of actual code execution. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: move some function definitions around for conventions' sake Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: adjust comment to explain we should try to use kernel cmdline It might not be there on older gadget bootloaders which don't set that kernel command line value and as such we will have to fallback to our current behavior. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lkenv/lkenv.go: fix for gofmt 1.9 Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk: update handling of os.ErrNotExist for various cases The only case where we can really proceed without having an initial env file is at prepare image time when the bootloader env file doesn't exist, as we could be writing an empty/new one. All other cases we need the file to exist as a pre-requisite, if only for accurate error reporting. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: rename variable, simplify return statement Thanks to @pedronis for the suggestion. Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: implement TODO about checking the backup file in Present() Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk.go: re-word awkwardly phrased TODO about using kernel cmdline Signed-off-by: Ian Johnson <ian.johnson@canonical.com> * bootloader/lk_test.go: add unit test for Present() RoleSole checking backup Signed-off-by: Ian Johnson <ian.johnson@canonical.com> Co-authored-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
2020-12-04 13:50:28 -06:00
{name: "lk", gadgetFile: "lk.conf", sysFile: "/boot/lk/snapbootsel.bin", opts: &bootloader.Options{PrepareImageTime: true}},
{
name: "piboot",
gadgetFile: "piboot.conf",
sysFile: "/boot/piboot/piboot.conf",
},
} {
mockGadgetDir := c.MkDir()
rootDir := c.MkDir()
err := os.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), t.gadgetFileContent, 0644)
c.Assert(err, IsNil)
err = bootloader.InstallBootConfig(mockGadgetDir, rootDir, t.opts)
c.Assert(err, IsNil, Commentf("installing boot config for %s", t.name))
fn := filepath.Join(rootDir, t.sysFile)
c.Assert(fn, testutil.FilePresent, Commentf("boot config missing for %s at %s", t.name, t.sysFile))
}
}
func (s *bootenvTestSuite) TestInstallBootloaderConfigFromAssets(c *C) {
recoveryOpts := &bootloader.Options{
Role: bootloader.RoleRecovery,
}
systemBootOpts := &bootloader.Options{
Role: bootloader.RoleRunMode,
}
defaultRecoveryGrubAsset := assets.Internal("grub-recovery.cfg")
c.Assert(defaultRecoveryGrubAsset, NotNil)
defaultGrubAsset := assets.Internal("grub.cfg")
c.Assert(defaultGrubAsset, NotNil)
for _, t := range []struct {
name string
gadgetFile, sysFile string
gadgetFileContent []byte
sysFileContent []byte
assetContent []byte
assetName string
err string
opts *bootloader.Options
}{
{
name: "recovery grub",
opts: recoveryOpts,
gadgetFile: "grub.conf",
// empty file in the gadget
gadgetFileContent: nil,
sysFile: "/EFI/ubuntu/grub.cfg",
assetName: "grub-recovery.cfg",
assetContent: []byte("hello assets"),
// boot config from assets
sysFileContent: []byte("hello assets"),
}, {
name: "recovery grub with non empty gadget file",
opts: recoveryOpts,
gadgetFile: "grub.conf",
gadgetFileContent: []byte("not so empty"),
sysFile: "/EFI/ubuntu/grub.cfg",
assetName: "grub-recovery.cfg",
assetContent: []byte("hello assets"),
// boot config from assets
sysFileContent: []byte("hello assets"),
}, {
name: "recovery grub with default asset",
opts: recoveryOpts,
gadgetFile: "grub.conf",
// empty file in the gadget
gadgetFileContent: nil,
sysFile: "/EFI/ubuntu/grub.cfg",
sysFileContent: defaultRecoveryGrubAsset,
}, {
name: "recovery grub missing asset",
opts: recoveryOpts,
gadgetFile: "grub.conf",
// empty file in the gadget
gadgetFileContent: nil,
sysFile: "/EFI/ubuntu/grub.cfg",
assetName: "grub-recovery.cfg",
// no asset content
err: `internal error: no boot asset for "grub-recovery.cfg"`,
}, {
name: "system-boot grub",
opts: systemBootOpts,
gadgetFile: "grub.conf",
// empty file in the gadget
gadgetFileContent: nil,
sysFile: "/EFI/ubuntu/grub.cfg",
assetName: "grub.cfg",
assetContent: []byte("hello assets"),
sysFileContent: []byte("hello assets"),
}, {
name: "system-boot grub with default asset",
opts: systemBootOpts,
gadgetFile: "grub.conf",
// empty file in the gadget
gadgetFileContent: nil,
sysFile: "/EFI/ubuntu/grub.cfg",
sysFileContent: defaultGrubAsset,
},
} {
mockGadgetDir := c.MkDir()
rootDir := c.MkDir()
fn := filepath.Join(rootDir, t.sysFile)
err := os.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), t.gadgetFileContent, 0644)
c.Assert(err, IsNil)
var restoreAsset func()
if t.assetName != "" {
restoreAsset = assets.MockInternal(t.assetName, t.assetContent)
}
err = bootloader.InstallBootConfig(mockGadgetDir, rootDir, t.opts)
if t.err == "" {
c.Assert(err, IsNil, Commentf("installing boot config for %s", t.name))
// mocked asset content
c.Assert(fn, testutil.FileEquals, string(t.sysFileContent))
} else {
c.Assert(err, ErrorMatches, t.err)
c.Assert(fn, testutil.FileAbsent)
}
if restoreAsset != nil {
restoreAsset()
}
}
}
func (s *bootenvTestSuite) TestBootloaderFindPresentNonNilError(c *C) {
rootdir := c.MkDir()
// add a mock bootloader to the list of bootloaders that Find() uses
mockBl := bootloadertest.Mock("mock", rootdir)
restore := bootloader.MockAddBootloaderToFind(func(dir string, opts *bootloader.Options) bootloader.Bootloader {
c.Assert(dir, Equals, rootdir)
return mockBl
})
defer restore()
// make us find our bootloader
mockBl.MockedPresent = true
bl, err := bootloader.Find(rootdir, nil)
c.Assert(err, IsNil)
c.Assert(bl, NotNil)
c.Assert(bl.Name(), Equals, "mock")
c.Assert(bl, DeepEquals, mockBl)
// now make finding our bootloader a fatal error, this time we will get the
// error back
mockBl.PresentErr = fmt.Errorf("boom")
_, err = bootloader.Find(rootdir, nil)
c.Assert(err, ErrorMatches, "bootloader \"mock\" found but not usable: boom")
}
func (s *bootenvTestSuite) TestBootloaderFindBadOptions(c *C) {
_, err := bootloader.Find("", &bootloader.Options{
PrepareImageTime: true,
Role: bootloader.RoleRunMode,
})
c.Assert(err, ErrorMatches, "internal error: cannot use run mode bootloader at prepare-image time")
_, err = bootloader.Find("", &bootloader.Options{
NoSlashBoot: true,
Role: bootloader.RoleSole,
})
c.Assert(err, ErrorMatches, "internal error: bootloader.RoleSole doesn't expect NoSlashBoot set")
}
func (s *bootenvTestSuite) TestBootloaderFind(c *C) {
for _, tc := range []struct {
name string
sysFile string
opts *bootloader.Options
expName string
}{
{name: "grub", sysFile: "/boot/grub/grub.cfg", expName: "grub"},
{
// native run partition layout
name: "grub", sysFile: "/EFI/ubuntu/grub.cfg",
opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
expName: "grub",
},
{
// recovery layout
name: "grub", sysFile: "/EFI/ubuntu/grub.cfg",
opts: &bootloader.Options{Role: bootloader.RoleRecovery},
expName: "grub",
},
// traditional uboot.env - the uboot.env file needs to be non-empty
{name: "uboot.env", sysFile: "/boot/uboot/uboot.env", expName: "uboot"},
// boot.sel uboot variant
{
name: "uboot boot.scr",
sysFile: "/uboot/ubuntu/boot.sel",
opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
expName: "uboot",
},
{name: "androidboot", sysFile: "/boot/androidboot/androidboot.env", expName: "androidboot"},
// lk is detected differently based on runtime/prepare-image
{name: "lk", sysFile: "/dev/disk/by-partlabel/snapbootsel", expName: "lk"},
{
name: "lk", sysFile: "/boot/lk/snapbootsel.bin",
expName: "lk", opts: &bootloader.Options{PrepareImageTime: true},
},
} {
c.Logf("tc: %v", tc.name)
rootDir := c.MkDir()
err := os.MkdirAll(filepath.Join(rootDir, filepath.Dir(tc.sysFile)), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(filepath.Join(rootDir, tc.sysFile), nil, 0644)
c.Assert(err, IsNil)
bl, err := bootloader.Find(rootDir, tc.opts)
c.Assert(err, IsNil)
c.Assert(bl, NotNil)
c.Check(bl.Name(), Equals, tc.expName)
}
}
func (s *bootenvTestSuite) TestBootloaderForGadget(c *C) {
for _, tc := range []struct {
name string
gadgetFile string
opts *bootloader.Options
expName string
}{
{name: "grub", gadgetFile: "grub.conf", expName: "grub"},
{name: "grub", gadgetFile: "grub.conf", opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}, expName: "grub"},
{name: "grub", gadgetFile: "grub.conf", opts: &bootloader.Options{Role: bootloader.RoleRecovery}, expName: "grub"},
{name: "uboot", gadgetFile: "uboot.conf", expName: "uboot"},
{name: "androidboot", gadgetFile: "androidboot.conf", expName: "androidboot"},
{name: "lk", gadgetFile: "lk.conf", expName: "lk"},
} {
c.Logf("tc: %v", tc.name)
gadgetDir := c.MkDir()
rootDir := c.MkDir()
err := os.MkdirAll(filepath.Join(rootDir, filepath.Dir(tc.gadgetFile)), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(filepath.Join(gadgetDir, tc.gadgetFile), nil, 0644)
c.Assert(err, IsNil)
bl, err := bootloader.ForGadget(gadgetDir, rootDir, tc.opts)
c.Assert(err, IsNil)
c.Assert(bl, NotNil)
c.Check(bl.Name(), Equals, tc.expName)
}
}
func (s *bootenvTestSuite) TestBootFileWithPath(c *C) {
a := bootloader.NewBootFile("", "some/path", bootloader.RoleRunMode)
b := a.WithPath("other/path")
c.Assert(a.Path, Equals, "some/path")
c.Assert(b.Path, Equals, "other/path")
}