gitee.com/mysnapcore/mysnapd@v0.1.0/boot/cmdline_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package boot_test
    21  
    22  import (
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"gitee.com/mysnapcore/mysnapd/boot"
    30  	"gitee.com/mysnapcore/mysnapd/boot/boottest"
    31  	"gitee.com/mysnapcore/mysnapd/bootloader"
    32  	"gitee.com/mysnapcore/mysnapd/bootloader/bootloadertest"
    33  	"gitee.com/mysnapcore/mysnapd/osutil"
    34  	"gitee.com/mysnapcore/mysnapd/snap/snaptest"
    35  	"gitee.com/mysnapcore/mysnapd/testutil"
    36  )
    37  
    38  var _ = Suite(&kernelCommandLineSuite{})
    39  
    40  // baseBootSuite is used to setup the common test environment
    41  type kernelCommandLineSuite struct {
    42  	testutil.BaseTest
    43  	rootDir string
    44  }
    45  
    46  func (s *kernelCommandLineSuite) SetUpTest(c *C) {
    47  	s.BaseTest.SetUpTest(c)
    48  	s.rootDir = c.MkDir()
    49  
    50  	err := os.MkdirAll(filepath.Join(s.rootDir, "proc"), 0755)
    51  	c.Assert(err, IsNil)
    52  	restore := osutil.MockProcCmdline(filepath.Join(s.rootDir, "proc/cmdline"))
    53  	s.AddCleanup(restore)
    54  }
    55  
    56  func (s *kernelCommandLineSuite) mockProcCmdlineContent(c *C, newContent string) {
    57  	mockProcCmdline := filepath.Join(s.rootDir, "proc/cmdline")
    58  	err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644)
    59  	c.Assert(err, IsNil)
    60  }
    61  
    62  func (s *kernelCommandLineSuite) TestModeAndLabel(c *C) {
    63  	for _, tc := range []struct {
    64  		cmd   string
    65  		mode  string
    66  		label string
    67  		err   string
    68  	}{{
    69  		cmd:   "snapd_recovery_mode= snapd_recovery_system=this-is-a-label other-option=foo",
    70  		mode:  boot.ModeInstall,
    71  		label: "this-is-a-label",
    72  	}, {
    73  		cmd:   "snapd_recovery_system=label foo=bar foobaz=\\0\\0123 snapd_recovery_mode=install",
    74  		label: "label",
    75  		mode:  boot.ModeInstall,
    76  	}, {
    77  		cmd:  "snapd_recovery_mode=run snapd_recovery_system=1234",
    78  		mode: boot.ModeRun,
    79  	}, {
    80  		cmd:   "snapd_recovery_mode=recover snapd_recovery_system=1234",
    81  		label: "1234",
    82  		mode:  boot.ModeRecover,
    83  	}, {
    84  		cmd:   "snapd_recovery_mode=factory-reset snapd_recovery_system=1234",
    85  		label: "1234",
    86  		mode:  boot.ModeFactoryReset,
    87  	}, {
    88  		cmd: "option=1 other-option=\0123 none",
    89  		err: "cannot detect mode nor recovery system to use",
    90  	}, {
    91  		cmd: "snapd_recovery_mode=install-foo",
    92  		err: `cannot use unknown mode "install-foo"`,
    93  	}, {
    94  		// no recovery system label
    95  		cmd: "snapd_recovery_mode=install foo=bar",
    96  		err: `cannot specify install mode without system label`,
    97  	}, {
    98  		cmd: "snapd_recovery_system=1234",
    99  		err: `cannot specify system label without a mode`,
   100  	}, {
   101  		// multiple kernel command line params end up using the last one - this
   102  		// effectively matches the kernel handling too
   103  		cmd:  "snapd_recovery_mode=install snapd_recovery_system=1234 snapd_recovery_mode=run",
   104  		mode: "run",
   105  		// label gets unset because it's not used for run mode
   106  		label: "",
   107  	}, {
   108  		cmd:   "snapd_recovery_system=not-this-one snapd_recovery_mode=install snapd_recovery_system=1234",
   109  		mode:  "install",
   110  		label: "1234",
   111  	}, {
   112  		cmd:  "snapd_recovery_mode=cloudimg-rootfs",
   113  		mode: boot.ModeRunCVM,
   114  	}} {
   115  		c.Logf("tc: %q", tc)
   116  		s.mockProcCmdlineContent(c, tc.cmd)
   117  
   118  		mode, label, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
   119  		if tc.err == "" {
   120  			c.Assert(err, IsNil)
   121  			c.Check(mode, Equals, tc.mode)
   122  			c.Check(label, Equals, tc.label)
   123  		} else {
   124  			c.Assert(err, ErrorMatches, tc.err)
   125  		}
   126  	}
   127  }
   128  
   129  func (s *kernelCommandLineSuite) TestComposeCommandLineNotManagedHappy(c *C) {
   130  	model := boottest.MakeMockUC20Model()
   131  
   132  	bl := bootloadertest.Mock("btloader", c.MkDir())
   133  	bootloader.Force(bl)
   134  	defer bootloader.Force(nil)
   135  
   136  	cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
   137  	c.Assert(err, IsNil)
   138  	c.Assert(cmdline, Equals, "")
   139  
   140  	cmdline, err = boot.ComposeCommandLine(model, "")
   141  	c.Assert(err, IsNil)
   142  	c.Assert(cmdline, Equals, "")
   143  
   144  	tbl := bl.WithTrustedAssets()
   145  	bootloader.Force(tbl)
   146  
   147  	cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "")
   148  	c.Assert(err, IsNil)
   149  	c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314")
   150  
   151  	cmdline, err = boot.ComposeCommandLine(model, "")
   152  	c.Assert(err, IsNil)
   153  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run")
   154  }
   155  
   156  func (s *kernelCommandLineSuite) TestComposeCommandLineNotUC20(c *C) {
   157  	model := boottest.MakeMockModel()
   158  
   159  	bl := bootloadertest.Mock("btloader", c.MkDir())
   160  	bootloader.Force(bl)
   161  	defer bootloader.Force(nil)
   162  	cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
   163  	c.Assert(err, IsNil)
   164  	c.Check(cmdline, Equals, "")
   165  
   166  	cmdline, err = boot.ComposeCommandLine(model, "")
   167  	c.Assert(err, IsNil)
   168  	c.Check(cmdline, Equals, "")
   169  }
   170  
   171  func (s *kernelCommandLineSuite) TestComposeCommandLineManagedHappy(c *C) {
   172  	model := boottest.MakeMockUC20Model()
   173  
   174  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   175  	bootloader.Force(tbl)
   176  	defer bootloader.Force(nil)
   177  
   178  	tbl.StaticCommandLine = "panic=-1"
   179  
   180  	cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
   181  	c.Assert(err, IsNil)
   182  	c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1")
   183  	cmdline, err = boot.ComposeCommandLine(model, "")
   184  	c.Assert(err, IsNil)
   185  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1")
   186  
   187  	cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "")
   188  	c.Assert(err, IsNil)
   189  	c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1")
   190  	cmdline, err = boot.ComposeCommandLine(model, "")
   191  	c.Assert(err, IsNil)
   192  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1")
   193  }
   194  
   195  func (s *kernelCommandLineSuite) TestComposeCandidateCommandLineManagedHappy(c *C) {
   196  	model := boottest.MakeMockUC20Model()
   197  
   198  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   199  	bootloader.Force(tbl)
   200  	defer bootloader.Force(nil)
   201  
   202  	tbl.StaticCommandLine = "panic=-1"
   203  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   204  
   205  	cmdline, err := boot.ComposeCandidateCommandLine(model, "")
   206  	c.Assert(err, IsNil)
   207  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run candidate panic=0")
   208  }
   209  
   210  func (s *kernelCommandLineSuite) TestComposeCandidateRecoveryCommandLineManagedHappy(c *C) {
   211  	model := boottest.MakeMockUC20Model()
   212  
   213  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   214  	bootloader.Force(tbl)
   215  	defer bootloader.Force(nil)
   216  
   217  	tbl.StaticCommandLine = "panic=-1"
   218  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   219  
   220  	cmdline, err := boot.ComposeCandidateRecoveryCommandLine(model, "1234", "")
   221  	c.Assert(err, IsNil)
   222  	c.Check(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0")
   223  
   224  	cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, "", "")
   225  	c.Assert(err, ErrorMatches, "internal error: system is unset")
   226  	c.Check(cmdline, Equals, "")
   227  }
   228  
   229  const gadgetSnapYaml = `name: gadget
   230  version: 1.0
   231  type: gadget
   232  `
   233  
   234  func (s *kernelCommandLineSuite) TestComposeCommandLineWithGadget(c *C) {
   235  	model := boottest.MakeMockUC20Model()
   236  
   237  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   238  	bootloader.Force(tbl)
   239  	defer bootloader.Force(nil)
   240  
   241  	tbl.StaticCommandLine = "panic=-1"
   242  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   243  
   244  	for _, tc := range []struct {
   245  		which          string
   246  		files          [][]string
   247  		expCommandLine string
   248  		errMsg         string
   249  	}{{
   250  		which: "current",
   251  		files: [][]string{
   252  			{"cmdline.extra", "cmdline extra"},
   253  		},
   254  		expCommandLine: "snapd_recovery_mode=run panic=-1 cmdline extra",
   255  	}, {
   256  		which: "candidate",
   257  		files: [][]string{
   258  			{"cmdline.extra", "cmdline extra"},
   259  		},
   260  		expCommandLine: "snapd_recovery_mode=run candidate panic=0 cmdline extra",
   261  	}, {
   262  		which: "current",
   263  		files: [][]string{
   264  			{"cmdline.full", "cmdline full"},
   265  		},
   266  		expCommandLine: "snapd_recovery_mode=run cmdline full",
   267  	}, {
   268  		which: "candidate",
   269  		files: [][]string{
   270  			{"cmdline.full", "cmdline full"},
   271  		},
   272  		expCommandLine: "snapd_recovery_mode=run cmdline full",
   273  	}, {
   274  		which: "candidate",
   275  		files: [][]string{
   276  			{"cmdline.extra", `bad-quote="`},
   277  		},
   278  		errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`,
   279  	}} {
   280  		sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
   281  			{"meta/snap.yaml", gadgetSnapYaml},
   282  		}, tc.files...))
   283  		var cmdline string
   284  		var err error
   285  		switch tc.which {
   286  		case "current":
   287  			cmdline, err = boot.ComposeCommandLine(model, sf)
   288  		case "candidate":
   289  			cmdline, err = boot.ComposeCandidateCommandLine(model, sf)
   290  		default:
   291  			c.Fatalf("unexpected command line type")
   292  		}
   293  		if tc.errMsg == "" {
   294  			c.Assert(err, IsNil)
   295  			c.Assert(cmdline, Equals, tc.expCommandLine)
   296  		} else {
   297  			c.Assert(err, ErrorMatches, tc.errMsg)
   298  		}
   299  	}
   300  }
   301  
   302  func (s *kernelCommandLineSuite) TestComposeRecoveryCommandLineWithGadget(c *C) {
   303  	model := boottest.MakeMockUC20Model()
   304  
   305  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   306  	bootloader.Force(tbl)
   307  	defer bootloader.Force(nil)
   308  
   309  	tbl.StaticCommandLine = "panic=-1"
   310  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   311  	system := "1234"
   312  
   313  	for _, tc := range []struct {
   314  		which          string
   315  		files          [][]string
   316  		expCommandLine string
   317  		errMsg         string
   318  	}{{
   319  		which: "current",
   320  		files: [][]string{
   321  			{"cmdline.extra", "cmdline extra"},
   322  		},
   323  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 panic=-1 cmdline extra",
   324  	}, {
   325  		which: "candidate",
   326  		files: [][]string{
   327  			{"cmdline.extra", "cmdline extra"},
   328  		},
   329  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0 cmdline extra",
   330  	}, {
   331  		which: "current",
   332  		files: [][]string{
   333  			{"cmdline.full", "cmdline full"},
   334  		},
   335  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full",
   336  	}, {
   337  		which: "candidate",
   338  		files: [][]string{
   339  			{"cmdline.full", "cmdline full"},
   340  		},
   341  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full",
   342  	}, {
   343  		which: "candidate",
   344  		files: [][]string{
   345  			{"cmdline.extra", `bad-quote="`},
   346  		},
   347  		errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`,
   348  	}} {
   349  		sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
   350  			{"meta/snap.yaml", gadgetSnapYaml},
   351  		}, tc.files...))
   352  		var cmdline string
   353  		var err error
   354  		switch tc.which {
   355  		case "current":
   356  			cmdline, err = boot.ComposeRecoveryCommandLine(model, system, sf)
   357  		case "candidate":
   358  			cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, system, sf)
   359  		default:
   360  			c.Fatalf("unexpected command line type")
   361  		}
   362  		if tc.errMsg == "" {
   363  			c.Assert(err, IsNil)
   364  			c.Assert(cmdline, Equals, tc.expCommandLine)
   365  		} else {
   366  			c.Assert(err, ErrorMatches, tc.errMsg)
   367  		}
   368  	}
   369  }
   370  
   371  func (s *kernelCommandLineSuite) TestBootVarsForGadgetCommandLine(c *C) {
   372  	for _, tc := range []struct {
   373  		errMsg       string
   374  		files        [][]string
   375  		expectedVars map[string]string
   376  	}{{
   377  		files: [][]string{
   378  			{"cmdline.extra", "foo bar baz"},
   379  		},
   380  		expectedVars: map[string]string{
   381  			"snapd_extra_cmdline_args": "foo bar baz",
   382  			"snapd_full_cmdline_args":  "",
   383  		},
   384  	}, {
   385  		files: [][]string{
   386  			{"cmdline.extra", "snapd.debug=1"},
   387  		},
   388  		expectedVars: map[string]string{
   389  			"snapd_extra_cmdline_args": "snapd.debug=1",
   390  			"snapd_full_cmdline_args":  "",
   391  		},
   392  	}, {
   393  		files: [][]string{
   394  			{"cmdline.extra", "snapd_foo"},
   395  		},
   396  		errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument \"snapd_foo\"`,
   397  	}, {
   398  		files: [][]string{
   399  			{"cmdline.full", "full foo bar baz"},
   400  		},
   401  		expectedVars: map[string]string{
   402  			"snapd_extra_cmdline_args": "",
   403  			"snapd_full_cmdline_args":  "full foo bar baz",
   404  		},
   405  	}, {
   406  		// with no arguments boot variables should be cleared
   407  		files: [][]string{},
   408  		expectedVars: map[string]string{
   409  			"snapd_extra_cmdline_args": "",
   410  			"snapd_full_cmdline_args":  "",
   411  		},
   412  	}} {
   413  		sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
   414  			{"meta/snap.yaml", gadgetSnapYaml},
   415  		}, tc.files...))
   416  		vars, err := boot.BootVarsForTrustedCommandLineFromGadget(sf)
   417  		if tc.errMsg == "" {
   418  			c.Assert(err, IsNil)
   419  			c.Assert(vars, DeepEquals, tc.expectedVars)
   420  		} else {
   421  			c.Assert(err, ErrorMatches, tc.errMsg)
   422  		}
   423  	}
   424  }