github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/bootloader/grub_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 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("snapd_recovery_mode=run", "", "extra")
   842  	c.Assert(err, IsNil)
   843  	c.Check(args, Equals, "snapd_recovery_mode=run static=1 extra")
   844  
   845  	optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   846  	mgr := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader)
   847  
   848  	args, err = mgr.CommandLine("snapd_recovery_mode=recover", "snapd_recovery_system=1234", "extra")
   849  	c.Assert(err, IsNil)
   850  	c.Check(args, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 static=1 recovery extra")
   851  }
   852  
   853  func (s *grubTestSuite) TestCommandLineMocked(c *C) {
   854  	grubCfg := `# Snapd-Boot-Config-Edition: 2
   855  boot script
   856  `
   857  	staticCmdline := `arg1   foo=123 panic=-1 arg2="with spaces "`
   858  	staticCmdlineEdition3 := `edition=3 static args`
   859  	restore := assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{
   860  		{FirstEdition: 1, Snippet: []byte(staticCmdline)},
   861  		{FirstEdition: 3, Snippet: []byte(staticCmdlineEdition3)},
   862  	})
   863  	defer restore()
   864  	staticCmdlineRecovery := `recovery config panic=-1`
   865  	restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{
   866  		{FirstEdition: 1, Snippet: []byte(staticCmdlineRecovery)},
   867  	})
   868  	defer restore()
   869  
   870  	// native EFI/ubuntu setup
   871  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
   872  
   873  	optsNoSlashBoot := &bootloader.Options{NoSlashBoot: true}
   874  	g := bootloader.NewGrub(s.rootdir, optsNoSlashBoot)
   875  	c.Assert(g, NotNil)
   876  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   877  	c.Assert(ok, Equals, true)
   878  
   879  	extraArgs := `extra_arg=1  extra_foo=-1   panic=3 baz="more  spaces"`
   880  	args, err := tg.CommandLine("snapd_recovery_mode=run", "", extraArgs)
   881  	c.Assert(err, IsNil)
   882  	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"`)
   883  
   884  	// empty mode/system do not produce confusing results
   885  	args, err = tg.CommandLine("", "", extraArgs)
   886  	c.Assert(err, IsNil)
   887  	c.Check(args, Equals, `arg1 foo=123 panic=-1 arg2="with spaces " extra_arg=1 extra_foo=-1 panic=3 baz="more  spaces"`)
   888  
   889  	// now check the recovery bootloader
   890  	optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   891  	mrg := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader)
   892  	args, err = mrg.CommandLine("snapd_recovery_mode=recover", "snapd_recovery_system=20200202", extraArgs)
   893  	c.Assert(err, IsNil)
   894  	// static command line from recovery asset
   895  	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"`)
   896  
   897  	// try with a different edition
   898  	grubCfg3 := `# Snapd-Boot-Config-Edition: 3
   899  boot script
   900  `
   901  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg3))
   902  	tg = bootloader.NewGrub(s.rootdir, optsNoSlashBoot).(bootloader.TrustedAssetsBootloader)
   903  	c.Assert(g, NotNil)
   904  	extraArgs = `extra_arg=1`
   905  	args, err = tg.CommandLine("snapd_recovery_mode=run", "", extraArgs)
   906  	c.Assert(err, IsNil)
   907  	c.Check(args, Equals, `snapd_recovery_mode=run edition=3 static args extra_arg=1`)
   908  }
   909  
   910  func (s *grubTestSuite) TestCandidateCommandLineMocked(c *C) {
   911  	grubCfg := `# Snapd-Boot-Config-Edition: 1
   912  boot script
   913  `
   914  	// edition on disk
   915  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
   916  
   917  	edition2 := []byte(`# Snapd-Boot-Config-Edition: 2`)
   918  	edition3 := []byte(`# Snapd-Boot-Config-Edition: 3`)
   919  	edition4 := []byte(`# Snapd-Boot-Config-Edition: 4`)
   920  
   921  	restore := assets.MockInternal("grub.cfg", edition2)
   922  	defer restore()
   923  	restore = assets.MockInternal("grub-recovery.cfg", edition2)
   924  	defer restore()
   925  
   926  	restore = assets.MockSnippetsForEdition("grub.cfg:static-cmdline", []assets.ForEditions{
   927  		{FirstEdition: 1, Snippet: []byte(`edition=1`)},
   928  		{FirstEdition: 3, Snippet: []byte(`edition=3`)},
   929  	})
   930  	defer restore()
   931  	restore = assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", []assets.ForEditions{
   932  		{FirstEdition: 1, Snippet: []byte(`recovery edition=1`)},
   933  		{FirstEdition: 3, Snippet: []byte(`recovery edition=3`)},
   934  		{FirstEdition: 4, Snippet: []byte(`recovery edition=4up`)},
   935  	})
   936  	defer restore()
   937  
   938  	optsNoSlashBoot := &bootloader.Options{NoSlashBoot: true}
   939  	mg := bootloader.NewGrub(s.rootdir, optsNoSlashBoot).(bootloader.TrustedAssetsBootloader)
   940  	optsRecovery := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   941  	recoverymg := bootloader.NewGrub(s.rootdir, optsRecovery).(bootloader.TrustedAssetsBootloader)
   942  
   943  	args, err := mg.CandidateCommandLine("snapd_recovery_mode=run", "", "extra=1")
   944  	c.Assert(err, IsNil)
   945  	c.Check(args, Equals, `snapd_recovery_mode=run edition=1 extra=1`)
   946  	args, err = recoverymg.CandidateCommandLine("snapd_recovery_mode=recover", "snapd_recovery_system=20200202", "extra=1")
   947  	c.Assert(err, IsNil)
   948  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=1 extra=1`)
   949  
   950  	restore = assets.MockInternal("grub.cfg", edition3)
   951  	defer restore()
   952  	restore = assets.MockInternal("grub-recovery.cfg", edition3)
   953  	defer restore()
   954  
   955  	args, err = mg.CandidateCommandLine("snapd_recovery_mode=run", "", "extra=1")
   956  	c.Assert(err, IsNil)
   957  	c.Check(args, Equals, `snapd_recovery_mode=run edition=3 extra=1`)
   958  	args, err = recoverymg.CandidateCommandLine("snapd_recovery_mode=recover", "snapd_recovery_system=20200202", "extra=1")
   959  	c.Assert(err, IsNil)
   960  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=3 extra=1`)
   961  
   962  	// bump edition only for recovery
   963  	restore = assets.MockInternal("grub-recovery.cfg", edition4)
   964  	defer restore()
   965  	// boot bootloader unchanged
   966  	args, err = mg.CandidateCommandLine("snapd_recovery_mode=run", "", "extra=1")
   967  	c.Assert(err, IsNil)
   968  	c.Check(args, Equals, `snapd_recovery_mode=run edition=3 extra=1`)
   969  	// recovery uses a new edition
   970  	args, err = recoverymg.CandidateCommandLine("snapd_recovery_mode=recover", "snapd_recovery_system=20200202", "extra=1")
   971  	c.Assert(err, IsNil)
   972  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 recovery edition=4up extra=1`)
   973  }
   974  
   975  func (s *grubTestSuite) TestCommandLineReal(c *C) {
   976  	grubCfg := `# Snapd-Boot-Config-Edition: 1
   977  boot script
   978  `
   979  	// native EFI/ubuntu setup
   980  	s.makeFakeGrubEFINativeEnv(c, []byte(grubCfg))
   981  
   982  	opts := &bootloader.Options{NoSlashBoot: true}
   983  	g := bootloader.NewGrub(s.rootdir, opts)
   984  	c.Assert(g, NotNil)
   985  	tg, ok := g.(bootloader.TrustedAssetsBootloader)
   986  	c.Assert(ok, Equals, true)
   987  
   988  	extraArgs := "foo bar baz=1"
   989  	args, err := tg.CommandLine("snapd_recovery_mode=run", "", extraArgs)
   990  	c.Assert(err, IsNil)
   991  	c.Check(args, Equals, `snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz=1`)
   992  
   993  	// now check the recovery bootloader
   994  	opts = &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
   995  	mrg := bootloader.NewGrub(s.rootdir, opts).(bootloader.TrustedAssetsBootloader)
   996  	args, err = mrg.CommandLine("snapd_recovery_mode=recover", "snapd_recovery_system=20200202", extraArgs)
   997  	c.Assert(err, IsNil)
   998  	// static command line from recovery asset
   999  	c.Check(args, Equals, `snapd_recovery_mode=recover snapd_recovery_system=20200202 console=ttyS0 console=tty1 panic=-1 foo bar baz=1`)
  1000  }
  1001  
  1002  func (s *grubTestSuite) TestTrustedAssetsNativePartitionLayout(c *C) {
  1003  	// native EFI/ubuntu setup
  1004  	s.makeFakeGrubEFINativeEnv(c, []byte("grub.cfg"))
  1005  	opts := &bootloader.Options{NoSlashBoot: true}
  1006  	g := bootloader.NewGrub(s.rootdir, opts)
  1007  	c.Assert(g, NotNil)
  1008  
  1009  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1010  	c.Assert(ok, Equals, true)
  1011  
  1012  	ta, err := tab.TrustedAssets()
  1013  	c.Assert(err, IsNil)
  1014  	c.Check(ta, DeepEquals, []string{
  1015  		"EFI/boot/grubx64.efi",
  1016  	})
  1017  
  1018  	// recovery bootloader
  1019  	recoveryOpts := &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRecovery}
  1020  	tarb := bootloader.NewGrub(s.rootdir, recoveryOpts).(bootloader.TrustedAssetsBootloader)
  1021  	c.Assert(tarb, NotNil)
  1022  
  1023  	ta, err = tarb.TrustedAssets()
  1024  	c.Assert(err, IsNil)
  1025  	c.Check(ta, DeepEquals, []string{
  1026  		"EFI/boot/bootx64.efi",
  1027  		"EFI/boot/grubx64.efi",
  1028  	})
  1029  
  1030  }
  1031  
  1032  func (s *grubTestSuite) TestTrustedAssetsRoot(c *C) {
  1033  	s.makeFakeGrubEnv(c)
  1034  	g := bootloader.NewGrub(s.rootdir, nil)
  1035  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1036  	c.Assert(ok, Equals, true)
  1037  
  1038  	ta, err := tab.TrustedAssets()
  1039  	c.Assert(err, ErrorMatches, "internal error: trusted assets called without native host-partition layout")
  1040  	c.Check(ta, IsNil)
  1041  }
  1042  
  1043  func (s *grubTestSuite) TestRecoveryBootChains(c *C) {
  1044  	s.makeFakeGrubEFINativeEnv(c, nil)
  1045  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1046  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1047  	c.Assert(ok, Equals, true)
  1048  
  1049  	chain, err := tab.RecoveryBootChain("kernel.snap")
  1050  	c.Assert(err, IsNil)
  1051  	c.Assert(chain, DeepEquals, []bootloader.BootFile{
  1052  		{Path: "EFI/boot/bootx64.efi", Role: bootloader.RoleRecovery},
  1053  		{Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRecovery},
  1054  		{Snap: "kernel.snap", Path: "kernel.efi", Role: bootloader.RoleRecovery},
  1055  	})
  1056  }
  1057  
  1058  func (s *grubTestSuite) TestRecoveryBootChainsNotRecoveryBootloader(c *C) {
  1059  	s.makeFakeGrubEnv(c)
  1060  	g := bootloader.NewGrub(s.rootdir, nil)
  1061  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1062  	c.Assert(ok, Equals, true)
  1063  
  1064  	_, err := tab.RecoveryBootChain("kernel.snap")
  1065  	c.Assert(err, ErrorMatches, "not a recovery bootloader")
  1066  }
  1067  
  1068  func (s *grubTestSuite) TestBootChains(c *C) {
  1069  	s.makeFakeGrubEFINativeEnv(c, nil)
  1070  	g := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRecovery})
  1071  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1072  	c.Assert(ok, Equals, true)
  1073  
  1074  	g2 := bootloader.NewGrub(s.rootdir, &bootloader.Options{Role: bootloader.RoleRunMode})
  1075  
  1076  	chain, err := tab.BootChain(g2, "kernel.snap")
  1077  	c.Assert(err, IsNil)
  1078  	c.Assert(chain, DeepEquals, []bootloader.BootFile{
  1079  		{Path: "EFI/boot/bootx64.efi", Role: bootloader.RoleRecovery},
  1080  		{Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRecovery},
  1081  		{Path: "EFI/boot/grubx64.efi", Role: bootloader.RoleRunMode},
  1082  		{Snap: "kernel.snap", Path: "kernel.efi", Role: bootloader.RoleRunMode},
  1083  	})
  1084  }
  1085  
  1086  func (s *grubTestSuite) TestBootChainsNotRecoveryBootloader(c *C) {
  1087  	s.makeFakeGrubEnv(c)
  1088  	g := bootloader.NewGrub(s.rootdir, nil)
  1089  	tab, ok := g.(bootloader.TrustedAssetsBootloader)
  1090  	c.Assert(ok, Equals, true)
  1091  
  1092  	g2 := bootloader.NewGrub(s.rootdir, &bootloader.Options{NoSlashBoot: true, Role: bootloader.RoleRunMode})
  1093  
  1094  	_, err := tab.BootChain(g2, "kernel.snap")
  1095  	c.Assert(err, ErrorMatches, "not a recovery bootloader")
  1096  }