Skip to content

Commit

Permalink
boot: load extra kernel command line parameters from the gadget
Browse files Browse the repository at this point in the history
Load the kernel command line arguments provided by the gadget if the path to a
gadget snap directory or file is provided.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
  • Loading branch information
bboozzoo committed Apr 9, 2021
1 parent d5fd0a2 commit af2862f
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 16 deletions.
27 changes: 25 additions & 2 deletions boot/cmdline.go
Expand Up @@ -26,8 +26,10 @@ import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap/snapfile"
"github.com/snapcore/snapd/strutil"
)

Expand Down Expand Up @@ -136,8 +138,25 @@ func composeCommandLine(model *asserts.Model, currentOrCandidate int, mode, syst
}
return "", err
}
// TODO:UC20: fetch extra args from gadget
extraArgs := ""
if gadgetDirOrSnapPath != "" {
sf, err := snapfile.Open(gadgetDirOrSnapPath)
if err != nil {
return "", fmt.Errorf("cannot open gadget snap: %v", err)
}
extraOrFull, full, err := gadget.KernelCommandLineFromGadget(sf)
if err != nil && err != gadget.ErrNoKernelCommandline {
return "", fmt.Errorf("cannot use kernel command line from gadget: %v", err)
}
if err == nil {
// gadget provides some part of the kernel command line
if full {
// TODO:UC20: support full command lines
return "", fmt.Errorf("full kernel command line provided by the gadget is not supported yet")
}
extraArgs = extraOrFull
}
}
if currentOrCandidate == currentEdition {
return mbl.CommandLine(modeArg, systemArg, extraArgs)
} else {
Expand Down Expand Up @@ -225,6 +244,8 @@ func observeSuccessfulCommandLineUpdate(m *Modeenv) (*Modeenv, error) {
// expected kernel command line matches the one the system booted with and
// populates modeenv kernel command line list accordingly.
func observeSuccessfulCommandLineCompatBoot(model *asserts.Model, m *Modeenv) (*Modeenv, error) {
// since this is a compatibility scenario, the kernel command line
// arguments would not have come from the gadget before either
cmdlineExpected, err := ComposeCommandLine(model, "")
if err != nil {
return nil, err
Expand Down Expand Up @@ -292,7 +313,9 @@ func kernelCommandLinesForResealWithFallback(model *asserts.Model, modeenv *Mode
return modeenv.CurrentKernelCommandLines, nil
}
// fallback for when reseal is called before mark boot successful set a
// default during snapd update
// default during snapd update, since this is a compatibility scenario
// there would be no kernel command lines arguments coming from the
// gadget either
cmdline, err := ComposeCommandLine(model, "")
if err != nil {
return nil, err
Expand Down
126 changes: 126 additions & 0 deletions boot/cmdline_test.go
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
)

Expand Down Expand Up @@ -213,3 +214,128 @@ func (s *kernelCommandLineSuite) TestComposeCandidateRecoveryCommandLineManagedH
c.Assert(err, ErrorMatches, "internal error: system is unset")
c.Check(cmdline, Equals, "")
}

func (s *kernelCommandLineSuite) TestComposeCommandLineWithGadget(c *C) {
model := boottest.MakeMockUC20Model()

tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
bootloader.Force(tbl)
defer bootloader.Force(nil)

tbl.StaticCommandLine = "panic=-1"
tbl.CandidateStaticCommandLine = "candidate panic=0"

for _, tc := range []struct {
which string
files [][]string
expCommandLine string
errMsg string
}{{
which: "current",
files: [][]string{
{"cmdline.extra", "cmdline extra"},
},
expCommandLine: "snapd_recovery_mode=run panic=-1 cmdline extra",
}, {
which: "candidate",
files: [][]string{
{"cmdline.extra", "cmdline extra"},
},
expCommandLine: "snapd_recovery_mode=run candidate panic=0 cmdline extra",
}, {
which: "current",
files: [][]string{
{"cmdline.full", "cmdline full"},
},
errMsg: "full kernel command line provided by the gadget is not supported yet",
}, {
which: "candidate",
files: [][]string{
{"cmdline.extra", `bad-quote="`},
},
errMsg: `cannot use kernel command line from gadget: invalid kernel command line "bad-quote=\\"" in cmdline.extra: unbalanced quoting`,
}} {
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
{"meta/snap.yaml", gadgetSnapYaml},
}, tc.files...))
var cmdline string
var err error
switch tc.which {
case "current":
cmdline, err = boot.ComposeCommandLine(model, sf)
case "candidate":
cmdline, err = boot.ComposeCandidateCommandLine(model, sf)
default:
c.Fatalf("unexpected command line type")
}
if tc.errMsg == "" {
c.Assert(err, IsNil)
c.Assert(cmdline, Equals, tc.expCommandLine)
} else {
c.Assert(err, ErrorMatches, tc.errMsg)
}
}
}

