mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
bootloader/grub: implement composing of grub command line
The patch implements a helper for composing the kernel command line when using a grub bootloader. The command line is composed of 3 main elements, the static part defined in the boot asset, plus extra arguments (usually set by snapd in the boot environment) and mode arguments Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
This commit is contained in:
@@ -104,7 +104,9 @@ func LkRuntimeMode(b Bootloader) bool {
|
||||
}
|
||||
|
||||
var (
|
||||
EditionFromDiskConfigAsset = editionFromDiskConfigAsset
|
||||
EditionFromConfigAsset = editionFromConfigAsset
|
||||
ConfigAssetFrom = configAssetFrom
|
||||
EditionFromDiskConfigAsset = editionFromDiskConfigAsset
|
||||
EditionFromConfigAsset = editionFromConfigAsset
|
||||
ConfigAssetFrom = configAssetFrom
|
||||
StaticCommandLineFromGrubAsset = staticCommandLineFromGrubAsset
|
||||
SortSnapdKernelCommandLineArgsForGrub = sortSnapdKernelCommandLineArgsForGrub
|
||||
)
|
||||
|
||||
@@ -20,13 +20,18 @@
|
||||
package bootloader
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/snapcore/snapd/bootloader/assets"
|
||||
"github.com/snapcore/snapd/bootloader/grubenv"
|
||||
"github.com/snapcore/snapd/osutil"
|
||||
"github.com/snapcore/snapd/snap"
|
||||
"github.com/snapcore/snapd/strutil"
|
||||
)
|
||||
|
||||
// sanity - grub implements the required interfaces
|
||||
@@ -388,5 +393,91 @@ func (g *grub) ManagedAssets() []string {
|
||||
//
|
||||
// Implements ManagedAssetsBootloader for the grub bootloader.
|
||||
func (g *grub) CommandLine(modeArgs, extraArgs string) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
// we do not trust the on disk asset, use the built-in one
|
||||
assetName := "grub.cfg"
|
||||
if g.recovery {
|
||||
assetName = "grub-recovery.cfg"
|
||||
}
|
||||
staticCmdline, err := staticCommandLineFromGrubAsset(assetName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot extract static command line element: %v", err)
|
||||
}
|
||||
args, err := strutil.KernelCommandLineSplit(modeArgs + " " + staticCmdline + " " + extraArgs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot use badly formatted kernel command line: %v", err)
|
||||
}
|
||||
// sort arguments so that they match their positions
|
||||
args = sortSnapdKernelCommandLineArgsForGrub(args)
|
||||
// join all argument with a single space, see
|
||||
// grub-core/lib/cmdline.c:grub_create_loader_cmdline() for reference,
|
||||
// arguments are separated by a single space, the space after last is
|
||||
// replaced with terminating NULL
|
||||
return strings.Join(args, " "), nil
|
||||
}
|
||||
|
||||
// static command line is defined as:
|
||||
// set snapd_static_cmdline_args='arg arg arg'\n
|
||||
// or
|
||||
// set snapd_static_cmdline_args='arg'\n
|
||||
const grubStaticCmdlinePrefix = `set snapd_static_cmdline_args=`
|
||||
const grubStaticCmdlineQuote = `'`
|
||||
|
||||
// staticCommandLineFromGrubAsset extracts the static command line element from
|
||||
// grub boot config asset on disk.
|
||||
func staticCommandLineFromGrubAsset(asset string) (string, error) {
|
||||
gbc := assets.Internal(asset)
|
||||
if gbc == nil {
|
||||
return "", fmt.Errorf("internal error: asset %q not found", asset)
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(gbc))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, grubStaticCmdlinePrefix) {
|
||||
continue
|
||||
}
|
||||
// minimal length is the prefix + suffix with no content
|
||||
minLength := len(grubStaticCmdlinePrefix) + len(grubStaticCmdlineQuote)*2
|
||||
if !strings.HasPrefix(line, grubStaticCmdlinePrefix+grubStaticCmdlineQuote) ||
|
||||
!strings.HasSuffix(line, grubStaticCmdlineQuote) ||
|
||||
len(line) < minLength {
|
||||
|
||||
return "", fmt.Errorf("incorrect static command line format: %q", line)
|
||||
}
|
||||
cmdline := line[len(grubStaticCmdlinePrefix+grubStaticCmdlineQuote) : len(line)-len(grubStaticCmdlineQuote)]
|
||||
return cmdline, nil
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// sortSnapdKernelCommandLineArgsForGrub sorts the command line arguments so
|
||||
// that the snapd_recovery_mode/system arguments are placed at the location that
|
||||
// matches the built-in grub boot config assets. Other arguments remain in the order
|
||||
// they appear.
|
||||
func sortSnapdKernelCommandLineArgsForGrub(args []string) []string {
|
||||
out := make([]string, 0, len(args))
|
||||
modeArgs := []string(nil)
|
||||
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(arg, "snapd_recovery_") {
|
||||
modeArgs = append(modeArgs, arg)
|
||||
} else {
|
||||
out = append(out, arg)
|
||||
}
|
||||
}
|
||||
// see grub.cfg and grub-recovery.cfg assets, the order is:
|
||||
// for run mode: snapd_recovery_mode=run <args>
|
||||
// for recovery mode: snapd_recovery_mode=recover snapd_recovery_system=<label> <args>
|
||||
for _, prefixOrder := range []string{"snapd_recovery_system=", "snapd_recovery_mode="} {
|
||||
for i, marg := range modeArgs {
|
||||
if strings.HasPrefix(marg, prefixOrder) {
|
||||
modeArgs = append(modeArgs[:i], modeArgs[i+1:]...)
|
||||
modeArgs = append([]string{marg}, modeArgs...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(modeArgs, out...)
|
||||
}
|
||||
|
||||
@@ -821,3 +821,125 @@ this is updated grub.cfg
|
||||
c.Assert(err, ErrorMatches, `open .*/EFI/ubuntu/grub.cfg\..+: permission denied`)
|
||||
c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig)
|
||||
}
|
||||
|
||||
func (s *grubTestSuite) TestStaticCmdlineFromDiskAsset(c *C) {
|
||||
grubCfg := assets.Internal("grub.cfg")
|
||||
c.Assert(grubCfg, NotNil)
|
||||
grubRecoveryCfg := assets.Internal("grub-recovery.cfg")
|
||||
c.Assert(grubRecoveryCfg, NotNil)
|
||||
|
||||
longBootConfig := `# Snapd-Boot-Config-Edition: 2
|
||||
this is a long boot config
|
||||
set snapd_static_cmdline_args='foo bar baz'
|
||||
`
|
||||
|
||||
for idx, tc := range []struct {
|
||||
content string
|
||||
cmdline string
|
||||
errStr string
|
||||
}{
|
||||
{content: string(grubCfg), cmdline: "console=ttyS0 console=tty1 panic=-1"},
|
||||
{content: string(grubRecoveryCfg), cmdline: "console=ttyS0 console=tty1 panic=-1"},
|
||||
{
|
||||
content: "set snapd_static_cmdline_args='foo=1 bar=2 baz=0x123'\n",
|
||||
cmdline: "foo=1 bar=2 baz=0x123",
|
||||
},
|
||||
{
|
||||
content: `set snapd_static_cmdline_args='foo=BIOS\x20Boot'`,
|
||||
cmdline: `foo=BIOS\x20Boot`,
|
||||
},
|
||||
{
|
||||
content: `set snapd_static_cmdline_args='addr=1$123'`,
|
||||
cmdline: `addr=1$123`,
|
||||
},
|
||||
{
|
||||
content: longBootConfig, cmdline: "foo bar baz",
|
||||
},
|
||||
// no static args
|
||||
{content: "set snapd_static_cmdline_args=''\n", cmdline: ""},
|
||||
{content: "some random script", cmdline: ""},
|
||||
// malformed
|
||||
{
|
||||
content: "set snapd_static_cmdline_args=\n",
|
||||
errStr: "incorrect static command line format: \"set snapd.*\"",
|
||||
},
|
||||
{
|
||||
content: "set snapd_static_cmdline_args='\n",
|
||||
errStr: "incorrect static command line format: \"set snapd.*\"",
|
||||
},
|
||||
} {
|
||||
c.Logf("tc: %v", idx)
|
||||
restore := assets.MockInternal("asset", []byte(tc.content))
|
||||
cmdline, err := bootloader.StaticCommandLineFromGrubAsset("asset")
|
||||
restore()
|
||||
if tc.errStr != "" {
|
||||
c.Assert(err, ErrorMatches, tc.errStr)
|
||||
} else {
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(cmdline, Equals, tc.cmdline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *grubTestSuite) TestCommandLineHappy(c *C) {
|
||||
// pretend there's more than one space between arguments
|
||||
grubCfg := `# Snapd-Boot-Config-Edition: 2
|
||||
set snapd_static_cmdline_args='arg1 foo=123 panic=-1 arg2="with spaces "'
|
||||
boot script
|
||||
`
|
||||
restore := assets.MockInternal("grub.cfg", []byte(grubCfg))
|
||||
defer restore()
|
||||
grubRecoveryCfg := `# Snapd-Boot-Config-Edition: 2
|
||||
set snapd_static_cmdline_args='recovery config panic=-1 '
|
||||
boot script
|
||||
`
|
||||
restore = assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfg))
|
||||
defer restore()
|
||||
|
||||
// native EFI/ubuntu setup
|
||||
s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
|
||||
|
||||
opts := &bootloader.Options{NoSlashBoot: true}
|
||||
g := bootloader.NewGrub(s.rootdir, opts)
|
||||
c.Assert(g, NotNil)
|
||||
mg, ok := g.(bootloader.ManagedAssetsBootloader)
|
||||
c.Assert(ok, Equals, true)
|
||||
|
||||
modeArgs := ""
|
||||
extraArgs := `extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`
|
||||
args, err := mg.CommandLine(modeArgs, extraArgs)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(args, Equals, `arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`)
|
||||
|
||||
modeArgs = "snapd_recovery_mode=run"
|
||||
args, err = mg.CommandLine(modeArgs, extraArgs)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(args, Equals, `snapd_recovery_mode=run arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`)
|
||||
|
||||
modeArgs = "snapd_recovery_system=20200202 snapd_recovery_mode=recover"
|
||||
args, err = mg.CommandLine(modeArgs, extraArgs)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`)
|
||||
|
||||
// now check the recovery bootloader
|
||||
opts = &bootloader.Options{NoSlashBoot: true, Recovery: true}
|
||||
mrg := bootloader.NewGrub(s.rootdir, opts).(bootloader.ManagedAssetsBootloader)
|
||||
args, err = mrg.CommandLine(modeArgs, extraArgs)
|
||||
c.Assert(err, IsNil)
|
||||
// static command line from recovery asset
|
||||
c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery config panic=-1 extra_arg=1 extra_foo=-1 panic=3 baz="more spaces"`)
|
||||
}
|
||||
|
||||
func (s *grubTestSuite) TestSortArgsForGrub(c *C) {
|
||||
out := bootloader.SortSnapdKernelCommandLineArgsForGrub([]string{"foo", "bar", "snapd_recovery_mode=run", "panic=-1"})
|
||||
c.Assert(out, DeepEquals, []string{"snapd_recovery_mode=run", "foo", "bar", "panic=-1"})
|
||||
// recovery mode
|
||||
out = bootloader.SortSnapdKernelCommandLineArgsForGrub([]string{
|
||||
"snapd_recovery_system=1234", "foo",
|
||||
"snapd_recovery_mode=recover", "panic=-1",
|
||||
})
|
||||
c.Assert(out, DeepEquals, []string{
|
||||
"snapd_recovery_mode=recover", "snapd_recovery_system=1234",
|
||||
"foo", "panic=-1",
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user