github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/bootloader/grub_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 bootloader_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  
    29  	"github.com/mvo5/goconfigparser"
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/bootloader"
    33  	"github.com/snapcore/snapd/bootloader/assets"
    34  	"github.com/snapcore/snapd/bootloader/grubenv"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/snap/snapfile"
    39  	"github.com/snapcore/snapd/snap/snaptest"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  type grubTestSuite struct {
    44  	baseBootenvTestSuite
    45  
    46  	bootdir string
    47  }
    48  
    49  var _ = Suite(&grubTestSuite{})
    50  
    51  func (s *grubTestSuite) SetUpTest(c *C) {
    52  	s.baseBootenvTestSuite.SetUpTest(c)
    53  	bootloader.MockGrubFiles(c, s.rootdir)
    54  
    55  	s.bootdir = filepath.Join(s.rootdir, "boot")
    56  }
    57  
    58  // grubEditenvCmd finds the right grub{,2}-editenv command
    59  func grubEditenvCmd() string {
    60  	for _, exe := range []string{"grub2-editenv", "grub-editenv"} {
    61  		if osutil.ExecutableExists(exe) {
    62  			return exe
    63  		}
    64  	}
    65  	return ""
    66  }
    67  
    68  func grubEnvPath(rootdir string) string {
    69  	return filepath.Join(rootdir, "boot/grub/grubenv")
    70  }
    71  
    72  func (s *grubTestSuite) grubEditenvSet(c *C, key, value string) {
    73  	if grubEditenvCmd() == "" {
    74  		c.Skip("grub{,2}-editenv is not available")
    75  	}
    76  
    77  	output, err := exec.Command(grubEditenvCmd(), grubEnvPath(s.rootdir), "set", fmt.Sprintf("%s=%s", key, value)).CombinedOutput()
    78  	c.Check(err, IsNil)
    79  	c.Check(string(output), Equals, "")
    80  }
    81  
    82  func (s *grubTestSuite) grubEditenvGet(c *C, key string) string {
    83  	if grubEditenvCmd() == "" {
    84  		c.Skip("grub{,2}-editenv is not available")
    85  	}
    86  
    87  	output, err := exec.Command(grubEditenvCmd(), grubEnvPath(s.rootdir), "list").CombinedOutput()
    88  	c.Assert(err, IsNil)
    89  	cfg := goconfigparser.New()
    90  	cfg.AllowNoSectionHeader = true
    91  	err = cfg.ReadString(string(output))
    92  	c.Assert(err, IsNil)
    93  	v, err := cfg.Get("", key)
    94  	c.Assert(err, IsNil)
    95  	return v
    96  }
    97  
    98  func (s *grubTestSuite) makeFakeGrubEnv(c *C) {
    99  	s.grubEditenvSet(c, "k", "v")
   100  }
   101  
   102  func (s *grubTestSuite) TestNewGrub(c *C) {
   103  	// no files means bl is not present, but we can still create the bl object
   104  	c.Assert(os.RemoveAll(s.rootdir), IsNil)
   105  	g := bootloader.NewGrub(s.rootdir, nil)
   106  	c.Assert(g, NotNil)
   107  	c.Assert(g.Name(), Equals, "grub")
   108  
   109  	present, err := g.Present()
   110  	c.Assert(err, IsNil)
   111  	c.Assert(present, Equals, false)
   112  
   113  	// now with files present, the bl is present
   114  	bootloader.MockGrubFiles(c, s.rootdir)
   115  	s.makeFakeGrubEnv(c)
   116  	present, err = g.Present()
   117  	c.Assert(err, IsNil)
   118  	c.Assert(present, Equals, true)
   119  }
   120  
   121  func (s *grubTestSuite) TestGetBootloaderWithGrub(c *C) {
   122  	s.makeFakeGrubEnv(c)
   123  
   124  	bootloader, err := bootloader.Find(s.rootdir, nil)
   125  	c.Assert(err, IsNil)
   126  	c.Assert(bootloader.Name(), Equals, "grub")
   127  }
   128  
   129  func (s *grubTestSuite) TestGetBootloaderWithGrubWithDefaultRoot(c *C) {
   130  	s.makeFakeGrubEnv(c)
   131  
   132  	dirs.SetRootDir(s.rootdir)
   133  	defer func() { dirs.SetRootDir("") }()
   134  
   135  	bootloader, err := bootloader.Find("", nil)
   136  	c.Assert(err, IsNil)
   137  	c.Assert(bootloader.Name(), Equals, "grub")
   138  }
   139  
   140  func (s *grubTestSuite) TestGetBootVer(c *C) {
   141  	s.makeFakeGrubEnv(c)
   142  	s.grubEditenvSet(c, "snap_mode", "regular")
   143  
   144  	g := bootloader.NewGrub(s.rootdir, nil)
   145  	v, err := g.GetBootVars("snap_mode")
   146  	c.Assert(err, IsNil)
   147  	c.Check(v, HasLen, 1)
   148  	c.Check(v["snap_mode"], Equals, "regular")
   149  }
   150  
   151  func (s *grubTestSuite) TestSetBootVer(c *C) {
   152  	s.makeFakeGrubEnv(c)
   153  
   154  	g := bootloader.NewGrub(s.rootdir, nil)
   155  	err := g.SetBootVars(map[string]string{
   156  		"k1": "v1",
   157  		"k2": "v2",
   158  	})
   159  	c.Assert(err, IsNil)
   160  
   161  	c.Check(s.grubEditenvGet(c, "k1"), Equals, "v1")
   162  	c.Check(s.grubEditenvGet(c, "k2"), Equals, "v2")
   163  }
   164  
   165  func (s *grubTestSuite) TestExtractKernelAssetsNoUnpacksKernelForGrub(c *C) {
   166  	s.makeFakeGrubEnv(c)
   167  
   168  	g := bootloader.NewGrub(s.rootdir, nil)
   169  
   170  	files := [][]string{
   171  		{"kernel.img", "I'm a kernel"},
   172  		{"initrd.img", "...and I'm an initrd"},
   173  		{"meta/kernel.yaml", "version: 4.2"},
   174  	}
   175  	si := &snap.SideInfo{
   176  		RealName: "ubuntu-kernel",
   177  		Revision: snap.R(42),
   178  	}
   179  	fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
   180  	snapf, err := snapfile.Open(fn)
   181  	c.Assert(err, IsNil)
   182  
   183  	info, err := snap.ReadInfoFromSnapFile(snapf, si)
   184  	c.Assert(err, IsNil)
   185  
   186  	err = g.ExtractKernelAssets(info, snapf)
   187  	c.Assert(err, IsNil)
   188  
   189  	// kernel is *not* here
   190  	kernimg := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "kernel.img")
   191  	c.Assert(osutil.FileExists(kernimg), Equals, false)
   192  }
   193  
   194  func (s *grubTestSuite) TestExtractKernelForceWorks(c *C) {
   195  	s.makeFakeGrubEnv(c)
   196  
   197  	g := bootloader.NewGrub(s.rootdir, nil)
   198  	c.Assert(g, NotNil)
   199  
   200  	files := [][]string{
   201  		{"kernel.img", "I'm a kernel"},
   202  		{"initrd.img", "...and I'm an initrd"},
   203  		{"meta/force-kernel-extraction", ""},
   204  		{"meta/kernel.yaml", "version: 4.2"},
   205  	}
   206  	si := &snap.SideInfo{
   207  		RealName: "ubuntu-kernel",
   208  		Revision: snap.R(42),
   209  	}
   210  	fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
   211  	snapf, err := snapfile.Open(fn)
   212  	c.Assert(err, IsNil)
   213  
   214  	info, err := snap.ReadInfoFromSnapFile(snapf, si)
   215  	c.Assert(err, IsNil)
   216  
   217  	err = g.ExtractKernelAssets(info, snapf)
   218  	c.Assert(err, IsNil)
   219  
   220  	// kernel is extracted
   221  	kernimg := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "kernel.img")
   222  	c.Assert(osutil.FileExists(kernimg), Equals, true)
   223  	// initrd
   224  	initrdimg := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "initrd.img")
   225  	c.Assert(osutil.FileExists(initrdimg), Equals, true)
   226  
   227  	// ensure that removal of assets also works
   228  	err = g.RemoveKernelAssets(info)
   229  	c.Assert(err, IsNil)
   230  	exists, _, err := osutil.DirExists(filepath.Dir(kernimg))
   231  	c.Assert(err, IsNil)
   232  	c.Check(exists, Equals, false)
   233  }
   234  
   235  func (s *grubTestSuite) grubDir() string {
   236  	return filepath.Join(s.bootdir, "grub")
   237  }
   238  
   239  func (s *grubTestSuite) grubEFINativeDir() string {
   240  	return filepath.Join(s.rootdir, "EFI/ubuntu")
   241  }
   242  
   243  func (s *grubTestSuite) makeFakeGrubEFINativeEnv(c *C, content []byte) {
   244  	err := os.MkdirAll(s.grubEFINativeDir(), 0755)
   245  	c.Assert(err, IsNil)
   246  	err = ioutil.WriteFile(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), content, 0644)
   247  	c.Assert(err, IsNil)
   248  }
   249  
   250  func (s *grubTestSuite) TestNewGrubWithOptionRecovery(c *C) {
   251  	s.makeFakeGrubEFINativeEnv(c, nil)
   252  
   253  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
   254  	c.Assert(g, NotNil)
   255  	c.Assert(g.Name(), Equals, "grub")
   256  }
   257  
   258  func (s *grubTestSuite) TestNewGrubWithOptionRecoveryBootEnv(c *C) {
   259  	s.makeFakeGrubEFINativeEnv(c, nil)
   260  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
   261  
   262  	// check that setting vars goes to the right place
   263  	c.Check(filepath.Join(s.grubEFINativeDir(), "grubenv"), testutil.FileAbsent)
   264  	err := g.SetBootVars(map[string]string{
   265  		"k1": "v1",
   266  		"k2": "v2",
   267  	})
   268  	c.Assert(err, IsNil)
   269  	c.Check(filepath.Join(s.grubEFINativeDir(), "grubenv"), testutil.FilePresent)
   270  
   271  	env, err := g.GetBootVars("k1", "k2")
   272  	c.Assert(err, IsNil)
   273  	c.Check(env, DeepEquals, map[string]string{
   274  		"k1": "v1",
   275  		"k2": "v2",
   276  	})
   277  }
   278  
   279  func (s *grubTestSuite) TestNewGrubWithOptionRecoveryNoEnv(c *C) {
   280  	// fake a *regular* grub env
   281  	s.makeFakeGrubEnv(c)
   282  
   283  	// we can't create a recovery grub with that
   284  	g, err := bootloader.Find(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
   285  	c.Assert(g, IsNil)
   286  	c.Assert(err, Equals, bootloader.ErrBootloader)
   287  }
   288  
   289  func (s *grubTestSuite) TestGrubSetRecoverySystemEnv(c *C) {
   290  	s.makeFakeGrubEFINativeEnv(c, nil)
   291  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
   292  
   293  	// check that we can set a recovery system specific bootenv
   294  	bvars := map[string]string{
   295  		"snapd_recovery_kernel": "/snaps/pc-kernel_1.snap",
   296  		"other_options":         "are-supported",
   297  	}
   298  
   299  	err := g.SetRecoverySystemEnv("/systems/20191209", bvars)
   300  	c.Assert(err, IsNil)
   301  	recoverySystemGrubenv := filepath.Join(s.rootdir, "/systems/20191209/grubenv")
   302  	c.Assert(recoverySystemGrubenv, testutil.FilePresent)
   303  
   304  	genv := grubenv.NewEnv(recoverySystemGrubenv)
   305  	err = genv.Load()
   306  	c.Assert(err, IsNil)
   307  	c.Check(genv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_1.snap")
   308  	c.Check(genv.Get("other_options"), Equals, "are-supported")
   309  }
   310  
   311  func (s *grubTestSuite) TestGetRecoverySystemEnv(c *C) {
   312  	s.makeFakeGrubEFINativeEnv(c, nil)
   313  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
   314  
   315  	err := os.MkdirAll(filepath.Join(s.rootdir, "/systems/20191209"), 0755)
   316  	c.Assert(err, IsNil)
   317  	recoverySystemGrubenv := filepath.Join(s.rootdir, "/systems/20191209/grubenv")
   318  
   319  	// does not fail when there is no recovery env
   320  	value, err := g.GetRecoverySystemEnv("/systems/20191209", "no_file")
   321  	c.Assert(err, IsNil)
   322  	c.Check(value, Equals, "")
   323  
   324  	genv := grubenv.NewEnv(recoverySystemGrubenv)
   325  	genv.Set("snapd_extra_cmdline_args", "foo bar baz")
   326  	genv.Set("random_option", `has "some spaces"`)
   327  	err = genv.Save()
   328  	c.Assert(err, IsNil)
   329  
   330  	value, err = g.GetRecoverySystemEnv("/systems/20191209", "snapd_extra_cmdline_args")
   331  	c.Assert(err, IsNil)
   332  	c.Check(value, Equals, "foo bar baz")
   333  	value, err = g.GetRecoverySystemEnv("/systems/20191209", "random_option")
   334  	c.Assert(err, IsNil)
   335  	c.Check(value, Equals, `has "some spaces"`)
   336  	value, err = g.GetRecoverySystemEnv("/systems/20191209", "not_set")
   337  	c.Assert(err, IsNil)
   338  	c.Check(value, Equals, ``)
   339  }
   340  
   341  func (s *grubTestSuite) makeKernelAssetSnap(c *C, snapFileName string) snap.PlaceInfo {
   342  	kernelSnap, err := snap.ParsePlaceInfoFromSnapFileName(snapFileName)
   343  	c.Assert(err, IsNil)
   344  
   345  	// make a kernel.efi snap as it would be by ExtractKernelAssets()
   346  	kernelSnapExtractedAssetsDir := filepath.Join(s.grubDir(), snapFileName)
   347  	err = os.MkdirAll(kernelSnapExtractedAssetsDir, 0755)
   348  	c.Assert(err, IsNil)
   349  
   350  	err = ioutil.WriteFile(filepath.Join(kernelSnapExtractedAssetsDir, "kernel.efi"), nil, 0644)
   351  	c.Assert(err, IsNil)
   352  
   353  	return kernelSnap
   354  }
   355  
   356  func (s *grubTestSuite) makeKernelAssetSnapAndSymlink(c *C, snapFileName, symlinkName string) snap.PlaceInfo {
   357  	kernelSnap := s.makeKernelAssetSnap(c, snapFileName)
   358  
   359  	// make a kernel.efi symlink to the kernel.efi above
   360  	err := os.Symlink(
   361  		filepath.Join(snapFileName, "kernel.efi"),
   362  		filepath.Join(s.grubDir(), symlinkName),
   363  	)
   364  	c.Assert(err, IsNil)
   365  
   366  	return kernelSnap
   367  }
   368  
   369  func (s *grubTestSuite) TestGrubExtractedRunKernelImageKernel(c *C) {
   370  	s.makeFakeGrubEnv(c)
   371  	g := bootloader.NewGrub(s.rootdir, nil)
   372  	eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader)
   373  	c.Assert(ok, Equals, true)
   374  
   375  	kernel := s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_1.snap", "kernel.efi")
   376  
   377  	// ensure that the returned kernel is the same as the one we put there
   378  	sn, err := eg.Kernel()
   379  	c.Assert(err, IsNil)
   380  	c.Assert(sn, DeepEquals, kernel)
   381  }
   382  
   383  func (s *grubTestSuite) TestGrubExtractedRunKernelImageTryKernel(c *C) {
   384  	s.makeFakeGrubEnv(c)
   385  	g := bootloader.NewGrub(s.rootdir, nil)
   386  	eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader)
   387  	c.Assert(ok, Equals, true)
   388  
   389  	// ensure it doesn't return anything when the symlink doesn't exist
   390  	_, err := eg.TryKernel()
   391  	c.Assert(err, Equals, bootloader.ErrNoTryKernelRef)
   392  
   393  	// when a bad kernel snap name is in the extracted path, it will complain
   394  	// appropriately
   395  	kernelSnapExtractedAssetsDir := filepath.Join(s.grubDir(), "bad_snap_rev_name")
   396  	badKernelSnapPath := filepath.Join(kernelSnapExtractedAssetsDir, "kernel.efi")
   397  	tryKernelSymlink := filepath.Join(s.grubDir(), "try-kernel.efi")
   398  	err = os.MkdirAll(kernelSnapExtractedAssetsDir, 0755)
   399  	c.Assert(err, IsNil)
   400  
   401  	err = ioutil.WriteFile(badKernelSnapPath, nil, 0644)
   402  	c.Assert(err, IsNil)
   403  
   404  	err = os.Symlink("bad_snap_rev_name/kernel.efi", tryKernelSymlink)
   405  	c.Assert(err, IsNil)
   406  
   407  	_, err = eg.TryKernel()
   408  	c.Assert(err, ErrorMatches, "cannot parse kernel snap file name from symlink target \"bad_snap_rev_name\": .*")
   409  
   410  	// remove the bad symlink
   411  	err = os.Remove(tryKernelSymlink)
   412  	c.Assert(err, IsNil)
   413  
   414  	// make a real symlink
   415  	tryKernel := s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_2.snap", "try-kernel.efi")
   416  
   417  	// ensure that the returned kernel is the same as the one we put there
   418  	sn, err := eg.TryKernel()
   419  	c.Assert(err, IsNil)
   420  	c.Assert(sn, DeepEquals, tryKernel)
   421  
   422  	// if the destination of the symlink is removed, we get an error
   423  	err = os.Remove(filepath.Join(s.grubDir(), "pc-kernel_2.snap", "kernel.efi"))
   424  	c.Assert(err, IsNil)
   425  	_, err = eg.TryKernel()
   426  	c.Assert(err, ErrorMatches, "cannot read dangling symlink try-kernel.efi")
   427  }
   428  
   429  func (s *grubTestSuite) TestGrubExtractedRunKernelImageEnableKernel(c *C) {
   430  	s.makeFakeGrubEnv(c)
   431  	g := bootloader.NewGrub(s.rootdir, nil)
   432  	eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader)
   433  	c.Assert(ok, Equals, true)
   434  
   435  	// ensure we fail to create a dangling symlink to a kernel snap that was not
   436  	// actually extracted
   437  	nonExistSnap, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_12.snap")
   438  	c.Assert(err, IsNil)
   439  	err = eg.EnableKernel(nonExistSnap)
   440  	c.Assert(err, ErrorMatches, "cannot enable kernel.efi at pc-kernel_12.snap/kernel.efi: file does not exist")
   441  
   442  	kernel := s.makeKernelAssetSnap(c, "pc-kernel_1.snap")
   443  
   444  	// enable the Kernel we extracted
   445  	err = eg.EnableKernel(kernel)
   446  	c.Assert(err, IsNil)
   447  
   448  	// ensure that the symlink was put where we expect it
   449  	asset, err := os.Readlink(filepath.Join(s.grubDir(), "kernel.efi"))
   450  	c.Assert(err, IsNil)
   451  	c.Assert(asset, DeepEquals, filepath.Join("pc-kernel_1.snap", "kernel.efi"))
   452  
   453  	// create a new kernel snap and ensure that we can safely enable that one
   454  	// too
   455  	kernel2 := s.makeKernelAssetSnap(c, "pc-kernel_2.snap")
   456  	err = eg.EnableKernel(kernel2)
   457  	c.Assert(err, IsNil)
   458  
   459  	// ensure that the symlink was put where we expect it
   460  	asset, err = os.Readlink(filepath.Join(s.grubDir(), "kernel.efi"))
   461  	c.Assert(err, IsNil)
   462  	c.Assert(asset, DeepEquals, filepath.Join("pc-kernel_2.snap", "kernel.efi"))
   463  }
   464  
   465  func (s *grubTestSuite) TestGrubExtractedRunKernelImageEnableTryKernel(c *C) {
   466  	s.makeFakeGrubEnv(c)
   467  	g := bootloader.NewGrub(s.rootdir, nil)
   468  	eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader)
   469  	c.Assert(ok, Equals, true)
   470  
   471  	kernel := s.makeKernelAssetSnap(c, "pc-kernel_1.snap")
   472  
   473  	// enable the Kernel we extracted
   474  	err := eg.EnableTryKernel(kernel)
   475  	c.Assert(err, IsNil)
   476  
   477  	// ensure that the symlink was put where we expect it
   478  	asset, err := os.Readlink(filepath.Join(s.grubDir(), "try-kernel.efi"))
   479  	c.Assert(err, IsNil)
   480  
   481  	c.Assert(asset, DeepEquals, filepath.Join("pc-kernel_1.snap", "kernel.efi"))
   482  }
   483  
   484  func (s *grubTestSuite) TestGrubExtractedRunKernelImageDisableTryKernel(c *C) {
   485  	if os.Geteuid() == 0 {
   486  		c.Skip("the test cannot be run by the root user")
   487  	}
   488  
   489  	s.makeFakeGrubEnv(c)
   490  	g := bootloader.NewGrub(s.rootdir, nil)
   491  	eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader)
   492  	c.Assert(ok, Equals, true)
   493  
   494  	// trying to disable when the try-kernel.efi symlink is missing does not
   495  	// raise any errors
   496  	err := eg.DisableTryKernel()
   497  	c.Assert(err, IsNil)
   498  
   499  	// make the symlink and check that the symlink is missing afterwards
   500  	s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_1.snap", "try-kernel.efi")
   501  	// make sure symlink is there
   502  	c.Assert(filepath.Join(s.grubDir(), "try-kernel.efi"), testutil.FilePresent)
   503  
   504  	err = eg.DisableTryKernel()
   505  	c.Assert(err, IsNil)
   506  
   507  	// ensure that the symlink is no longer there
   508  	c.Assert(filepath.Join(s.grubDir(), "try-kernel.efi"), testutil.FileAbsent)
   509  	c.Assert(filepath.Join(s.grubDir(), "pc-kernel_1.snap/kernel.efi"), testutil.FilePresent)
   510  
   511  	// try again but make sure that the directory cannot be written to
   512  	s.makeKernelAssetSnapAndSymlink(c, "pc-kernel_1.snap", "try-kernel.efi")
   513  	err = os.Chmod(s.grubDir(), 000)
   514  	c.Assert(err, IsNil)
   515  	defer os.Chmod(s.grubDir(), 0755)
   516  
   517  	err = eg.DisableTryKernel()
   518  	c.Assert(err, ErrorMatches, "remove .*/grub/try-kernel.efi: permission denied")
   519  }
   520  
   521  func (s *grubTestSuite) TestKernelExtractionRunImageKernel(c *C) {
   522  	s.makeFakeGrubEnv(c)
   523  
   524  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode})
   525  	c.Assert(g, NotNil)
   526  
   527  	files := [][]string{
   528  		{"kernel.efi", "I'm a kernel"},
   529  		{"another-kernel-file", "another kernel file"},
   530  		{"meta/kernel.yaml", "version: 4.2"},
   531  	}
   532  	si := &snap.SideInfo{
   533  		RealName: "ubuntu-kernel",
   534  		Revision: snap.R(42),
   535  	}
   536  	fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
   537  	snapf, err := snapfile.Open(fn)
   538  	c.Assert(err, IsNil)
   539  
   540  	info, err := snap.ReadInfoFromSnapFile(snapf, si)
   541  	c.Assert(err, IsNil)
   542  
   543  	err = g.ExtractKernelAssets(info, snapf)
   544  	c.Assert(err, IsNil)
   545  
   546  	// kernel is extracted
   547  	kernefi := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "kernel.efi")
   548  	c.Assert(kernefi, testutil.FilePresent)
   549  	// other file is not extracted
   550  	other := filepath.Join(s.bootdir, "grub", "ubuntu-kernel_42.snap", "another-kernel-file")
   551  	c.Assert(other, testutil.FileAbsent)
   552  
   553  	// ensure that removal of assets also works
   554  	err = g.RemoveKernelAssets(info)
   555  	c.Assert(err, IsNil)
   556  	exists, _, err := osutil.DirExists(filepath.Dir(kernefi))
   557  	c.Assert(err, IsNil)
   558  	c.Check(exists, Equals, false)
   559  }
   560  
   561  func (s *grubTestSuite) TestKernelExtractionRunImageKernelNoSlashBoot(c *C) {
   562  	// this is ubuntu-boot but during install we use the native EFI/ubuntu
   563  	// layout, same as Recovery, without the /boot mount
   564  	s.makeFakeGrubEFINativeEnv(c, nil)
   565  
   566  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true})
   567  	c.Assert(g, NotNil)
   568  
   569  	files := [][]string{
   570  		{"kernel.efi", "I'm a kernel"},
   571  		{"another-kernel-file", "another kernel file"},
   572  		{"meta/kernel.yaml", "version: 4.2"},
   573  	}
   574  	si := &snap.SideInfo{
   575  		RealName: "ubuntu-kernel",
   576  		Revision: snap.R(42),
   577  	}
   578  	fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
   579  	snapf, err := snapfile.Open(fn)
   580  	c.Assert(err, IsNil)
   581  
   582  	info, err := snap.ReadInfoFromSnapFile(snapf, si)
   583  	c.Assert(err, IsNil)
   584  
   585  	err = g.ExtractKernelAssets(info, snapf)
   586  	c.Assert(err, IsNil)
   587  
   588  	// kernel is extracted
   589  	kernefi := filepath.Join(s.rootdir, "EFI/ubuntu", "ubuntu-kernel_42.snap", "kernel.efi")
   590  	c.Assert(kernefi, testutil.FilePresent)
   591  	// other file is not extracted
   592  	other := filepath.Join(s.rootdir, "EFI/ubuntu", "ubuntu-kernel_42.snap", "another-kernel-file")
   593  	c.Assert(other, testutil.FileAbsent)
   594  
   595  	// enable the Kernel we extracted
   596  	eg, ok := g.(bootloader.ExtractedRunKernelImageBootloader)
   597  	c.Assert(ok, Equals, true)
   598  	err = eg.EnableKernel(info)
   599  	c.Assert(err, IsNil)
   600  
   601  	// ensure that the symlink was put where we expect it
   602  	asset, err := os.Readlink(filepath.Join(s.rootdir, "EFI/ubuntu", "kernel.efi"))
   603  	c.Assert(err, IsNil)
   604  
   605  	c.Assert(asset, DeepEquals, filepath.Join("ubuntu-kernel_42.snap", "kernel.efi"))
   606  
   607  	// ensure that removal of assets also works
   608  	err = g.RemoveKernelAssets(info)
   609  	c.Assert(err, IsNil)
   610  	exists, _, err := osutil.DirExists(filepath.Dir(kernefi))
   611  	c.Assert(err, IsNil)
   612  	c.Check(exists, Equals, false)
   613  }
   614  
   615  func (s *grubTestSuite) TestListManagedAssets(c *C) {
   616  	s.makeFakeGrubEFINativeEnv(c, []byte(`this is
   617  some random boot config`))
   618  
   619  	opts := &bootloader.Options{NoSlashBoot: true}
   620  	g := bootloader.NewGrub(s.rootdir, opts)
   621  	c.Assert(g, NotNil)
   622  
   623  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   624  	c.Assert(ok, Equals, true)
   625  
   626  	c.Check(tg.ManagedAssets(), DeepEquals, []string{
   627  		"EFI/ubuntu/grub.cfg",
   628  	})
   629  
   630  	opts = &bootloader.Options{Role: bootloader.RoleRecovery}
   631  	tg = bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader)
   632  	c.Check(tg.ManagedAssets(), DeepEquals, []string{
   633  		"EFI/ubuntu/grub.cfg",
   634  	})
   635  
   636  	// as it called for the root fs rather than a mount point of a partition
   637  	// with boot assets
   638  	tg = bootloader.NewGrub(s.rootdir, nil).(bootloader.TrustedAssetsBootloader)
   639  	c.Check(tg.ManagedAssets(), DeepEquals, []string{
   640  		"boot/grub/grub.cfg",
   641  	})
   642  }
   643  
   644  func (s *grubTestSuite) TestRecoveryUpdateBootConfigNoEdition(c *C) {
   645  	// native EFI/ubuntu setup
   646  	s.makeFakeGrubEFINativeEnv(c, []byte("recovery boot script"))
   647  
   648  	opts := &bootloader.Options{Role: bootloader.RoleRecovery}
   649  	g := bootloader.NewGrub(s.rootdir, opts)
   650  	c.Assert(g, NotNil)
   651  
   652  	restore := assets.MockInternal("grub-recovery.cfg", []byte(`# Snapd-Boot-Config-Edition: 5
   653  this is mocked grub-recovery.conf
   654  `))
   655  	defer restore()
   656  
   657  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   658  	c.Assert(ok, Equals, true)
   659  	// install the recovery boot script
   660  	updated, err := tg.UpdateBootConfig()
   661  	c.Assert(err, IsNil)
   662  	c.Assert(updated, Equals, false)
   663  	c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, `recovery boot script`)
   664  }
   665  
   666  func (s *grubTestSuite) TestRecoveryUpdateBootConfigUpdates(c *C) {
   667  	// native EFI/ubuntu setup
   668  	s.makeFakeGrubEFINativeEnv(c, []byte(`# Snapd-Boot-Config-Edition: 1
   669  recovery boot script`))
   670  
   671  	opts := &bootloader.Options{Role: bootloader.RoleRecovery}
   672  	g := bootloader.NewGrub(s.rootdir, opts)
   673  	c.Assert(g, NotNil)
   674  
   675  	restore := assets.MockInternal("grub-recovery.cfg", []byte(`# Snapd-Boot-Config-Edition: 3
   676  this is mocked grub-recovery.conf
   677  `))
   678  	defer restore()
   679  	restore = assets.MockInternal("grub.cfg", []byte(`# Snapd-Boot-Config-Edition: 4
   680  this is mocked grub.conf
   681  `))
   682  	defer restore()
   683  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   684  	c.Assert(ok, Equals, true)
   685  	// install the recovery boot script
   686  	updated, err := tg.UpdateBootConfig()
   687  	c.Assert(err, IsNil)
   688  	c.Assert(updated, Equals, true)
   689  	// the recovery boot asset was picked
   690  	c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, `# Snapd-Boot-Config-Edition: 3
   691  this is mocked grub-recovery.conf
   692  `)
   693  }
   694  
   695  func (s *grubTestSuite) testBootUpdateBootConfigUpdates(c *C, oldConfig, newConfig string, update bool) {
   696  	// native EFI/ubuntu setup
   697  	s.makeFakeGrubEFINativeEnv(c, []byte(oldConfig))
   698  
   699  	opts := &bootloader.Options{NoSlashBoot: true}
   700  	g := bootloader.NewGrub(s.rootdir, opts)
   701  	c.Assert(g, NotNil)
   702  
   703  	restore := assets.MockInternal("grub.cfg", []byte(newConfig))
   704  	defer restore()
   705  
   706  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   707  	c.Assert(ok, Equals, true)
   708  	updated, err := tg.UpdateBootConfig()
   709  	c.Assert(err, IsNil)
   710  	c.Assert(updated, Equals, update)
   711  	if update {
   712  		c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, newConfig)
   713  	} else {
   714  		c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig)
   715  	}
   716  }
   717  
   718  func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigNoUpdateWhenNotManaged(c *C) {
   719  	oldConfig := `not managed`
   720  	newConfig := `# Snapd-Boot-Config-Edition: 3
   721  this update is not applied
   722  `
   723  	// the current boot config is not managed, no update applied
   724  	const updateApplied = false
   725  	s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied)
   726  }
   727  
   728  func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigUpdates(c *C) {
   729  	oldConfig := `# Snapd-Boot-Config-Edition: 2
   730  boot script
   731  `
   732  	// edition is higher, update is applied
   733  	newConfig := `# Snapd-Boot-Config-Edition: 3
   734  this is updated grub.cfg
   735  `
   736  	const updateApplied = true
   737  	s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied)
   738  }
   739  
   740  func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigNoUpdate(c *C) {
   741  	oldConfig := `# Snapd-Boot-Config-Edition: 2
   742  boot script
   743  `
   744  	// edition is lower, no update is applied
   745  	newConfig := `# Snapd-Boot-Config-Edition: 1
   746  this is updated grub.cfg
   747  `
   748  	const updateApplied = false
   749  	s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied)
   750  }
   751  
   752  func (s *grubTestSuite) TestNoSlashBootUpdateBootConfigSameEdition(c *C) {
   753  	oldConfig := `# Snapd-Boot-Config-Edition: 1
   754  boot script
   755  `
   756  	// edition is equal, no update is applied
   757  	newConfig := `# Snapd-Boot-Config-Edition: 1
   758  this is updated grub.cfg
   759  `
   760  	const updateApplied = false
   761  	s.testBootUpdateBootConfigUpdates(c, oldConfig, newConfig, updateApplied)
   762  }
   763  
   764  func (s *grubTestSuite) TestBootUpdateBootConfigTrivialErr(c *C) {
   765  	if os.Geteuid() == 0 {
   766  		c.Skip("the test cannot be run by the root user")
   767  	}
   768  
   769  	oldConfig := `# Snapd-Boot-Config-Edition: 2
   770  boot script
   771  `
   772  	// edition is higher, update is applied
   773  	newConfig := `# Snapd-Boot-Config-Edition: 3
   774  this is updated grub.cfg
   775  `
   776  	// native EFI/ubuntu setup
   777  	s.makeFakeGrubEFINativeEnv(c, []byte(oldConfig))
   778  	restore := assets.MockInternal("grub.cfg", []byte(newConfig))
   779  	defer restore()
   780  
   781  	opts := &bootloader.Options{NoSlashBoot: true}
   782  	g := bootloader.NewGrub(s.rootdir, opts)
   783  	c.Assert(g, NotNil)
   784  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   785  	c.Assert(ok, Equals, true)
   786  
   787  	err := os.Chmod(s.grubEFINativeDir(), 0000)
   788  	c.Assert(err, IsNil)
   789  	defer os.Chmod(s.grubEFINativeDir(), 0755)
   790  
   791  	updated, err := tg.UpdateBootConfig()
   792  	c.Assert(err, ErrorMatches, "cannot load existing config asset: .*/EFI/ubuntu/grub.cfg: permission denied")
   793  	c.Assert(updated, Equals, false)
   794  	err = os.Chmod(s.grubEFINativeDir(), 0555)
   795  	c.Assert(err, IsNil)
   796  
   797  	c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig)
   798  
   799  	// writing out new config fails
   800  	err = os.Chmod(s.grubEFINativeDir(), 0111)
   801  	c.Assert(err, IsNil)
   802  	updated, err = tg.UpdateBootConfig()
   803  	c.Assert(err, ErrorMatches, `open .*/EFI/ubuntu/grub.cfg\..+: permission denied`)
   804  	c.Assert(updated, Equals, false)
   805  	c.Assert(filepath.Join(s.grubEFINativeDir(), "grub.cfg"), testutil.FileEquals, oldConfig)
   806  }
   807  
   808  func (s *grubTestSuite) TestStaticCmdlineForGrubAsset(c *C) {
   809  	restore := assets.MockSnippetsForEdition("grub-asset:static-cmdline", []assets.ForEditions{
   810  		{FirstEdition: 2, Snippet: []byte(`static cmdline "with spaces"`)},
   811  	})
   812  	defer restore()
   813  	cmdline := bootloader.StaticCommandLineForGrubAssetEdition("grub-asset", 1)
   814  	c.Check(cmdline, Equals, ``)
   815  	cmdline = bootloader.StaticCommandLineForGrubAssetEdition("grub-asset", 2)
   816  	c.Check(cmdline, Equals, `static cmdline "with spaces"`)
   817  	cmdline = bootloader.StaticCommandLineForGrubAssetEdition("grub-asset", 4)
   818  	c.Check(cmdline, Equals, `static cmdline "with spaces"`)
   819  }
   820  
   821  func (s *grubTestSuite) TestCommandLineNotManaged(c *C) {
   822  	grubCfg := "boot script\n"
   823  
   824  	// native EFI/ubuntu setup
   825  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
   826  
   827  	restore := assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{
   828  		{FirstEdition: 1, Snippet: []byte(`static=1`)},
   829  		{FirstEdition: 2, Snippet: []byte(`static=2`)},
   830  	})
   831  	defer restore()
   832  	restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{
   833  		{FirstEdition: 1, Snippet: []byte(`static=1 recovery`)},
   834  		{FirstEdition: 2, Snippet: []byte(`static=2 recovery`)},
   835  	})
   836  	defer restore()
   837  
   838  	opts := &bootloader.Options{NoSlashBoot: true}
   839  	mg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader)
   840  
   841  	args, err := mg.CommandLine(bootloader.CommandLineComponents{
   842  		ModeArg:   "snapd_recovery_mode=run",
   843  		ExtraArgs: "extra",
   844  	})
   845  	c.Assert(err, IsNil)
   846  	c.Check(args, Equals, "snapd_recovery_mode=run static=1 extra")
   847  
   848  	optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   849  	mgr := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader)
   850  
   851  	args, err = mgr.CommandLine(bootloader.CommandLineComponents{
   852  		ModeArg:   "snapd_recovery_mode=recover",
   853  		SystemArg: "snapd_recovery_system=1234",
   854  		ExtraArgs: "extra",
   855  	})
   856  	c.Assert(err, IsNil)
   857  	c.Check(args, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 static=1 recovery extra")
   858  }
   859  
   860  func (s *grubTestSuite) TestCommandLineMocked(c *C) {
   861  	grubCfg := `# Snapd-Boot-Config-Edition: 2
   862  boot script
   863  `
   864  	staticCmdline := `arg1   foo=123 panic=-1 arg2="with spaces "`
   865  	staticCmdlineEdition3 := `edition=3 static args`
   866  	restore := assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{
   867  		{FirstEdition: 1, Snippet: []byte(staticCmdline)},
   868  		{FirstEdition: 3, Snippet: []byte(staticCmdlineEdition3)},
   869  	})
   870  	defer restore()
   871  	staticCmdlineRecovery := `recovery config panic=-1`
   872  	restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{
   873  		{FirstEdition: 1, Snippet: []byte(staticCmdlineRecovery)},
   874  	})
   875  	defer restore()
   876  
   877  	// native EFI/ubuntu setup
   878  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
   879  
   880  	optsNoSlashBoot := &bootloader.Options{NoSlashBoot: true}
   881  	g := bootloader.NewGrub(s.rootdir, optsNoSlashBoot)
   882  	c.Assert(g, NotNil)
   883  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   884  	c.Assert(ok, Equals, true)
   885  
   886  	extraArgs := `extra_arg=1  extra_foo=-1   panic=3 baz="more  spaces"`
   887  	args, err := tg.CommandLine(bootloader.CommandLineComponents{
   888  		ModeArg:   "snapd_recovery_mode=run",
   889  		ExtraArgs: extraArgs,
   890  	})
   891  	c.Assert(err, IsNil)
   892  	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"`)
   893  
   894  	// empty mode/system do not produce confusing results
   895  	args, err = tg.CommandLine(bootloader.CommandLineComponents{
   896  		ExtraArgs: extraArgs,
   897  	})
   898  	c.Assert(err, IsNil)
   899  	c.Check(args, Equals, `arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more  spaces"`)
   900  
   901  	// now check the recovery bootloader
   902  	optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   903  	mrg := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader)
   904  	args, err = mrg.CommandLine(bootloader.CommandLineComponents{
   905  		ModeArg:   "snapd_recovery_mode=recover",
   906  		SystemArg: "snapd_recovery_system=20200202",
   907  		ExtraArgs: extraArgs,
   908  	})
   909  	c.Assert(err, IsNil)
   910  	// static command line from recovery asset
   911  	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"`)
   912  
   913  	// try with a different edition
   914  	grubCfg3 := `# Snapd-Boot-Config-Edition: 3
   915  boot script
   916  `
   917  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg3))
   918  	tg = bootloader.NewGrub(s.rootdir, optsNoSlashBoot).(bootloader.TrustedAssetsBootloader)
   919  	c.Assert(g, NotNil)
   920  	extraArgs = `extra_arg=1`
   921  	args, err = tg.CommandLine(bootloader.CommandLineComponents{
   922  		ModeArg:   "snapd_recovery_mode=run",
   923  		ExtraArgs: extraArgs,
   924  	})
   925  	c.Assert(err, IsNil)
   926  	c.Check(args, Equals, `snapd_recovery_mode=run edition=3 static args extra_arg=1`)
   927  
   928  	// full args set overrides static arguments
   929  	args, err = tg.CommandLine(bootloader.CommandLineComponents{
   930  		ModeArg:  "snapd_recovery_mode=run",
   931  		FullArgs: "full for run mode",
   932  	})
   933  	c.Assert(err, IsNil)
   934  	c.Check(args, Equals, `snapd_recovery_mode=run full for run mode`)
   935  	args, err = mrg.CommandLine(bootloader.CommandLineComponents{
   936  		ModeArg:   "snapd_recovery_mode=recover",
   937  		SystemArg: "snapd_recovery_system=20200202",
   938  		FullArgs:  "full for recover mode",
   939  	})
   940  	c.Assert(err, IsNil)
   941  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 full for recover mode`)
   942  
   943  }
   944  
   945  func (s *grubTestSuite) TestCandidateCommandLineMocked(c *C) {
   946  	grubCfg := `# Snapd-Boot-Config-Edition: 1
   947  boot script
   948  `
   949  	// edition on disk
   950  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
   951  
   952  	edition2 := []byte(`# Snapd-Boot-Config-Edition: 2`)
   953  	edition3 := []byte(`# Snapd-Boot-Config-Edition: 3`)
   954  	edition4 := []byte(`# Snapd-Boot-Config-Edition: 4`)
   955  
   956  	restore := assets.MockInternal("grub.cfg", edition2)
   957  	defer restore()
   958  	restore = assets.MockInternal("grub-recovery.cfg", edition2)
   959  	defer restore()
   960  
   961  	restore = assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{
   962  		{FirstEdition: 1, Snippet: []byte(`edition=1`)},
   963  		{FirstEdition: 3, Snippet: []byte(`edition=3`)},
   964  	})
   965  	defer restore()
   966  	restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{
   967  		{FirstEdition: 1, Snippet: []byte(`recovery edition=1`)},
   968  		{FirstEdition: 3, Snippet: []byte(`recovery edition=3`)},
   969  		{FirstEdition: 4, Snippet: []byte(`recovery edition=4up`)},
   970  	})
   971  	defer restore()
   972  
   973  	optsNoSlashBoot := &bootloader.Options{NoSlashBoot: true}
   974  	mg := bootloader.NewGrub(s.rootdir, optsNoSlashBoot).(bootloader.TrustedAssetsBootloader)
   975  	optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   976  	recoverymg := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader)
   977  
   978  	args, err := mg.CandidateCommandLine(bootloader.CommandLineComponents{
   979  		ModeArg:   "snapd_recovery_mode=run",
   980  		ExtraArgs: "extra=1",
   981  	})
   982  	c.Assert(err, IsNil)
   983  	c.Check(args, Equals, `snapd_recovery_mode=run edition=1 extra=1`)
   984  	args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{
   985  		ModeArg:   "snapd_recovery_mode=recover",
   986  		SystemArg: "snapd_recovery_system=20200202",
   987  		ExtraArgs: "extra=1",
   988  	})
   989  	c.Assert(err, IsNil)
   990  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=1 extra=1`)
   991  
   992  	restore = assets.MockInternal("grub.cfg", edition3)
   993  	defer restore()
   994  	restore = assets.MockInternal("grub-recovery.cfg", edition3)
   995  	defer restore()
   996  
   997  	args, err = mg.CandidateCommandLine(bootloader.CommandLineComponents{
   998  		ModeArg:   "snapd_recovery_mode=run",
   999  		ExtraArgs: "extra=1",
  1000  	})
  1001  	c.Assert(err, IsNil)
  1002  	c.Check(args, Equals, `snapd_recovery_mode=run edition=3 extra=1`)
  1003  	args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{
  1004  		ModeArg:   "snapd_recovery_mode=recover",
  1005  		SystemArg: "snapd_recovery_system=20200202",
  1006  		ExtraArgs: "extra=1",
  1007  	})
  1008  	c.Assert(err, IsNil)
  1009  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=3 extra=1`)
  1010  
  1011  	// bump edition only for recovery
  1012  	restore = assets.MockInternal("grub-recovery.cfg", edition4)
  1013  	defer restore()
  1014  	// boot bootloader unchanged
  1015  	args, err = mg.CandidateCommandLine(bootloader.CommandLineComponents{
  1016  		ModeArg:   "snapd_recovery_mode=run",
  1017  		ExtraArgs: "extra=1",
  1018  	})
  1019  	c.Assert(err, IsNil)
  1020  	c.Check(args, Equals, `snapd_recovery_mode=run edition=3 extra=1`)
  1021  	// recovery uses a new edition
  1022  	args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{
  1023  		ModeArg:   "snapd_recovery_mode=recover",
  1024  		SystemArg: "snapd_recovery_system=20200202",
  1025  		ExtraArgs: "extra=1",
  1026  	})
  1027  	c.Assert(err, IsNil)
  1028  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=4up extra=1`)
  1029  
  1030  	// the static snippet is ignored when using full arg set
  1031  	args, err = recoverymg.CandidateCommandLine(bootloader.CommandLineComponents{
  1032  		ModeArg:   "snapd_recovery_mode=recover",
  1033  		SystemArg: "snapd_recovery_system=20200202",
  1034  		FullArgs:  "full args set",
  1035  	})
  1036  	c.Assert(err, IsNil)
  1037  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 full args set`)
  1038  }
  1039  
  1040  func (s *grubTestSuite) TestCommandLineReal(c *C) {
  1041  	grubCfg := `# Snapd-Boot-Config-Edition: 1
  1042  boot script
  1043  `
  1044  	// native EFI/ubuntu setup
  1045  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
  1046  
  1047  	opts := &bootloader.Options{NoSlashBoot: true}
  1048  	g := bootloader.NewGrub(s.rootdir, opts)
  1049  	c.Assert(g, NotNil)
  1050  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
  1051  	c.Assert(ok, Equals, true)
  1052  
  1053  	extraArgs := "foo bar baz=1"
  1054  	args, err := tg.CommandLine(bootloader.CommandLineComponents{
  1055  		ModeArg:   "snapd_recovery_mode=run",
  1056  		ExtraArgs: extraArgs,
  1057  	})
  1058  	c.Assert(err, IsNil)
  1059  	c.Check(args, Equals, `snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz=1`)
  1060  	// with full args the static part is not used
  1061  	args, err = tg.CommandLine(bootloader.CommandLineComponents{
  1062  		ModeArg:  "snapd_recovery_mode=run",
  1063  		FullArgs: "full for run mode",
  1064  	})
  1065  	c.Assert(err, IsNil)
  1066  	c.Check(args, Equals, `snapd_recovery_mode=run full for run mode`)
  1067  
  1068  	// now check the recovery bootloader
  1069  	opts = &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
  1070  	mrg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader)
  1071  	args, err = mrg.CommandLine(bootloader.CommandLineComponents{
  1072  		ModeArg:   "snapd_recovery_mode=recover",
  1073  		SystemArg: "snapd_recovery_system=20200202",
  1074  		ExtraArgs: extraArgs,
  1075  	})
  1076  	c.Assert(err, IsNil)
  1077  	// static command line from recovery asset
  1078  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 console=ttyS0 console=tty1 panic=-1 foo bar baz=1`)
  1079  	// similarly, when passed full args, the static part is not used
  1080  	args, err = mrg.CommandLine(bootloader.CommandLineComponents{
  1081  		ModeArg:   "snapd_recovery_mode=recover",
  1082  		SystemArg: "snapd_recovery_system=20200202",
  1083  		FullArgs:  "full for recover mode",
  1084  	})
  1085  	c.Assert(err, IsNil)
  1086  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 full for recover mode`)
  1087  }
  1088  
  1089  func (s *grubTestSuite) TestCommandLineComponentsValidate(c *C) {
  1090  	grubCfg := `# Snapd-Boot-Config-Edition: 1
  1091  boot script
  1092  `
  1093  	// native EFI/ubuntu setup
  1094  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
  1095  
  1096  	opts := &bootloader.Options{NoSlashBoot: true}
  1097  	g := bootloader.NewGrub(s.rootdir, opts)
  1098  	c.Assert(g, NotNil)
  1099  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
  1100  	c.Assert(ok, Equals, true)
  1101  
  1102  	args, err := tg.CommandLine(bootloader.CommandLineComponents{
  1103  		ModeArg:   "snapd_recovery_mode=run",
  1104  		ExtraArgs: "extra is set",
  1105  		FullArgs:  "full is set",
  1106  	})
  1107  	c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line")
  1108  	c.Check(args, Equals, "")
  1109  	// invalid for the candidate command line too
  1110  	args, err = tg.CandidateCommandLine(bootloader.CommandLineComponents{
  1111  		ModeArg:   "snapd_recovery_mode=run",
  1112  		ExtraArgs: "extra is set",
  1113  		FullArgs:  "full is set",
  1114  	})
  1115  	c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line")
  1116  	c.Check(args, Equals, "")
  1117  
  1118  	// now check the recovery bootloader
  1119  	opts = &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
  1120  	mrg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader)
  1121  	args, err = mrg.CommandLine(bootloader.CommandLineComponents{
  1122  		ModeArg:   "snapd_recovery_mode=recover",
  1123  		SystemArg: "snapd_recovery_system=20200202",
  1124  		ExtraArgs: "extra is set",
  1125  		FullArgs:  "full is set",
  1126  	})
  1127  	c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line")
  1128  	c.Check(args, Equals, "")
  1129  	// candidate recovery command line is checks validity of the components too
  1130  	args, err = mrg.CandidateCommandLine(bootloader.CommandLineComponents{
  1131  		ModeArg:   "snapd_recovery_mode=recover",
  1132  		SystemArg: "snapd_recovery_system=20200202",
  1133  		ExtraArgs: "extra is set",
  1134  		FullArgs:  "full is set",
  1135  	})
  1136  	c.Assert(err, ErrorMatches, "cannot use both full and extra components of command line")
  1137  	c.Check(args, Equals, "")
  1138  }
  1139  
  1140  func (s *grubTestSuite) TestTrustedAssetsNativePartitionLayout(c *C) {
  1141  	// native EFI/ubuntu setup
  1142  	s.makeFakeGrubEFINativeEnv(c, []byte("grub.cfg"))
  1143  	opts := &bootloader.Options{NoSlashBoot: true}
  1144  	g := bootloader.NewGrub(s.rootdir, opts)
  1145  	c.Assert(g, NotNil)
  1146  
  1147  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1148  	c.Assert(ok, Equals, true)
  1149  
  1150  	ta, err := tab.TrustedAssets()
  1151  	c.Assert(err, IsNil)
  1152  	c.Check(ta, DeepEquals, []string{
  1153  		"EFI/boot/grubx64.efi",
  1154  	})
  1155  
  1156  	// recovery bootloader
  1157  	recoveryOpts := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
  1158  	tarb := bootloader.NewGrub(s.rootdir, recoveryOpts).(bootloader.TrustedAssetsBootloader)
  1159  	c.Assert(tarb, NotNil)
  1160  
  1161  	ta, err = tarb.TrustedAssets()
  1162  	c.Assert(err, IsNil)
  1163  	c.Check(ta, DeepEquals, []string{
  1164  		"EFI/boot/bootx64.efi",
  1165  		"EFI/boot/grubx64.efi",
  1166  	})
  1167  
  1168  }
  1169  
  1170  func (s *grubTestSuite) TestTrustedAssetsRoot(c *C) {
  1171  	s.makeFakeGrubEnv(c)
  1172  	g := bootloader.NewGrub(s.rootdir, nil)
  1173  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1174  	c.Assert(ok, Equals, true)
  1175  
  1176  	ta, err := tab.TrustedAssets()
  1177  	c.Assert(err, ErrorMatches, "internal error: trusted assets called without native host-partition layout")
  1178  	c.Check(ta, IsNil)
  1179  }
  1180  
  1181  func (s *grubTestSuite) TestRecoveryBootChains(c *C) {
  1182  	s.makeFakeGrubEFINativeEnv(c, nil)
  1183  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1184  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1185  	c.Assert(ok, Equals, true)
  1186  
  1187  	chain, err := tab.RecoveryBootChain("kernel.snap")
  1188  	c.Assert(err, IsNil)
  1189  	c.Assert(chain, DeepEquals, []bootloader.BootFile{
  1190  		{Path: "EFI/boot/bootx64.efi", Role: bootloader.RoleRecovery},
  1191  		{Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRecovery},
  1192  		{Snap: "kernel.snap", Path: "kernel.efi", Role: bootloader.RoleRecovery},
  1193  	})
  1194  }
  1195  
  1196  func (s *grubTestSuite) TestRecoveryBootChainsNotRecoveryBootloader(c *C) {
  1197  	s.makeFakeGrubEnv(c)
  1198  	g := bootloader.NewGrub(s.rootdir, nil)
  1199  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1200  	c.Assert(ok, Equals, true)
  1201  
  1202  	_, err := tab.RecoveryBootChain("kernel.snap")
  1203  	c.Assert(err, ErrorMatches, "not a recovery bootloader")
  1204  }
  1205  
  1206  func (s *grubTestSuite) TestBootChains(c *C) {
  1207  	s.makeFakeGrubEFINativeEnv(c, nil)
  1208  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1209  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1210  	c.Assert(ok, Equals, true)
  1211  
  1212  	g2 := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode})
  1213  
  1214  	chain, err := tab.BootChain(g2, "kernel.snap")
  1215  	c.Assert(err, IsNil)
  1216  	c.Assert(chain, DeepEquals, []bootloader.BootFile{
  1217  		{Path: "EFI/boot/bootx64.efi", Role: bootloader.RoleRecovery},
  1218  		{Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRecovery},
  1219  		{Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRunMode},
  1220  		{Snap: "kernel.snap", Path: "kernel.efi", Role: bootloader.RoleRunMode},
  1221  	})
  1222  }
  1223  
  1224  func (s *grubTestSuite) TestBootChainsNotRecoveryBootloader(c *C) {
  1225  	s.makeFakeGrubEnv(c)
  1226  	g := bootloader.NewGrub(s.rootdir, nil)
  1227  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1228  	c.Assert(ok, Equals, true)
  1229  
  1230  	g2 := bootloader.NewGrub(s.rootdir, &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRunMode})
  1231  
  1232  	_, err := tab.BootChain(g2, "kernel.snap")
  1233  	c.Assert(err, ErrorMatches, "not a recovery bootloader")
  1234  }