Skip to content

Commit

Permalink
Merge pull request #10130 from bboozzoo/bboozzoo/uc20-boot-load-cmdli…
Browse files Browse the repository at this point in the history
…ne-from-gadget

boot: load bits of kernel command line from gadget snaps
  • Loading branch information
mvo5 committed Apr 13, 2021
2 parents 41fcb02 + 22c0624 commit 5cf8e21
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 16 deletions.
25 changes: 23 additions & 2 deletions boot/cmdline.go
Expand Up @@ -169,8 +169,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 @@ -258,6 +275,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 @@ -325,7 +344,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
125 changes: 125 additions & 0 deletions boot/cmdline_test.go
Expand Up @@ -220,6 +220,131 @@ version: 1.0
type: gadget
`

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)
}
}
}

func (s *kernelCommandLineSuite) TestBootVarsForGadgetCommandLine(c *C) {
for _, tc := range []struct {
errMsg string
Expand Down
75 changes: 61 additions & 14 deletions boot/seal_test.go
Expand Up @@ -1010,15 +1010,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 @@ -1029,9 +1033,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 @@ -1042,9 +1049,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 @@ -1055,13 +1066,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 @@ -1075,7 +1116,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 @@ -1098,12 +1139,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 5cf8e21

Please sign in to comment.