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