func (s *kernelCommandLineSuite) TestComposeRecoveryCommandLineWithGadget(c *C) {
model := boottest.MakeMockUC20Model()

tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
bootloader.Force(tbl)
defer bootloader.Force(nil)

tbl.StaticCommandLine = "panic=-1"
tbl.CandidateStaticCommandLine = "candidate panic=0"
system := "1234"

for _, tc := range []struct {
which string
files [][]string
expCommandLine string
errMsg string
}{{
which: "current",
files: [][]string{
{"cmdline.extra", "cmdline extra"},
},
expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 panic=-1 cmdline extra",
}, {
which: "candidate",
files: [][]string{
{"cmdline.extra", "cmdline extra"},
},
expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0 cmdline extra",
}, {
which: "current",
files: [][]string{
{"cmdline.full", "cmdline full"},
},
errMsg: "full kernel command line provided by the gadget is not supported yet",
}, {
which: "candidate",
files: [][]string{
{"cmdline.extra", `bad-quote="`},
},
errMsg: `cannot use kernel command line from gadget: invalid kernel command line "bad-quote=\\"" in cmdline.extra: unbalanced quoting`,
}} {
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
{"meta/snap.yaml", gadgetSnapYaml},
}, tc.files...))
var cmdline string
var err error
switch tc.which {
case "current":
cmdline, err = boot.ComposeRecoveryCommandLine(model, system, sf)
case "candidate":
cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, system, sf)
default:
c.Fatalf("unexpected command line type")
}
if tc.errMsg == "" {
c.Assert(err, IsNil)
c.Assert(cmdline, Equals, tc.expCommandLine)
} else {
c.Assert(err, ErrorMatches, tc.errMsg)
}
}
}
75 changes: 61 additions & 14 deletions boot/seal_test.go
Expand Up @@ -1015,15 +1015,19 @@ func (s *sealSuite) TestResealKeyToModeenvFallbackCmdline(c *C) {

func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
for _, tc := range []struct {
assetsMap boot.BootAssetsMap
recoverySystems []string
undefinedKernel bool
expectedAssets []boot.BootAsset
expectedKernelRevs []int
err string
desc string
assetsMap boot.BootAssetsMap
recoverySystems []string
undefinedKernel bool
gadgetFilesForSystem map[string][][]string
expectedAssets []boot.BootAsset
expectedKernelRevs []int
// in the order of boot chains
expectedCmdlines [][]string
err string
}{
{
// transition sequences
desc: "transition sequences",
recoverySystems: []string{"20200825"},
assetsMap: boot.BootAssetsMap{
"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
Expand All @@ -1034,9 +1038,12 @@ func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
},
expectedKernelRevs: []int{1},
expectedCmdlines: [][]string{
{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
},
},
{
// two systems
desc: "two systems",
recoverySystems: []string{"20200825", "20200831"},
assetsMap: boot.BootAssetsMap{
"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
Expand All @@ -1047,9 +1054,13 @@ func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
},
expectedKernelRevs: []int{1, 3},
expectedCmdlines: [][]string{
{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1"},
},
},
{
// non-transition sequence
desc: "non transition sequence",
recoverySystems: []string{"20200825"},
assetsMap: boot.BootAssetsMap{
"grubx64.efi": []string{"grub-hash-1"},
Expand All @@ -1060,13 +1071,43 @@ func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1"}},
},
expectedKernelRevs: []int{1},
expectedCmdlines: [][]string{
{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"},
},
},
{
desc: "two systems with command lines",
recoverySystems: []string{"20200825", "20200831"},
assetsMap: boot.BootAssetsMap{
"grubx64.efi": []string{"grub-hash-1", "grub-hash-2"},
"bootx64.efi": []string{"shim-hash-1"},
},
expectedAssets: []boot.BootAsset{
{Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}},
{Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}},
},
gadgetFilesForSystem: map[string][][]string{
"20200825": {
{"cmdline.extra", "extra for 20200825"},
},
"20200831": {
// TODO: make it a cmdline.full
{"cmdline.extra", "some-extra-for-20200831"},
},
},
expectedKernelRevs: []int{1, 3},
expectedCmdlines: [][]string{
{"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1 extra for 20200825"},
{"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1 some-extra-for-20200831"},
},
},
{
// invalid recovery system label
desc: "invalid recovery system label",
recoverySystems: []string{"0"},
err: `cannot read system "0" seed: invalid system seed`,
},
} {
c.Logf("tc: %q", tc.desc)
rootdir := c.MkDir()
dirs.SetRootDir(rootdir)
defer dirs.SetRootDir("")
Expand All @@ -1080,7 +1121,7 @@ func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
if label == "20200831" {
kernelRev = 3
}
return nil, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil
return nil, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, tc.gadgetFilesForSystem[label])}, nil
})
defer restore()

Expand All @@ -1103,12 +1144,18 @@ func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) {
if tc.err == "" {
c.Assert(err, IsNil)
c.Assert(bc, HasLen, len(tc.recoverySystems))
c.Assert(tc.expectedCmdlines, HasLen, len(bc), Commentf("broken test, expected command lines must be of the same length as recovery systems and recovery boot chains"))
for i, chain := range bc {
c.Assert(chain.AssetChain, DeepEquals, tc.expectedAssets)
c.Check(chain.Kernel, Equals, "pc-kernel")
c.Assert(chain.Kernel, Equals, "pc-kernel")
expectedKernelRev := tc.expectedKernelRevs[i]
c.Check(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev))
c.Check(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev), Path: "kernel.efi", Role: bootloader.RoleRecovery})
c.Assert(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev))
c.Assert(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{
Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev),
Path: "kernel.efi",
Role: bootloader.RoleRecovery,
})
c.Assert(chain.KernelCmdlines, DeepEquals, tc.expectedCmdlines[i])
}
} else {
c.Assert(err, ErrorMatches, tc.err)
Expand Down

0 comments on commit af2862f

Please sign in to comment.