github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/makebootable_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 boot_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/boot/boottest"
    33  	"github.com/snapcore/snapd/bootloader"
    34  	"github.com/snapcore/snapd/bootloader/assets"
    35  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    36  	"github.com/snapcore/snapd/bootloader/grubenv"
    37  	"github.com/snapcore/snapd/bootloader/ubootenv"
    38  	"github.com/snapcore/snapd/dirs"
    39  	"github.com/snapcore/snapd/gadget"
    40  	"github.com/snapcore/snapd/osutil"
    41  	"github.com/snapcore/snapd/secboot"
    42  	"github.com/snapcore/snapd/seed"
    43  	"github.com/snapcore/snapd/snap"
    44  	"github.com/snapcore/snapd/snap/snapfile"
    45  	"github.com/snapcore/snapd/snap/snaptest"
    46  	"github.com/snapcore/snapd/testutil"
    47  	"github.com/snapcore/snapd/timings"
    48  )
    49  
    50  type makeBootableSuite struct {
    51  	baseBootenvSuite
    52  
    53  	bootloader *bootloadertest.MockBootloader
    54  }
    55  
    56  var _ = Suite(&makeBootableSuite{})
    57  
    58  func (s *makeBootableSuite) SetUpTest(c *C) {
    59  	s.baseBootenvSuite.SetUpTest(c)
    60  
    61  	s.bootloader = bootloadertest.Mock("mock", c.MkDir())
    62  	s.forceBootloader(s.bootloader)
    63  }
    64  
    65  func makeSnap(c *C, name, yaml string, revno snap.Revision) (fn string, info *snap.Info) {
    66  	return makeSnapWithFiles(c, name, yaml, revno, nil)
    67  }
    68  
    69  func makeSnapWithFiles(c *C, name, yaml string, revno snap.Revision, files [][]string) (fn string, info *snap.Info) {
    70  	si := &snap.SideInfo{
    71  		RealName: name,
    72  		Revision: revno,
    73  	}
    74  	fn = snaptest.MakeTestSnapWithFiles(c, yaml, files)
    75  	snapf, err := snapfile.Open(fn)
    76  	c.Assert(err, IsNil)
    77  	info, err = snap.ReadInfoFromSnapFile(snapf, si)
    78  	c.Assert(err, IsNil)
    79  	return fn, info
    80  }
    81  
    82  func (s *makeBootableSuite) TestMakeBootableImage(c *C) {
    83  	bootloader.Force(nil)
    84  	model := boottest.MakeMockModel()
    85  
    86  	grubCfg := []byte("#grub cfg")
    87  	unpackedGadgetDir := c.MkDir()
    88  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
    89  	c.Assert(err, IsNil)
    90  
    91  	seedSnapsDirs := filepath.Join(s.rootdir, "/var/lib/snapd/seed", "snaps")
    92  	err = os.MkdirAll(seedSnapsDirs, 0755)
    93  	c.Assert(err, IsNil)
    94  
    95  	baseFn, baseInfo := makeSnap(c, "core18", `name: core18
    96  type: base
    97  version: 4.0
    98  `, snap.R(3))
    99  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   100  	err = os.Rename(baseFn, baseInSeed)
   101  	c.Assert(err, IsNil)
   102  	kernelFn, kernelInfo := makeSnap(c, "pc-kernel", `name: pc-kernel
   103  type: kernel
   104  version: 4.0
   105  `, snap.R(5))
   106  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   107  	err = os.Rename(kernelFn, kernelInSeed)
   108  	c.Assert(err, IsNil)
   109  
   110  	bootWith := &boot.BootableSet{
   111  		Base:              baseInfo,
   112  		BasePath:          baseInSeed,
   113  		Kernel:            kernelInfo,
   114  		KernelPath:        kernelInSeed,
   115  		UnpackedGadgetDir: unpackedGadgetDir,
   116  	}
   117  
   118  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   119  	c.Assert(err, IsNil)
   120  
   121  	// check the bootloader config
   122  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "boot/grub/grubenv"))
   123  	c.Assert(seedGenv.Load(), IsNil)
   124  	c.Check(seedGenv.Get("snap_kernel"), Equals, "pc-kernel_5.snap")
   125  	c.Check(seedGenv.Get("snap_core"), Equals, "core18_3.snap")
   126  	c.Check(seedGenv.Get("snap_menuentry"), Equals, "My Model")
   127  
   128  	// check symlinks from snap blob dir
   129  	kernelBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename())
   130  	dst, err := os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename()))
   131  	c.Assert(err, IsNil)
   132  	c.Check(dst, Equals, "../seed/snaps/pc-kernel_5.snap")
   133  	c.Check(kernelBlob, testutil.FilePresent)
   134  
   135  	baseBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename())
   136  	dst, err = os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename()))
   137  	c.Assert(err, IsNil)
   138  	c.Check(dst, Equals, "../seed/snaps/core18_3.snap")
   139  	c.Check(baseBlob, testutil.FilePresent)
   140  
   141  	// check that the bootloader (grub here) configuration was copied
   142  	c.Check(filepath.Join(s.rootdir, "boot", "grub/grub.cfg"), testutil.FileEquals, grubCfg)
   143  }
   144  
   145  type makeBootable20Suite struct {
   146  	baseBootenvSuite
   147  
   148  	bootloader *bootloadertest.MockRecoveryAwareBootloader
   149  }
   150  
   151  type makeBootable20UbootSuite struct {
   152  	baseBootenvSuite
   153  
   154  	bootloader *bootloadertest.MockExtractedRecoveryKernelImageBootloader
   155  }
   156  
   157  var _ = Suite(&makeBootable20Suite{})
   158  var _ = Suite(&makeBootable20UbootSuite{})
   159  
   160  func (s *makeBootable20Suite) SetUpTest(c *C) {
   161  	s.baseBootenvSuite.SetUpTest(c)
   162  
   163  	s.bootloader = bootloadertest.Mock("mock", c.MkDir()).RecoveryAware()
   164  	s.forceBootloader(s.bootloader)
   165  }
   166  
   167  func (s *makeBootable20UbootSuite) SetUpTest(c *C) {
   168  	s.baseBootenvSuite.SetUpTest(c)
   169  
   170  	s.bootloader = bootloadertest.Mock("mock", c.MkDir()).ExtractedRecoveryKernelImage()
   171  	s.forceBootloader(s.bootloader)
   172  }
   173  
   174  func (s *makeBootable20Suite) TestMakeBootableImage20(c *C) {
   175  	bootloader.Force(nil)
   176  	model := boottest.MakeMockUC20Model()
   177  
   178  	unpackedGadgetDir := c.MkDir()
   179  	grubRecoveryCfg := "#grub-recovery cfg"
   180  	grubRecoveryCfgAsset := "#grub-recovery cfg from assets"
   181  	grubCfg := "#grub cfg"
   182  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   183  		{"grub-recovery.conf", grubRecoveryCfg},
   184  		{"grub.conf", grubCfg},
   185  		{"meta/snap.yaml", gadgetSnapYaml},
   186  	})
   187  	restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset))
   188  	defer restore()
   189  
   190  	// on uc20 the seed layout if different
   191  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   192  	err := os.MkdirAll(seedSnapsDirs, 0755)
   193  	c.Assert(err, IsNil)
   194  
   195  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   196  type: base
   197  version: 5.0
   198  `, snap.R(3))
   199  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   200  	err = os.Rename(baseFn, baseInSeed)
   201  	c.Assert(err, IsNil)
   202  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   203  type: kernel
   204  version: 5.0
   205  `, snap.R(5), [][]string{
   206  		{"kernel.efi", "I'm a kernel.efi"},
   207  	})
   208  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   209  	err = os.Rename(kernelFn, kernelInSeed)
   210  	c.Assert(err, IsNil)
   211  
   212  	label := "20191209"
   213  	recoverySystemDir := filepath.Join("/systems", label)
   214  	bootWith := &boot.BootableSet{
   215  		Base:                baseInfo,
   216  		BasePath:            baseInSeed,
   217  		Kernel:              kernelInfo,
   218  		KernelPath:          kernelInSeed,
   219  		RecoverySystemDir:   recoverySystemDir,
   220  		RecoverySystemLabel: label,
   221  		UnpackedGadgetDir:   unpackedGadgetDir,
   222  		Recovery:            true,
   223  	}
   224  
   225  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   226  	c.Assert(err, IsNil)
   227  
   228  	// ensure only a single file got copied (the grub.cfg)
   229  	files, err := filepath.Glob(filepath.Join(s.rootdir, "EFI/ubuntu/*"))
   230  	c.Assert(err, IsNil)
   231  	// grub.cfg and grubenv
   232  	c.Check(files, HasLen, 2)
   233  	// check that the recovery bootloader configuration was installed with
   234  	// the correct content
   235  	c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset)
   236  
   237  	// ensure no /boot was setup
   238  	c.Check(filepath.Join(s.rootdir, "boot"), testutil.FileAbsent)
   239  
   240  	// ensure the correct recovery system configuration was set
   241  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   242  	c.Assert(seedGenv.Load(), IsNil)
   243  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   244  
   245  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   246  	c.Assert(systemGenv.Load(), IsNil)
   247  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   248  }
   249  
   250  func (s *makeBootable20Suite) TestMakeBootableImage20BootFlags(c *C) {
   251  	bootloader.Force(nil)
   252  	model := boottest.MakeMockUC20Model()
   253  
   254  	unpackedGadgetDir := c.MkDir()
   255  	grubRecoveryCfg := "#grub-recovery cfg"
   256  	grubRecoveryCfgAsset := "#grub-recovery cfg from assets"
   257  	grubCfg := "#grub cfg"
   258  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   259  		{"grub-recovery.conf", grubRecoveryCfg},
   260  		{"grub.conf", grubCfg},
   261  		{"meta/snap.yaml", gadgetSnapYaml},
   262  	})
   263  	restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset))
   264  	defer restore()
   265  
   266  	// on uc20 the seed layout if different
   267  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   268  	err := os.MkdirAll(seedSnapsDirs, 0755)
   269  	c.Assert(err, IsNil)
   270  
   271  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   272  type: base
   273  version: 5.0
   274  `, snap.R(3))
   275  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   276  	err = os.Rename(baseFn, baseInSeed)
   277  	c.Assert(err, IsNil)
   278  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   279  type: kernel
   280  version: 5.0
   281  `, snap.R(5), [][]string{
   282  		{"kernel.efi", "I'm a kernel.efi"},
   283  	})
   284  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   285  	err = os.Rename(kernelFn, kernelInSeed)
   286  	c.Assert(err, IsNil)
   287  
   288  	label := "20191209"
   289  	recoverySystemDir := filepath.Join("/systems", label)
   290  	bootWith := &boot.BootableSet{
   291  		Base:                baseInfo,
   292  		BasePath:            baseInSeed,
   293  		Kernel:              kernelInfo,
   294  		KernelPath:          kernelInSeed,
   295  		RecoverySystemDir:   recoverySystemDir,
   296  		RecoverySystemLabel: label,
   297  		UnpackedGadgetDir:   unpackedGadgetDir,
   298  		Recovery:            true,
   299  	}
   300  	bootFlags := []string{"factory"}
   301  
   302  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, bootFlags)
   303  	c.Assert(err, IsNil)
   304  
   305  	// ensure the correct recovery system configuration was set
   306  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   307  	c.Assert(seedGenv.Load(), IsNil)
   308  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   309  	c.Check(seedGenv.Get("snapd_boot_flags"), Equals, "factory")
   310  
   311  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   312  	c.Assert(systemGenv.Load(), IsNil)
   313  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   314  
   315  }
   316  
   317  func (s *makeBootable20Suite) testMakeBootableImage20CustomKernelArgs(c *C, whichFile, content, errMsg string) {
   318  	bootloader.Force(nil)
   319  	model := boottest.MakeMockUC20Model()
   320  
   321  	unpackedGadgetDir := c.MkDir()
   322  	grubCfg := "#grub cfg"
   323  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   324  		{"grub.conf", grubCfg},
   325  		{"meta/snap.yaml", gadgetSnapYaml},
   326  		{whichFile, content},
   327  	})
   328  
   329  	// on uc20 the seed layout if different
   330  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   331  	err := os.MkdirAll(seedSnapsDirs, 0755)
   332  	c.Assert(err, IsNil)
   333  
   334  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   335  type: base
   336  version: 5.0
   337  `, snap.R(3))
   338  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   339  	err = os.Rename(baseFn, baseInSeed)
   340  	c.Assert(err, IsNil)
   341  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   342  type: kernel
   343  version: 5.0
   344  `, snap.R(5), [][]string{
   345  		{"kernel.efi", "I'm a kernel.efi"},
   346  	})
   347  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   348  	err = os.Rename(kernelFn, kernelInSeed)
   349  	c.Assert(err, IsNil)
   350  
   351  	label := "20191209"
   352  	recoverySystemDir := filepath.Join("/systems", label)
   353  	bootWith := &boot.BootableSet{
   354  		Base:                baseInfo,
   355  		BasePath:            baseInSeed,
   356  		Kernel:              kernelInfo,
   357  		KernelPath:          kernelInSeed,
   358  		RecoverySystemDir:   recoverySystemDir,
   359  		RecoverySystemLabel: label,
   360  		UnpackedGadgetDir:   unpackedGadgetDir,
   361  		Recovery:            true,
   362  	}
   363  
   364  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   365  	if errMsg != "" {
   366  		c.Assert(err, ErrorMatches, errMsg)
   367  		return
   368  	}
   369  	c.Assert(err, IsNil)
   370  
   371  	// ensure the correct recovery system configuration was set
   372  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   373  	c.Assert(seedGenv.Load(), IsNil)
   374  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   375  	// and kernel command line
   376  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   377  	c.Assert(systemGenv.Load(), IsNil)
   378  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   379  	switch whichFile {
   380  	case "cmdline.extra":
   381  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content)
   382  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "")
   383  	case "cmdline.full":
   384  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
   385  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content)
   386  	}
   387  }
   388  
   389  func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelExtraArgs(c *C) {
   390  	s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "foo bar baz", "")
   391  }
   392  
   393  func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelFullArgs(c *C) {
   394  	s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.full", "foo bar baz", "")
   395  }
   396  
   397  func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelInvalidArgs(c *C) {
   398  	errMsg := `cannot obtain recovery system command line: cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument "snapd_foo=bar"`
   399  	s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "snapd_foo=bar", errMsg)
   400  }
   401  
   402  func (s *makeBootable20Suite) TestMakeBootableImage20UnsetRecoverySystemLabelError(c *C) {
   403  	model := boottest.MakeMockUC20Model()
   404  
   405  	unpackedGadgetDir := c.MkDir()
   406  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   407  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644)
   408  	c.Assert(err, IsNil)
   409  	grubCfg := []byte("#grub cfg")
   410  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   411  	c.Assert(err, IsNil)
   412  
   413  	label := "20191209"
   414  	recoverySystemDir := filepath.Join("/systems", label)
   415  	bootWith := &boot.BootableSet{
   416  		RecoverySystemDir: recoverySystemDir,
   417  		UnpackedGadgetDir: unpackedGadgetDir,
   418  		Recovery:          true,
   419  	}
   420  
   421  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   422  	c.Assert(err, ErrorMatches, "internal error: recovery system label unset")
   423  }
   424  
   425  func (s *makeBootable20Suite) TestMakeBootableImage20MultipleRecoverySystemsError(c *C) {
   426  	model := boottest.MakeMockUC20Model()
   427  
   428  	bootWith := &boot.BootableSet{Recovery: true}
   429  	err := os.MkdirAll(filepath.Join(s.rootdir, "systems/20191204"), 0755)
   430  	c.Assert(err, IsNil)
   431  	err = os.MkdirAll(filepath.Join(s.rootdir, "systems/20191205"), 0755)
   432  	c.Assert(err, IsNil)
   433  
   434  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   435  	c.Assert(err, ErrorMatches, "cannot make multiple recovery systems bootable yet")
   436  }
   437  
   438  func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) {
   439  	model := boottest.MakeMockModel()
   440  
   441  	err := boot.MakeRunnableSystem(model, nil, nil)
   442  	c.Assert(err, ErrorMatches, "internal error: cannot make non-uc20 system runnable")
   443  }
   444  
   445  func (s *makeBootable20Suite) TestMakeSystemRunnable20(c *C) {
   446  	bootloader.Force(nil)
   447  
   448  	model := boottest.MakeMockUC20Model()
   449  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   450  	err := os.MkdirAll(seedSnapsDirs, 0755)
   451  	c.Assert(err, IsNil)
   452  
   453  	// grub on ubuntu-seed
   454  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   455  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   456  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   457  	c.Assert(err, IsNil)
   458  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   459  	c.Assert(err, IsNil)
   460  
   461  	// setup recovery boot assets
   462  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   463  	c.Assert(err, IsNil)
   464  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
   465  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
   466  		[]byte("recovery shim content"), 0644)
   467  	c.Assert(err, IsNil)
   468  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
   469  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
   470  		[]byte("recovery grub content"), 0644)
   471  	c.Assert(err, IsNil)
   472  
   473  	// grub on ubuntu-boot
   474  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   475  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   476  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   477  	c.Assert(err, IsNil)
   478  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   479  	c.Assert(err, IsNil)
   480  
   481  	unpackedGadgetDir := c.MkDir()
   482  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   483  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   484  	grubCfg := []byte("#grub cfg")
   485  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
   486  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   487  		{"grub-recovery.conf", string(grubRecoveryCfg)},
   488  		{"grub.conf", string(grubCfg)},
   489  		{"bootx64.efi", "shim content"},
   490  		{"grubx64.efi", "grub content"},
   491  		{"meta/snap.yaml", gadgetSnapYaml},
   492  	})
   493  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
   494  	defer restore()
   495  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
   496  	defer restore()
   497  
   498  	// make the snaps symlinks so that we can ensure that makebootable follows
   499  	// the symlinks and copies the files and not the symlinks
   500  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   501  type: base
   502  version: 5.0
   503  `, snap.R(3))
   504  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   505  	err = os.Symlink(baseFn, baseInSeed)
   506  	c.Assert(err, IsNil)
   507  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   508  type: kernel
   509  version: 5.0
   510  `, snap.R(5),
   511  		[][]string{
   512  			{"kernel.efi", "I'm a kernel.efi"},
   513  		},
   514  	)
   515  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   516  	err = os.Symlink(kernelFn, kernelInSeed)
   517  	c.Assert(err, IsNil)
   518  
   519  	bootWith := &boot.BootableSet{
   520  		RecoverySystemDir: "20191216",
   521  		BasePath:          baseInSeed,
   522  		Base:              baseInfo,
   523  		KernelPath:        kernelInSeed,
   524  		Kernel:            kernelInfo,
   525  		Recovery:          false,
   526  		UnpackedGadgetDir: unpackedGadgetDir,
   527  	}
   528  
   529  	// set up observer state
   530  	useEncryption := true
   531  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
   532  	c.Assert(obs, NotNil)
   533  	c.Assert(err, IsNil)
   534  	runBootStruct := &gadget.LaidOutStructure{
   535  		VolumeStructure: &gadget.VolumeStructure{
   536  			Role: gadget.SystemBoot,
   537  		},
   538  	}
   539  
   540  	// only grubx64.efi gets installed to system-boot
   541  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
   542  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
   543  	c.Assert(err, IsNil)
   544  
   545  	// observe recovery assets
   546  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
   547  	c.Assert(err, IsNil)
   548  
   549  	// set encryption key
   550  	myKey := secboot.EncryptionKey{}
   551  	myKey2 := secboot.EncryptionKey{}
   552  	for i := range myKey {
   553  		myKey[i] = byte(i)
   554  		myKey2[i] = byte(128 + i)
   555  	}
   556  	obs.ChosenEncryptionKeys(myKey, myKey2)
   557  
   558  	// set a mock recovery kernel
   559  	readSystemEssentialCalls := 0
   560  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   561  		readSystemEssentialCalls++
   562  		return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
   563  	})
   564  	defer restore()
   565  
   566  	// set mock key sealing
   567  	sealKeysCalls := 0
   568  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   569  		sealKeysCalls++
   570  		switch sealKeysCalls {
   571  		case 1:
   572  			c.Check(keys, HasLen, 1)
   573  			c.Check(keys[0].Key, DeepEquals, myKey)
   574  		case 2:
   575  			c.Check(keys, HasLen, 2)
   576  			c.Check(keys[0].Key, DeepEquals, myKey)
   577  			c.Check(keys[1].Key, DeepEquals, myKey2)
   578  		default:
   579  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   580  		}
   581  		c.Assert(params.ModelParams, HasLen, 1)
   582  
   583  		shim := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   584  			"var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"),
   585  			bootloader.RoleRecovery)
   586  		grub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   587  			"var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"),
   588  			bootloader.RoleRecovery)
   589  		runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   590  			"var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"),
   591  			bootloader.RoleRunMode)
   592  		kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   593  		runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode)
   594  
   595  		switch sealKeysCalls {
   596  		case 1:
   597  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   598  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   599  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))),
   600  			})
   601  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   602  				"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   603  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   604  			})
   605  		case 2:
   606  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   607  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   608  			})
   609  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   610  				"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   611  			})
   612  		default:
   613  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   614  		}
   615  
   616  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   617  
   618  		return nil
   619  	})
   620  	defer restore()
   621  
   622  	err = boot.MakeRunnableSystem(model, bootWith, obs)
   623  	c.Assert(err, IsNil)
   624  
   625  	// also do the logical thing and make the next boot go to run mode
   626  	err = boot.EnsureNextBootToRunMode("20191216")
   627  	c.Assert(err, IsNil)
   628  
   629  	// ensure grub.cfg in boot was installed from internal assets
   630  	c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset))
   631  
   632  	// ensure base/kernel got copied to /var/lib/snapd/snaps
   633  	core20Snap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap")
   634  	pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "pc-kernel_5.snap")
   635  	c.Check(core20Snap, testutil.FilePresent)
   636  	c.Check(pcKernelSnap, testutil.FilePresent)
   637  	c.Check(osutil.IsSymlink(core20Snap), Equals, false)
   638  	c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false)
   639  
   640  	// ensure the bootvars got updated the right way
   641  	mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
   642  	c.Check(mockSeedGrubenv, testutil.FilePresent)
   643  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run")
   644  	mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv")
   645  	c.Check(mockBootGrubenv, testutil.FilePresent)
   646  
   647  	// ensure that kernel_status is empty, we specifically want this to be set
   648  	// to the empty string
   649  	// use (?m) to match multi-line file in the regex here, because the file is
   650  	// a grubenv with padding #### blocks
   651  	c.Check(mockBootGrubenv, testutil.FileMatches, `(?m)^kernel_status=$`)
   652  
   653  	// check that we have the extracted kernel in the right places, both in the
   654  	// old uc16/uc18 location and the new ubuntu-boot partition grub dir
   655  	extractedKernel := filepath.Join(mockBootGrubDir, "pc-kernel_5.snap", "kernel.efi")
   656  	c.Check(extractedKernel, testutil.FilePresent)
   657  
   658  	// the new uc20 location
   659  	extractedKernelSymlink := filepath.Join(mockBootGrubDir, "kernel.efi")
   660  	c.Check(extractedKernelSymlink, testutil.FilePresent)
   661  
   662  	// ensure modeenv looks correct
   663  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
   664  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run
   665  recovery_system=20191216
   666  current_recovery_systems=20191216
   667  good_recovery_systems=20191216
   668  base=core20_3.snap
   669  current_kernels=pc-kernel_5.snap
   670  model=my-brand/my-model-uc20
   671  grade=dangerous
   672  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   673  current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]}
   674  current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]}
   675  current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"]
   676  `)
   677  	copiedGrubBin := filepath.Join(
   678  		dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir),
   679  		"grub",
   680  		"grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d",
   681  	)
   682  	copiedRecoveryGrubBin := filepath.Join(
   683  		dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir),
   684  		"grub",
   685  		"grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5",
   686  	)
   687  	copiedRecoveryShimBin := filepath.Join(
   688  		dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir),
   689  		"grub",
   690  		"bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37",
   691  	)
   692  
   693  	// only one file in the cache under new root
   694  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), "grub", "*"), []string{
   695  		copiedRecoveryShimBin,
   696  		copiedGrubBin,
   697  		copiedRecoveryGrubBin,
   698  	})
   699  	// with the right content
   700  	c.Check(copiedGrubBin, testutil.FileEquals, "grub content")
   701  	c.Check(copiedRecoveryGrubBin, testutil.FileEquals, "recovery grub content")
   702  	c.Check(copiedRecoveryShimBin, testutil.FileEquals, "recovery shim content")
   703  
   704  	// make sure SealKey was called for the run object and the fallback object
   705  	c.Check(sealKeysCalls, Equals, 2)
   706  
   707  	// make sure the marker file for sealed key was created
   708  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent)
   709  
   710  	// make sure we wrote the boot chains data file
   711  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent)
   712  }
   713  
   714  func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) {
   715  	bootloader.Force(nil)
   716  
   717  	model := boottest.MakeMockUC20Model()
   718  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   719  	err := os.MkdirAll(seedSnapsDirs, 0755)
   720  	c.Assert(err, IsNil)
   721  
   722  	// grub on ubuntu-seed
   723  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   724  	err = os.MkdirAll(mockSeedGrubDir, 0755)
   725  	c.Assert(err, IsNil)
   726  	// no recovery grub.cfg so that test fails if it ever reaches that point
   727  
   728  	// grub on ubuntu-boot
   729  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   730  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   731  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   732  	c.Assert(err, IsNil)
   733  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   734  	c.Assert(err, IsNil)
   735  
   736  	unpackedGadgetDir := c.MkDir()
   737  
   738  	// make the snaps symlinks so that we can ensure that makebootable follows
   739  	// the symlinks and copies the files and not the symlinks
   740  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   741  type: base
   742  version: 5.0
   743  `, snap.R(3))
   744  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   745  	err = os.Symlink(baseFn, baseInSeed)
   746  	c.Assert(err, IsNil)
   747  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   748  type: kernel
   749  version: 5.0
   750  `, snap.R(5),
   751  		[][]string{
   752  			{"kernel.efi", "I'm a kernel.efi"},
   753  		},
   754  	)
   755  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   756  	err = os.Symlink(kernelFn, kernelInSeed)
   757  	c.Assert(err, IsNil)
   758  
   759  	bootWith := &boot.BootableSet{
   760  		RecoverySystemDir: "20191216",
   761  		BasePath:          baseInSeed,
   762  		Base:              baseInfo,
   763  		KernelPath:        kernelInSeed,
   764  		Kernel:            kernelInfo,
   765  		Recovery:          false,
   766  		UnpackedGadgetDir: unpackedGadgetDir,
   767  	}
   768  
   769  	// no grub marker in gadget directory raises an error
   770  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   771  	c.Assert(err, ErrorMatches, "internal error: cannot identify run system bootloader: cannot determine bootloader")
   772  
   773  	// set up grub.cfg in gadget
   774  	grubCfg := []byte("#grub cfg")
   775  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   776  	c.Assert(err, IsNil)
   777  
   778  	// no write access to destination directory
   779  	restore := assets.MockInternal("grub.cfg", nil)
   780  	defer restore()
   781  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   782  	c.Assert(err, ErrorMatches, `cannot install managed bootloader assets: internal error: no boot asset for "grub.cfg"`)
   783  }
   784  
   785  func (s *makeBootable20Suite) TestMakeRunnableSystem20RunModeSealKeyErr(c *C) {
   786  	bootloader.Force(nil)
   787  
   788  	model := boottest.MakeMockUC20Model()
   789  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   790  	err := os.MkdirAll(seedSnapsDirs, 0755)
   791  	c.Assert(err, IsNil)
   792  
   793  	// grub on ubuntu-seed
   794  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   795  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   796  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   797  	c.Assert(err, IsNil)
   798  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   799  	c.Assert(err, IsNil)
   800  
   801  	// setup recovery boot assets
   802  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   803  	c.Assert(err, IsNil)
   804  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
   805  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
   806  		[]byte("recovery shim content"), 0644)
   807  	c.Assert(err, IsNil)
   808  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
   809  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
   810  		[]byte("recovery grub content"), 0644)
   811  	c.Assert(err, IsNil)
   812  
   813  	// grub on ubuntu-boot
   814  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   815  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   816  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   817  	c.Assert(err, IsNil)
   818  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   819  	c.Assert(err, IsNil)
   820  
   821  	unpackedGadgetDir := c.MkDir()
   822  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   823  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   824  	grubCfg := []byte("#grub cfg")
   825  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
   826  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   827  		{"grub-recovery.conf", string(grubRecoveryCfg)},
   828  		{"grub.conf", string(grubCfg)},
   829  		{"bootx64.efi", "shim content"},
   830  		{"grubx64.efi", "grub content"},
   831  		{"meta/snap.yaml", gadgetSnapYaml},
   832  	})
   833  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
   834  	defer restore()
   835  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
   836  	defer restore()
   837  
   838  	// make the snaps symlinks so that we can ensure that makebootable follows
   839  	// the symlinks and copies the files and not the symlinks
   840  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   841  type: base
   842  version: 5.0
   843  `, snap.R(3))
   844  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   845  	err = os.Symlink(baseFn, baseInSeed)
   846  	c.Assert(err, IsNil)
   847  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   848  type: kernel
   849  version: 5.0
   850  `, snap.R(5),
   851  		[][]string{
   852  			{"kernel.efi", "I'm a kernel.efi"},
   853  		},
   854  	)
   855  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   856  	err = os.Symlink(kernelFn, kernelInSeed)
   857  	c.Assert(err, IsNil)
   858  
   859  	bootWith := &boot.BootableSet{
   860  		RecoverySystemDir: "20191216",
   861  		BasePath:          baseInSeed,
   862  		Base:              baseInfo,
   863  		KernelPath:        kernelInSeed,
   864  		Kernel:            kernelInfo,
   865  		Recovery:          false,
   866  		UnpackedGadgetDir: unpackedGadgetDir,
   867  	}
   868  
   869  	// set up observer state
   870  	useEncryption := true
   871  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
   872  	c.Assert(obs, NotNil)
   873  	c.Assert(err, IsNil)
   874  	runBootStruct := &gadget.LaidOutStructure{
   875  		VolumeStructure: &gadget.VolumeStructure{
   876  			Role: gadget.SystemBoot,
   877  		},
   878  	}
   879  
   880  	// only grubx64.efi gets installed to system-boot
   881  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
   882  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
   883  	c.Assert(err, IsNil)
   884  
   885  	// observe recovery assets
   886  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
   887  	c.Assert(err, IsNil)
   888  
   889  	// set encryption key
   890  	myKey := secboot.EncryptionKey{}
   891  	myKey2 := secboot.EncryptionKey{}
   892  	for i := range myKey {
   893  		myKey[i] = byte(i)
   894  		myKey2[i] = byte(128 + i)
   895  	}
   896  	obs.ChosenEncryptionKeys(myKey, myKey2)
   897  
   898  	// set a mock recovery kernel
   899  	readSystemEssentialCalls := 0
   900  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   901  		readSystemEssentialCalls++
   902  		return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
   903  	})
   904  	defer restore()
   905  
   906  	// set mock key sealing
   907  	sealKeysCalls := 0
   908  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   909  		sealKeysCalls++
   910  		switch sealKeysCalls {
   911  		case 1:
   912  			c.Check(keys, HasLen, 1)
   913  			c.Check(keys[0].Key, DeepEquals, myKey)
   914  		case 2:
   915  			c.Check(keys, HasLen, 2)
   916  			c.Check(keys[0].Key, DeepEquals, myKey)
   917  			c.Check(keys[1].Key, DeepEquals, myKey2)
   918  		default:
   919  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   920  		}
   921  		c.Assert(params.ModelParams, HasLen, 1)
   922  
   923  		shim := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   924  			"var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"),
   925  			bootloader.RoleRecovery)
   926  		grub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   927  			"var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"),
   928  			bootloader.RoleRecovery)
   929  		runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   930  			"var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"),
   931  			bootloader.RoleRunMode)
   932  		kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   933  		runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode)
   934  
   935  		c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   936  			secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   937  			secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))),
   938  		})
   939  		c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   940  			"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   941  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   942  		})
   943  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   944  
   945  		return fmt.Errorf("seal error")
   946  	})
   947  	defer restore()
   948  
   949  	err = boot.MakeRunnableSystem(model, bootWith, obs)
   950  	c.Assert(err, ErrorMatches, "cannot seal the encryption keys: seal error")
   951  }
   952  
   953  func (s *makeBootable20Suite) testMakeSystemRunnable20WithCustomKernelArgs(c *C, whichFile, content, errMsg, cmdlineRun, cmdlineRecovery string) {
   954  	bootloader.Force(nil)
   955  
   956  	model := boottest.MakeMockUC20Model()
   957  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   958  	err := os.MkdirAll(seedSnapsDirs, 0755)
   959  	c.Assert(err, IsNil)
   960  
   961  	// grub on ubuntu-seed
   962  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   963  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   964  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   965  	c.Assert(err, IsNil)
   966  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   967  	c.Assert(err, IsNil)
   968  
   969  	// setup recovery boot assets
   970  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   971  	c.Assert(err, IsNil)
   972  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
   973  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
   974  		[]byte("recovery shim content"), 0644)
   975  	c.Assert(err, IsNil)
   976  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
   977  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
   978  		[]byte("recovery grub content"), 0644)
   979  	c.Assert(err, IsNil)
   980  
   981  	// grub on ubuntu-boot
   982  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   983  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   984  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   985  	c.Assert(err, IsNil)
   986  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   987  	c.Assert(err, IsNil)
   988  
   989  	unpackedGadgetDir := c.MkDir()
   990  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   991  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   992  	grubCfg := []byte("#grub cfg")
   993  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
   994  	gadgetFiles := [][]string{
   995  		{"grub-recovery.conf", string(grubRecoveryCfg)},
   996  		{"grub.conf", string(grubCfg)},
   997  		{"bootx64.efi", "shim content"},
   998  		{"grubx64.efi", "grub content"},
   999  		{"meta/snap.yaml", gadgetSnapYaml},
  1000  		{whichFile, content},
  1001  	}
  1002  	snaptest.PopulateDir(unpackedGadgetDir, gadgetFiles)
  1003  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
  1004  	defer restore()
  1005  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
  1006  	defer restore()
  1007  
  1008  	// make the snaps symlinks so that we can ensure that makebootable follows
  1009  	// the symlinks and copies the files and not the symlinks
  1010  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1011  type: base
  1012  version: 5.0
  1013  `, snap.R(3))
  1014  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1015  	err = os.Symlink(baseFn, baseInSeed)
  1016  	c.Assert(err, IsNil)
  1017  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1018  type: kernel
  1019  version: 5.0
  1020  `, snap.R(5),
  1021  		[][]string{
  1022  			{"kernel.efi", "I'm a kernel.efi"},
  1023  		},
  1024  	)
  1025  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1026  	err = os.Symlink(kernelFn, kernelInSeed)
  1027  	c.Assert(err, IsNil)
  1028  
  1029  	bootWith := &boot.BootableSet{
  1030  		RecoverySystemDir: "20191216",
  1031  		BasePath:          baseInSeed,
  1032  		Base:              baseInfo,
  1033  		KernelPath:        kernelInSeed,
  1034  		Kernel:            kernelInfo,
  1035  		Recovery:          false,
  1036  		UnpackedGadgetDir: unpackedGadgetDir,
  1037  	}
  1038  
  1039  	// set up observer state
  1040  	useEncryption := true
  1041  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
  1042  	c.Assert(obs, NotNil)
  1043  	c.Assert(err, IsNil)
  1044  	runBootStruct := &gadget.LaidOutStructure{
  1045  		VolumeStructure: &gadget.VolumeStructure{
  1046  			Role: gadget.SystemBoot,
  1047  		},
  1048  	}
  1049  
  1050  	// only grubx64.efi gets installed to system-boot
  1051  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
  1052  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
  1053  	c.Assert(err, IsNil)
  1054  
  1055  	// observe recovery assets
  1056  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
  1057  	c.Assert(err, IsNil)
  1058  
  1059  	// set a mock recovery kernel
  1060  	readSystemEssentialCalls := 0
  1061  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1062  		readSystemEssentialCalls++
  1063  		return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, gadgetFiles)}, nil
  1064  	})
  1065  	defer restore()
  1066  
  1067  	// set mock key sealing
  1068  	sealKeysCalls := 0
  1069  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
  1070  		sealKeysCalls++
  1071  		switch sealKeysCalls {
  1072  		case 1, 2:
  1073  			// expecting only 2 calls
  1074  		default:
  1075  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
  1076  		}
  1077  		c.Assert(params.ModelParams, HasLen, 1)
  1078  
  1079  		switch sealKeysCalls {
  1080  		case 1:
  1081  			c.Assert(params.ModelParams[0].KernelCmdlines, HasLen, 2)
  1082  			c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlineRecovery)
  1083  			c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlineRun)
  1084  		case 2:
  1085  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{cmdlineRecovery})
  1086  		default:
  1087  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
  1088  		}
  1089  
  1090  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1091  
  1092  		return nil
  1093  	})
  1094  	defer restore()
  1095  
  1096  	err = boot.MakeRunnableSystem(model, bootWith, obs)
  1097  	if errMsg != "" {
  1098  		c.Assert(err, ErrorMatches, errMsg)
  1099  		return
  1100  	}
  1101  	c.Assert(err, IsNil)
  1102  
  1103  	// also do the logical thing and make the next boot go to run mode
  1104  	err = boot.EnsureNextBootToRunMode("20191216")
  1105  	c.Assert(err, IsNil)
  1106  
  1107  	// ensure grub.cfg in boot was installed from internal assets
  1108  	c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset))
  1109  
  1110  	// ensure the bootvars got updated the right way
  1111  	mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
  1112  	c.Check(mockSeedGrubenv, testutil.FilePresent)
  1113  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run")
  1114  	mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv")
  1115  	c.Check(mockBootGrubenv, testutil.FilePresent)
  1116  	systemGenv := grubenv.NewEnv(mockBootGrubenv)
  1117  	c.Assert(systemGenv.Load(), IsNil)
  1118  	switch whichFile {
  1119  	case "cmdline.extra":
  1120  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content)
  1121  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "")
  1122  	case "cmdline.full":
  1123  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
  1124  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content)
  1125  	}
  1126  
  1127  	// ensure modeenv looks correct
  1128  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
  1129  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, fmt.Sprintf(`mode=run
  1130  recovery_system=20191216
  1131  current_recovery_systems=20191216
  1132  good_recovery_systems=20191216
  1133  base=core20_3.snap
  1134  current_kernels=pc-kernel_5.snap
  1135  model=my-brand/my-model-uc20
  1136  grade=dangerous
  1137  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
  1138  current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]}
  1139  current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]}
  1140  current_kernel_command_lines=["%v"]
  1141  `, cmdlineRun))
  1142  	// make sure SealKey was called for the run object and the fallback object
  1143  	c.Check(sealKeysCalls, Equals, 2)
  1144  
  1145  	// make sure the marker file for sealed key was created
  1146  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent)
  1147  
  1148  	// make sure we wrote the boot chains data file
  1149  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent)
  1150  }
  1151  
  1152  func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelExtraArgs(c *C) {
  1153  	cmdlineRun := "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz"
  1154  	cmdlineRecovery := "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1 foo bar baz"
  1155  	s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar baz", "", cmdlineRun, cmdlineRecovery)
  1156  }
  1157  
  1158  func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelFullArgs(c *C) {
  1159  	cmdlineRun := "snapd_recovery_mode=run foo bar baz"
  1160  	cmdlineRecovery := "snapd_recovery_mode=recover snapd_recovery_system=20191216 foo bar baz"
  1161  	s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.full", "foo bar baz", "", cmdlineRun, cmdlineRecovery)
  1162  }
  1163  
  1164  func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelInvalidArgs(c *C) {
  1165  	errMsg := `cannot compose the candidate command line: cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument "snapd=unhappy"`
  1166  	s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar snapd=unhappy", errMsg, "", "")
  1167  }
  1168  
  1169  func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20TraditionalUbootenvFails(c *C) {
  1170  	bootloader.Force(nil)
  1171  	model := boottest.MakeMockUC20Model()
  1172  
  1173  	unpackedGadgetDir := c.MkDir()
  1174  	ubootEnv := []byte("#uboot env")
  1175  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), ubootEnv, 0644)
  1176  	c.Assert(err, IsNil)
  1177  
  1178  	// on uc20 the seed layout if different
  1179  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1180  	err = os.MkdirAll(seedSnapsDirs, 0755)
  1181  	c.Assert(err, IsNil)
  1182  
  1183  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1184  type: base
  1185  version: 5.0
  1186  `, snap.R(3))
  1187  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1188  	err = os.Rename(baseFn, baseInSeed)
  1189  	c.Assert(err, IsNil)
  1190  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
  1191  type: kernel
  1192  version: 5.0
  1193  `, snap.R(5), [][]string{
  1194  		{"kernel.img", "I'm a kernel"},
  1195  		{"initrd.img", "...and I'm an initrd"},
  1196  		{"dtbs/foo.dtb", "foo dtb"},
  1197  		{"dtbs/bar.dto", "bar dtbo"},
  1198  	})
  1199  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1200  	err = os.Rename(kernelFn, kernelInSeed)
  1201  	c.Assert(err, IsNil)
  1202  
  1203  	label := "20191209"
  1204  	recoverySystemDir := filepath.Join("/systems", label)
  1205  	bootWith := &boot.BootableSet{
  1206  		Base:                baseInfo,
  1207  		BasePath:            baseInSeed,
  1208  		Kernel:              kernelInfo,
  1209  		KernelPath:          kernelInSeed,
  1210  		RecoverySystemDir:   recoverySystemDir,
  1211  		RecoverySystemLabel: label,
  1212  		UnpackedGadgetDir:   unpackedGadgetDir,
  1213  		Recovery:            true,
  1214  	}
  1215  
  1216  	// TODO:UC20: enable this use case
  1217  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
  1218  	c.Assert(err, ErrorMatches, "non-empty uboot.env not supported on UC20 yet")
  1219  }
  1220  
  1221  func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20BootScr(c *C) {
  1222  	model := boottest.MakeMockUC20Model()
  1223  
  1224  	unpackedGadgetDir := c.MkDir()
  1225  	// the uboot.conf must be empty for this to work/do the right thing
  1226  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644)
  1227  	c.Assert(err, IsNil)
  1228  
  1229  	// on uc20 the seed layout if different
  1230  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1231  	err = os.MkdirAll(seedSnapsDirs, 0755)
  1232  	c.Assert(err, IsNil)
  1233  
  1234  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1235  type: base
  1236  version: 5.0
  1237  `, snap.R(3))
  1238  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1239  	err = os.Rename(baseFn, baseInSeed)
  1240  	c.Assert(err, IsNil)
  1241  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
  1242  type: kernel
  1243  version: 5.0
  1244  `, snap.R(5), [][]string{
  1245  		{"kernel.img", "I'm a kernel"},
  1246  		{"initrd.img", "...and I'm an initrd"},
  1247  		{"dtbs/foo.dtb", "foo dtb"},
  1248  		{"dtbs/bar.dto", "bar dtbo"},
  1249  	})
  1250  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1251  	err = os.Rename(kernelFn, kernelInSeed)
  1252  	c.Assert(err, IsNil)
  1253  
  1254  	label := "20191209"
  1255  	recoverySystemDir := filepath.Join("/systems", label)
  1256  	bootWith := &boot.BootableSet{
  1257  		Base:                baseInfo,
  1258  		BasePath:            baseInSeed,
  1259  		Kernel:              kernelInfo,
  1260  		KernelPath:          kernelInSeed,
  1261  		RecoverySystemDir:   recoverySystemDir,
  1262  		RecoverySystemLabel: label,
  1263  		UnpackedGadgetDir:   unpackedGadgetDir,
  1264  		Recovery:            true,
  1265  	}
  1266  
  1267  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
  1268  	c.Assert(err, IsNil)
  1269  
  1270  	// since uboot.conf was absent, we won't have installed the uboot.env, as
  1271  	// it is expected that the gadget assets would have installed boot.scr
  1272  	// instead
  1273  	c.Check(filepath.Join(s.rootdir, "uboot.env"), testutil.FileAbsent)
  1274  
  1275  	c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{
  1276  		"snapd_recovery_system": label,
  1277  		"snapd_recovery_mode":   "install",
  1278  	})
  1279  
  1280  	// ensure the correct recovery system configuration was set
  1281  	c.Check(
  1282  		s.bootloader.ExtractRecoveryKernelAssetsCalls,
  1283  		DeepEquals,
  1284  		[]bootloadertest.ExtractedRecoveryKernelCall{{
  1285  			RecoverySystemDir: recoverySystemDir,
  1286  			S:                 kernelInfo,
  1287  		}},
  1288  	)
  1289  }
  1290  
  1291  func (s *makeBootable20UbootSuite) TestUbootMakeRunnableSystem20RunModeBootSel(c *C) {
  1292  	bootloader.Force(nil)
  1293  
  1294  	model := boottest.MakeMockUC20Model()
  1295  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1296  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1297  	c.Assert(err, IsNil)
  1298  
  1299  	// uboot on ubuntu-seed
  1300  	mockSeedUbootBootSel := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel")
  1301  	err = os.MkdirAll(filepath.Dir(mockSeedUbootBootSel), 0755)
  1302  	c.Assert(err, IsNil)
  1303  	env, err := ubootenv.Create(mockSeedUbootBootSel, 4096)
  1304  	c.Assert(err, IsNil)
  1305  	c.Assert(env.Save(), IsNil)
  1306  
  1307  	// uboot on ubuntu-boot (as if it was installed when creating the partition)
  1308  	mockBootUbootBootSel := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel")
  1309  	err = os.MkdirAll(filepath.Dir(mockBootUbootBootSel), 0755)
  1310  	c.Assert(err, IsNil)
  1311  	env, err = ubootenv.Create(mockBootUbootBootSel, 4096)
  1312  	c.Assert(err, IsNil)
  1313  	c.Assert(env.Save(), IsNil)
  1314  
  1315  	unpackedGadgetDir := c.MkDir()
  1316  	c.Assert(ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644), IsNil)
  1317  
  1318  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1319  type: base
  1320  version: 5.0
  1321  `, snap.R(3))
  1322  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1323  	err = os.Rename(baseFn, baseInSeed)
  1324  	c.Assert(err, IsNil)
  1325  	kernelSnapFiles := [][]string{
  1326  		{"kernel.img", "I'm a kernel"},
  1327  		{"initrd.img", "...and I'm an initrd"},
  1328  		{"dtbs/foo.dtb", "foo dtb"},
  1329  		{"dtbs/bar.dto", "bar dtbo"},
  1330  	}
  1331  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
  1332  type: kernel
  1333  version: 5.0
  1334  `, snap.R(5), kernelSnapFiles)
  1335  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1336  	err = os.Rename(kernelFn, kernelInSeed)
  1337  	c.Assert(err, IsNil)
  1338  
  1339  	bootWith := &boot.BootableSet{
  1340  		RecoverySystemDir: "20191216",
  1341  		BasePath:          baseInSeed,
  1342  		Base:              baseInfo,
  1343  		KernelPath:        kernelInSeed,
  1344  		Kernel:            kernelInfo,
  1345  		Recovery:          false,
  1346  		UnpackedGadgetDir: unpackedGadgetDir,
  1347  	}
  1348  	err = boot.MakeRunnableSystem(model, bootWith, nil)
  1349  	c.Assert(err, IsNil)
  1350  
  1351  	// also do the logical next thing which is to ensure that the system
  1352  	// reboots into run mode
  1353  	err = boot.EnsureNextBootToRunMode("20191216")
  1354  	c.Assert(err, IsNil)
  1355  
  1356  	// ensure base/kernel got copied to /var/lib/snapd/snaps
  1357  	c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap"), testutil.FilePresent)
  1358  	c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "arm-kernel_5.snap"), testutil.FilePresent)
  1359  
  1360  	// ensure the bootvars on ubuntu-seed got updated the right way
  1361  	mockSeedUbootenv := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel")
  1362  	uenvSeed, err := ubootenv.Open(mockSeedUbootenv)
  1363  	c.Assert(err, IsNil)
  1364  	c.Assert(uenvSeed.Get("snapd_recovery_mode"), Equals, "run")
  1365  
  1366  	// now check ubuntu-boot boot.sel
  1367  	mockBootUbootenv := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel")
  1368  	uenvBoot, err := ubootenv.Open(mockBootUbootenv)
  1369  	c.Assert(err, IsNil)
  1370  	c.Assert(uenvBoot.Get("snap_try_kernel"), Equals, "")
  1371  	c.Assert(uenvBoot.Get("snap_kernel"), Equals, "arm-kernel_5.snap")
  1372  	c.Assert(uenvBoot.Get("kernel_status"), Equals, boot.DefaultStatus)
  1373  
  1374  	// check that we have the extracted kernel in the right places, in the
  1375  	// old uc16/uc18 location
  1376  	for _, file := range kernelSnapFiles {
  1377  		fName := file[0]
  1378  		c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/arm-kernel_5.snap", fName), testutil.FilePresent)
  1379  	}
  1380  
  1381  	// ensure modeenv looks correct
  1382  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
  1383  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run
  1384  recovery_system=20191216
  1385  current_recovery_systems=20191216
  1386  good_recovery_systems=20191216
  1387  base=core20_3.snap
  1388  current_kernels=arm-kernel_5.snap
  1389  model=my-brand/my-model-uc20
  1390  grade=dangerous
  1391  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
  1392  `)
  1393  }
  1394  
  1395  func (s *makeBootable20Suite) TestMakeRecoverySystemBootableAtRuntime20(c *C) {
  1396  	bootloader.Force(nil)
  1397  
  1398  	// on uc20 the seed layout if different
  1399  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1400  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1401  	c.Assert(err, IsNil)
  1402  
  1403  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1404  type: kernel
  1405  version: 5.0
  1406  `, snap.R(5), [][]string{
  1407  		{"kernel.efi", "I'm a kernel.efi"},
  1408  	})
  1409  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1410  	err = os.Rename(kernelFn, kernelInSeed)
  1411  	c.Assert(err, IsNil)
  1412  
  1413  	gadgets := map[string]string{}
  1414  	for _, rev := range []snap.Revision{snap.R(1), snap.R(5)} {
  1415  		gadgetFn, gadgetInfo := makeSnapWithFiles(c, "pc", gadgetSnapYaml, rev, [][]string{
  1416  			{"grub.conf", ""},
  1417  			{"meta/snap.yaml", gadgetSnapYaml},
  1418  			{"cmdline.full", fmt.Sprintf("args from gadget rev %s", rev.String())},
  1419  		})
  1420  		gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1421  		err = os.Rename(gadgetFn, gadgetInSeed)
  1422  		c.Assert(err, IsNil)
  1423  		// keep track of the gadgets
  1424  		gadgets[rev.String()] = gadgetInSeed
  1425  	}
  1426  
  1427  	snaptest.PopulateDir(s.rootdir, [][]string{
  1428  		{"EFI/ubuntu/grub.cfg", "this is grub"},
  1429  		{"EFI/ubuntu/grubenv", "canary"},
  1430  	})
  1431  
  1432  	label := "20191209"
  1433  	recoverySystemDir := filepath.Join("/systems", label)
  1434  	err = boot.MakeRecoverySystemBootable(s.rootdir, recoverySystemDir, &boot.RecoverySystemBootableSet{
  1435  		Kernel:     kernelInfo,
  1436  		KernelPath: kernelInSeed,
  1437  		// use gadget revision 1
  1438  		GadgetSnapOrDir: gadgets["1"],
  1439  		// like it's called when creating a new recovery system
  1440  		PrepareImageTime: false,
  1441  	})
  1442  	c.Assert(err, IsNil)
  1443  	// the recovery partition grubenv was not modified
  1444  	c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"), testutil.FileEquals, "canary")
  1445  
  1446  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
  1447  	c.Assert(systemGenv.Load(), IsNil)
  1448  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
  1449  	c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
  1450  	c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 1")
  1451  
  1452  	// create another system under a new label
  1453  	newLabel := "20210420"
  1454  	newRecoverySystemDir := filepath.Join("/systems", newLabel)
  1455  	// with a different gadget revision, but same kernel
  1456  	err = boot.MakeRecoverySystemBootable(s.rootdir, newRecoverySystemDir, &boot.RecoverySystemBootableSet{
  1457  		Kernel:          kernelInfo,
  1458  		KernelPath:      kernelInSeed,
  1459  		GadgetSnapOrDir: gadgets["5"],
  1460  		// like it's called when creating a new recovery system
  1461  		PrepareImageTime: false,
  1462  	})
  1463  	c.Assert(err, IsNil)
  1464  
  1465  	systemGenv = grubenv.NewEnv(filepath.Join(s.rootdir, newRecoverySystemDir, "grubenv"))
  1466  	c.Assert(systemGenv.Load(), IsNil)
  1467  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
  1468  	c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
  1469  	c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 5")
  1470  }