mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
511 lines
15 KiB
Go
511 lines
15 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2021 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 boot_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
. "gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/boot"
|
|
"github.com/snapcore/snapd/boot/boottest"
|
|
"github.com/snapcore/snapd/bootloader"
|
|
"github.com/snapcore/snapd/bootloader/bootloadertest"
|
|
"github.com/snapcore/snapd/gadget/gadgettest"
|
|
"github.com/snapcore/snapd/osutil/kcmdline"
|
|
"github.com/snapcore/snapd/snap/snaptest"
|
|
"github.com/snapcore/snapd/testutil"
|
|
)
|
|
|
|
var _ = Suite(&kernelCommandLineSuite{})
|
|
|
|
// baseBootSuite is used to setup the common test environment
|
|
type kernelCommandLineSuite struct {
|
|
testutil.BaseTest
|
|
rootDir string
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) SetUpTest(c *C) {
|
|
s.BaseTest.SetUpTest(c)
|
|
s.rootDir = c.MkDir()
|
|
|
|
err := os.MkdirAll(filepath.Join(s.rootDir, "proc"), 0755)
|
|
c.Assert(err, IsNil)
|
|
restore := kcmdline.MockProcCmdline(filepath.Join(s.rootDir, "proc/cmdline"))
|
|
s.AddCleanup(restore)
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) mockProcCmdlineContent(c *C, newContent string) {
|
|
mockProcCmdline := filepath.Join(s.rootDir, "proc/cmdline")
|
|
err := os.WriteFile(mockProcCmdline, []byte(newContent), 0644)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) TestModeAndLabel(c *C) {
|
|
for _, tc := range []struct {
|
|
cmd string
|
|
mode string
|
|
label string
|
|
err string
|
|
}{{
|
|
cmd: "snapd_recovery_mode= snapd_recovery_system=this-is-a-label other-option=foo",
|
|
mode: boot.ModeInstall,
|
|
label: "this-is-a-label",
|
|
}, {
|
|
cmd: "snapd_recovery_system=label foo=bar foobaz=\\0\\0123 snapd_recovery_mode=install",
|
|
label: "label",
|
|
mode: boot.ModeInstall,
|
|
}, {
|
|
cmd: "snapd_recovery_mode=run snapd_recovery_system=1234",
|
|
mode: boot.ModeRun,
|
|
}, {
|
|
cmd: "snapd_recovery_mode=recover snapd_recovery_system=1234",
|
|
label: "1234",
|
|
mode: boot.ModeRecover,
|
|
}, {
|
|
cmd: "snapd_recovery_mode=factory-reset snapd_recovery_system=1234",
|
|
label: "1234",
|
|
mode: boot.ModeFactoryReset,
|
|
}, {
|
|
cmd: "option=1 other-option=\0123 none",
|
|
err: "cannot detect mode nor recovery system to use",
|
|
}, {
|
|
cmd: "snapd_recovery_mode=install-foo",
|
|
err: `cannot use unknown mode "install-foo"`,
|
|
}, {
|
|
// no recovery system label
|
|
cmd: "snapd_recovery_mode=install foo=bar",
|
|
err: `cannot specify install mode without system label`,
|
|
}, {
|
|
cmd: "snapd_recovery_system=1234",
|
|
err: `cannot specify system label without a mode`,
|
|
}, {
|
|
// multiple kernel command line params end up using the last one - this
|
|
// effectively matches the kernel handling too
|
|
cmd: "snapd_recovery_mode=install snapd_recovery_system=1234 snapd_recovery_mode=run",
|
|
mode: "run",
|
|
// label gets unset because it's not used for run mode
|
|
label: "",
|
|
}, {
|
|
cmd: "snapd_recovery_system=not-this-one snapd_recovery_mode=install snapd_recovery_system=1234",
|
|
mode: "install",
|
|
label: "1234",
|
|
}, {
|
|
cmd: "snapd_recovery_mode=cloudimg-rootfs",
|
|
mode: boot.ModeRunCVM,
|
|
}} {
|
|
c.Logf("tc: %q", tc)
|
|
s.mockProcCmdlineContent(c, tc.cmd)
|
|
|
|
mode, label, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
|
|
if tc.err == "" {
|
|
c.Assert(err, IsNil)
|
|
c.Check(mode, Equals, tc.mode)
|
|
c.Check(label, Equals, tc.label)
|
|
} else {
|
|
c.Assert(err, ErrorMatches, tc.err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) TestComposeCommandLineNotManagedHappy(c *C) {
|
|
model := boottest.MakeMockUC20Model()
|
|
|
|
bl := bootloadertest.Mock("btloader", c.MkDir())
|
|
bootloader.Force(bl)
|
|
defer bootloader.Force(nil)
|
|
|
|
cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "")
|
|
|
|
cmdline, err = boot.ComposeCommandLine(model, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "")
|
|
|
|
tbl := bl.WithTrustedAssets()
|
|
bootloader.Force(tbl)
|
|
|
|
cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314")
|
|
|
|
cmdline, err = boot.ComposeCommandLine(model, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=run")
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) TestComposeCommandLineNotUC20(c *C) {
|
|
model := boottest.MakeMockModel()
|
|
|
|
bl := bootloadertest.Mock("btloader", c.MkDir())
|
|
bootloader.Force(bl)
|
|
defer bootloader.Force(nil)
|
|
cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(cmdline, Equals, "")
|
|
|
|
cmdline, err = boot.ComposeCommandLine(model, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(cmdline, Equals, "")
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) TestComposeCommandLineManagedHappy(c *C) {
|
|
model := boottest.MakeMockUC20Model()
|
|
|
|
tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
|
|
bootloader.Force(tbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
tbl.StaticCommandLine = "panic=-1"
|
|
|
|
cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1")
|
|
cmdline, err = boot.ComposeCommandLine(model, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1")
|
|
|
|
cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1")
|
|
cmdline, err = boot.ComposeCommandLine(model, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1")
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) TestComposeCandidateCommandLineManagedHappy(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"
|
|
|
|
cmdline, err := boot.ComposeCandidateCommandLine(model, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(cmdline, Equals, "snapd_recovery_mode=run candidate panic=0")
|
|
}
|
|
|
|
func (s *kernelCommandLineSuite) TestComposeCandidateRecoveryCommandLineManagedHappy(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"
|
|
|
|
cmdline, err := boot.ComposeCandidateRecoveryCommandLine(model, "1234", "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0")
|
|
|
|
cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, "", "")
|
|
c.Assert(err, ErrorMatches, "internal error: system is unset")
|
|
c.Check(cmdline, Equals, "")
|
|
}
|
|
|
|
const gadgetSnapYaml = `name: gadget
|
|
version: 1.0
|
|
type: gadget
|
|
`
|
|
|
|
func (s *kernelCommandLineSuite) TestComposeCommandLineWithGadget(c *C) {
|
|
model := boottest.MakeMockUC20Model()
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
|
|
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"},
|
|
},
|
|
expCommandLine: "snapd_recovery_mode=run cmdline full",
|
|
}, {
|
|
which: "candidate",
|
|
files: [][]string{
|
|
{"cmdline.full", "cmdline full"},
|
|
},
|
|
expCommandLine: "snapd_recovery_mode=run cmdline full",
|
|
}, {
|
|
which: "candidate",
|
|
files: [][]string{
|
|
{"cmdline.extra", `bad-quote="`},
|
|
},
|
|
errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`,
|
|
}} {
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
|
|
{"meta/snap.yaml", gadgetSnapYaml},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
}, 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()
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
|
|
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"},
|
|
},
|
|
expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full",
|
|
}, {
|
|
which: "candidate",
|
|
files: [][]string{
|
|
{"cmdline.full", "cmdline full"},
|
|
},
|
|
expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full",
|
|
}, {
|
|
which: "candidate",
|
|
files: [][]string{
|
|
{"cmdline.extra", `bad-quote="`},
|
|
},
|
|
errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`,
|
|
}} {
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
|
|
{"meta/snap.yaml", gadgetSnapYaml},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
}, 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) {
|
|
model := &gadgettest.ModelCharacteristics{}
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
|
|
for _, tc := range []struct {
|
|
errMsg string
|
|
files [][]string
|
|
cmdlineAppend string
|
|
expectedVars map[string]string
|
|
append []string
|
|
remove []string
|
|
}{{
|
|
files: [][]string{
|
|
{"cmdline.extra", "foo bar baz"},
|
|
},
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "default foo bar baz",
|
|
},
|
|
}, {
|
|
files: [][]string{
|
|
{"cmdline.extra", "snapd.debug=1"},
|
|
},
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "default snapd.debug=1",
|
|
},
|
|
}, {
|
|
files: [][]string{
|
|
{"cmdline.extra", "snapd_foo"},
|
|
},
|
|
errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument \"snapd_foo\"`,
|
|
}, {
|
|
files: [][]string{
|
|
{"cmdline.full", "full foo bar baz"},
|
|
},
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "full foo bar baz",
|
|
},
|
|
}, {
|
|
cmdlineAppend: "foo bar baz",
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "default foo bar baz",
|
|
},
|
|
}, {
|
|
files: [][]string{
|
|
{"cmdline.extra", "foo bar baz"},
|
|
},
|
|
cmdlineAppend: "x=y z",
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "default foo bar baz x=y z",
|
|
},
|
|
}, {
|
|
files: [][]string{
|
|
{"cmdline.full", "full foo bar baz"},
|
|
},
|
|
cmdlineAppend: "x=y z",
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "full foo bar baz x=y z",
|
|
},
|
|
}, {
|
|
// with no arguments boot variables should be cleared
|
|
files: [][]string{},
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "default",
|
|
},
|
|
}, {
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": `default bar baz=* "with spaces"`,
|
|
},
|
|
append: []string{"bar", "baz=*", `'"with spaces"'`},
|
|
}, {
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "nodefault",
|
|
},
|
|
append: []string{"nodefault"},
|
|
remove: []string{"default"},
|
|
}, {
|
|
expectedVars: map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": " ",
|
|
},
|
|
remove: []string{"default"},
|
|
}} {
|
|
gadgetYaml := mockGadgetYaml
|
|
if len(tc.append) > 0 || len(tc.remove) > 0 {
|
|
gadgetYaml = fmt.Sprintf("%skernel-cmdline:\n", gadgetYaml)
|
|
}
|
|
if len(tc.append) > 0 {
|
|
gadgetYaml = fmt.Sprintf("%s append:\n", gadgetYaml)
|
|
}
|
|
for _, append := range tc.append {
|
|
gadgetYaml = fmt.Sprintf("%s - %s\n", gadgetYaml, append)
|
|
}
|
|
if len(tc.remove) > 0 {
|
|
gadgetYaml = fmt.Sprintf("%s remove:\n", gadgetYaml)
|
|
}
|
|
for _, remove := range tc.remove {
|
|
gadgetYaml = fmt.Sprintf("%s - %s\n", gadgetYaml, remove)
|
|
}
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
|
|
{"meta/snap.yaml", gadgetSnapYaml},
|
|
{"meta/gadget.yaml", gadgetYaml},
|
|
}, tc.files...))
|
|
vars, err := boot.BootVarsForTrustedCommandLineFromGadget(sf, tc.cmdlineAppend, "default", model)
|
|
if tc.errMsg == "" {
|
|
c.Assert(err, IsNil)
|
|
c.Assert(vars, DeepEquals, tc.expectedVars)
|
|
} else {
|
|
c.Assert(err, ErrorMatches, tc.errMsg)
|
|
}
|
|
}
|
|
}
|