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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2022 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  	"gitee.com/mysnapcore/mysnapd/arch/archtest"
    31  	"gitee.com/mysnapcore/mysnapd/asserts"
    32  	"gitee.com/mysnapcore/mysnapd/boot"
    33  	"gitee.com/mysnapcore/mysnapd/boot/boottest"
    34  	"gitee.com/mysnapcore/mysnapd/bootloader"
    35  	"gitee.com/mysnapcore/mysnapd/bootloader/assets"
    36  	"gitee.com/mysnapcore/mysnapd/bootloader/bootloadertest"
    37  	"gitee.com/mysnapcore/mysnapd/bootloader/grubenv"
    38  	"gitee.com/mysnapcore/mysnapd/bootloader/ubootenv"
    39  	"gitee.com/mysnapcore/mysnapd/dirs"
    40  	"gitee.com/mysnapcore/mysnapd/gadget"
    41  	"gitee.com/mysnapcore/mysnapd/osutil"
    42  	"gitee.com/mysnapcore/mysnapd/release"
    43  	"gitee.com/mysnapcore/mysnapd/secboot"
    44  	"gitee.com/mysnapcore/mysnapd/secboot/keys"
    45  	"gitee.com/mysnapcore/mysnapd/seed"
    46  	"gitee.com/mysnapcore/mysnapd/snap"
    47  	"gitee.com/mysnapcore/mysnapd/snap/snapfile"
    48  	"gitee.com/mysnapcore/mysnapd/snap/snaptest"
    49  	"gitee.com/mysnapcore/mysnapd/testutil"
    50  	"gitee.com/mysnapcore/mysnapd/timings"
    51  )
    52  
    53  type makeBootableSuite struct {
    54  	baseBootenvSuite
    55  
    56  	bootloader *bootloadertest.MockBootloader
    57  }
    58  
    59  var _ = Suite(&makeBootableSuite{})
    60  
    61  func (s *makeBootableSuite) SetUpTest(c *C) {
    62  	s.baseBootenvSuite.SetUpTest(c)
    63  
    64  	s.bootloader = bootloadertest.Mock("mock", c.MkDir())
    65  	s.forceBootloader(s.bootloader)
    66  
    67  	s.AddCleanup(archtest.MockArchitecture("amd64"))
    68  	snippets := []assets.ForEditions{
    69  		{FirstEdition: 1, Snippet: []byte("console=ttyS0 console=tty1 panic=-1")},
    70  	}
    71  	s.AddCleanup(assets.MockSnippetsForEdition("grub.cfg:static-cmdline", snippets))
    72  	s.AddCleanup(assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", snippets))
    73  }
    74  
    75  func makeSnap(c *C, name, yaml string, revno snap.Revision) (fn string, info *snap.Info) {
    76  	return makeSnapWithFiles(c, name, yaml, revno, nil)
    77  }
    78  
    79  func makeSnapWithFiles(c *C, name, yaml string, revno snap.Revision, files [][]string) (fn string, info *snap.Info) {
    80  	si := &snap.SideInfo{
    81  		RealName: name,
    82  		Revision: revno,
    83  	}
    84  	fn = snaptest.MakeTestSnapWithFiles(c, yaml, files)
    85  	snapf, err := snapfile.Open(fn)
    86  	c.Assert(err, IsNil)
    87  	info, err = snap.ReadInfoFromSnapFile(snapf, si)
    88  	c.Assert(err, IsNil)
    89  	return fn, info
    90  }
    91  
    92  func (s *makeBootableSuite) TestMakeBootableImage(c *C) {
    93  	bootloader.Force(nil)
    94  	model := boottest.MakeMockModel()
    95  
    96  	grubCfg := []byte("#grub cfg")
    97  	unpackedGadgetDir := c.MkDir()
    98  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
    99  	c.Assert(err, IsNil)
   100  
   101  	seedSnapsDirs := filepath.Join(s.rootdir, "/var/lib/snapd/seed", "snaps")
   102  	err = os.MkdirAll(seedSnapsDirs, 0755)
   103  	c.Assert(err, IsNil)
   104  
   105  	baseFn, baseInfo := makeSnap(c, "core18", `name: core18
   106  type: base
   107  version: 4.0
   108  `, snap.R(3))
   109  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   110  	err = os.Rename(baseFn, baseInSeed)
   111  	c.Assert(err, IsNil)
   112  	kernelFn, kernelInfo := makeSnap(c, "pc-kernel", `name: pc-kernel
   113  type: kernel
   114  version: 4.0
   115  `, snap.R(5))
   116  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   117  	err = os.Rename(kernelFn, kernelInSeed)
   118  	c.Assert(err, IsNil)
   119  
   120  	bootWith := &boot.BootableSet{
   121  		Base:              baseInfo,
   122  		BasePath:          baseInSeed,
   123  		Kernel:            kernelInfo,
   124  		KernelPath:        kernelInSeed,
   125  		UnpackedGadgetDir: unpackedGadgetDir,
   126  	}
   127  
   128  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   129  	c.Assert(err, IsNil)
   130  
   131  	// check the bootloader config
   132  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "boot/grub/grubenv"))
   133  	c.Assert(seedGenv.Load(), IsNil)
   134  	c.Check(seedGenv.Get("snap_kernel"), Equals, "pc-kernel_5.snap")
   135  	c.Check(seedGenv.Get("snap_core"), Equals, "core18_3.snap")
   136  	c.Check(seedGenv.Get("snap_menuentry"), Equals, "My Model")
   137  
   138  	// check symlinks from snap blob dir
   139  	kernelBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename())
   140  	dst, err := os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename()))
   141  	c.Assert(err, IsNil)
   142  	c.Check(dst, Equals, "../seed/snaps/pc-kernel_5.snap")
   143  	c.Check(kernelBlob, testutil.FilePresent)
   144  
   145  	baseBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename())
   146  	dst, err = os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename()))
   147  	c.Assert(err, IsNil)
   148  	c.Check(dst, Equals, "../seed/snaps/core18_3.snap")
   149  	c.Check(baseBlob, testutil.FilePresent)
   150  
   151  	// check that the bootloader (grub here) configuration was copied
   152  	c.Check(filepath.Join(s.rootdir, "boot", "grub/grub.cfg"), testutil.FileEquals, grubCfg)
   153  }
   154  
   155  type makeBootable20Suite struct {
   156  	baseBootenvSuite
   157  
   158  	bootloader *bootloadertest.MockRecoveryAwareBootloader
   159  }
   160  
   161  type makeBootable20UbootSuite struct {
   162  	baseBootenvSuite
   163  
   164  	bootloader *bootloadertest.MockExtractedRecoveryKernelImageBootloader
   165  }
   166  
   167  var _ = Suite(&makeBootable20Suite{})
   168  var _ = Suite(&makeBootable20UbootSuite{})
   169  
   170  func (s *makeBootable20Suite) SetUpTest(c *C) {
   171  	s.baseBootenvSuite.SetUpTest(c)
   172  
   173  	s.bootloader = bootloadertest.Mock("mock", c.MkDir()).RecoveryAware()
   174  	s.forceBootloader(s.bootloader)
   175  	s.AddCleanup(archtest.MockArchitecture("amd64"))
   176  	snippets := []assets.ForEditions{
   177  		{FirstEdition: 1, Snippet: []byte("console=ttyS0 console=tty1 panic=-1")},
   178  	}
   179  	s.AddCleanup(assets.MockSnippetsForEdition("grub.cfg:static-cmdline", snippets))
   180  	s.AddCleanup(assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", snippets))
   181  }
   182  
   183  func (s *makeBootable20UbootSuite) SetUpTest(c *C) {
   184  	s.baseBootenvSuite.SetUpTest(c)
   185  
   186  	s.bootloader = bootloadertest.Mock("mock", c.MkDir()).ExtractedRecoveryKernelImage()
   187  	s.forceBootloader(s.bootloader)
   188  }
   189  
   190  func (s *makeBootable20Suite) TestMakeBootableImage20(c *C) {
   191  	bootloader.Force(nil)
   192  	model := boottest.MakeMockUC20Model()
   193  
   194  	unpackedGadgetDir := c.MkDir()
   195  	grubRecoveryCfg := "#grub-recovery cfg"
   196  	grubRecoveryCfgAsset := "#grub-recovery cfg from assets"
   197  	grubCfg := "#grub cfg"
   198  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   199  		{"grub-recovery.conf", grubRecoveryCfg},
   200  		{"grub.conf", grubCfg},
   201  		{"meta/snap.yaml", gadgetSnapYaml},
   202  	})
   203  	restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset))
   204  	defer restore()
   205  
   206  	// on uc20 the seed layout if different
   207  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   208  	err := os.MkdirAll(seedSnapsDirs, 0755)
   209  	c.Assert(err, IsNil)
   210  
   211  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   212  type: base
   213  version: 5.0
   214  `, snap.R(3))
   215  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   216  	err = os.Rename(baseFn, baseInSeed)
   217  	c.Assert(err, IsNil)
   218  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   219  type: kernel
   220  version: 5.0
   221  `, snap.R(5), [][]string{
   222  		{"kernel.efi", "I'm a kernel.efi"},
   223  	})
   224  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   225  	err = os.Rename(kernelFn, kernelInSeed)
   226  	c.Assert(err, IsNil)
   227  
   228  	label := "20191209"
   229  	recoverySystemDir := filepath.Join("/systems", label)
   230  	bootWith := &boot.BootableSet{
   231  		Base:                baseInfo,
   232  		BasePath:            baseInSeed,
   233  		Kernel:              kernelInfo,
   234  		KernelPath:          kernelInSeed,
   235  		RecoverySystemDir:   recoverySystemDir,
   236  		RecoverySystemLabel: label,
   237  		UnpackedGadgetDir:   unpackedGadgetDir,
   238  		Recovery:            true,
   239  	}
   240  
   241  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   242  	c.Assert(err, IsNil)
   243  
   244  	// ensure only a single file got copied (the grub.cfg)
   245  	files, err := filepath.Glob(filepath.Join(s.rootdir, "EFI/ubuntu/*"))
   246  	c.Assert(err, IsNil)
   247  	// grub.cfg and grubenv
   248  	c.Check(files, HasLen, 2)
   249  	// check that the recovery bootloader configuration was installed with
   250  	// the correct content
   251  	c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset)
   252  
   253  	// ensure no /boot was setup
   254  	c.Check(filepath.Join(s.rootdir, "boot"), testutil.FileAbsent)
   255  
   256  	// ensure the correct recovery system configuration was set
   257  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   258  	c.Assert(seedGenv.Load(), IsNil)
   259  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   260  
   261  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   262  	c.Assert(systemGenv.Load(), IsNil)
   263  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   264  }
   265  
   266  func (s *makeBootable20Suite) TestMakeBootableImage20BootFlags(c *C) {
   267  	bootloader.Force(nil)
   268  	model := boottest.MakeMockUC20Model()
   269  
   270  	unpackedGadgetDir := c.MkDir()
   271  	grubRecoveryCfg := "#grub-recovery cfg"
   272  	grubRecoveryCfgAsset := "#grub-recovery cfg from assets"
   273  	grubCfg := "#grub cfg"
   274  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   275  		{"grub-recovery.conf", grubRecoveryCfg},
   276  		{"grub.conf", grubCfg},
   277  		{"meta/snap.yaml", gadgetSnapYaml},
   278  	})
   279  	restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset))
   280  	defer restore()
   281  
   282  	// on uc20 the seed layout if different
   283  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   284  	err := os.MkdirAll(seedSnapsDirs, 0755)
   285  	c.Assert(err, IsNil)
   286  
   287  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   288  type: base
   289  version: 5.0
   290  `, snap.R(3))
   291  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   292  	err = os.Rename(baseFn, baseInSeed)
   293  	c.Assert(err, IsNil)
   294  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   295  type: kernel
   296  version: 5.0
   297  `, snap.R(5), [][]string{
   298  		{"kernel.efi", "I'm a kernel.efi"},
   299  	})
   300  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   301  	err = os.Rename(kernelFn, kernelInSeed)
   302  	c.Assert(err, IsNil)
   303  
   304  	label := "20191209"
   305  	recoverySystemDir := filepath.Join("/systems", label)
   306  	bootWith := &boot.BootableSet{
   307  		Base:                baseInfo,
   308  		BasePath:            baseInSeed,
   309  		Kernel:              kernelInfo,
   310  		KernelPath:          kernelInSeed,
   311  		RecoverySystemDir:   recoverySystemDir,
   312  		RecoverySystemLabel: label,
   313  		UnpackedGadgetDir:   unpackedGadgetDir,
   314  		Recovery:            true,
   315  	}
   316  	bootFlags := []string{"factory"}
   317  
   318  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, bootFlags)
   319  	c.Assert(err, IsNil)
   320  
   321  	// ensure the correct recovery system configuration was set
   322  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   323  	c.Assert(seedGenv.Load(), IsNil)
   324  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   325  	c.Check(seedGenv.Get("snapd_boot_flags"), Equals, "factory")
   326  
   327  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   328  	c.Assert(systemGenv.Load(), IsNil)
   329  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   330  
   331  }
   332  
   333  func (s *makeBootable20Suite) testMakeBootableImage20CustomKernelArgs(c *C, whichFile, content, errMsg string) {
   334  	bootloader.Force(nil)
   335  	model := boottest.MakeMockUC20Model()
   336  
   337  	unpackedGadgetDir := c.MkDir()
   338  	grubCfg := "#grub cfg"
   339  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   340  		{"grub.conf", grubCfg},
   341  		{"meta/snap.yaml", gadgetSnapYaml},
   342  		{whichFile, content},
   343  	})
   344  
   345  	// on uc20 the seed layout if different
   346  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   347  	err := os.MkdirAll(seedSnapsDirs, 0755)
   348  	c.Assert(err, IsNil)
   349  
   350  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   351  type: base
   352  version: 5.0
   353  `, snap.R(3))
   354  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   355  	err = os.Rename(baseFn, baseInSeed)
   356  	c.Assert(err, IsNil)
   357  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   358  type: kernel
   359  version: 5.0
   360  `, snap.R(5), [][]string{
   361  		{"kernel.efi", "I'm a kernel.efi"},
   362  	})
   363  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   364  	err = os.Rename(kernelFn, kernelInSeed)
   365  	c.Assert(err, IsNil)
   366  
   367  	label := "20191209"
   368  	recoverySystemDir := filepath.Join("/systems", label)
   369  	bootWith := &boot.BootableSet{
   370  		Base:                baseInfo,
   371  		BasePath:            baseInSeed,
   372  		Kernel:              kernelInfo,
   373  		KernelPath:          kernelInSeed,
   374  		RecoverySystemDir:   recoverySystemDir,
   375  		RecoverySystemLabel: label,
   376  		UnpackedGadgetDir:   unpackedGadgetDir,
   377  		Recovery:            true,
   378  	}
   379  
   380  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   381  	if errMsg != "" {
   382  		c.Assert(err, ErrorMatches, errMsg)
   383  		return
   384  	}
   385  	c.Assert(err, IsNil)
   386  
   387  	// ensure the correct recovery system configuration was set
   388  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   389  	c.Assert(seedGenv.Load(), IsNil)
   390  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   391  	// and kernel command line
   392  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   393  	c.Assert(systemGenv.Load(), IsNil)
   394  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   395  	switch whichFile {
   396  	case "cmdline.extra":
   397  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content)
   398  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "")
   399  	case "cmdline.full":
   400  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
   401  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content)
   402  	}
   403  }
   404  
   405  func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelExtraArgs(c *C) {
   406  	s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "foo bar baz", "")
   407  }
   408  
   409  func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelFullArgs(c *C) {
   410  	s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.full", "foo bar baz", "")
   411  }
   412  
   413  func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelInvalidArgs(c *C) {
   414  	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"`
   415  	s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "snapd_foo=bar", errMsg)
   416  }
   417  
   418  func (s *makeBootable20Suite) TestMakeBootableImage20UnsetRecoverySystemLabelError(c *C) {
   419  	model := boottest.MakeMockUC20Model()
   420  
   421  	unpackedGadgetDir := c.MkDir()
   422  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   423  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644)
   424  	c.Assert(err, IsNil)
   425  	grubCfg := []byte("#grub cfg")
   426  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   427  	c.Assert(err, IsNil)
   428  
   429  	label := "20191209"
   430  	recoverySystemDir := filepath.Join("/systems", label)
   431  	bootWith := &boot.BootableSet{
   432  		RecoverySystemDir: recoverySystemDir,
   433  		UnpackedGadgetDir: unpackedGadgetDir,
   434  		Recovery:          true,
   435  	}
   436  
   437  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   438  	c.Assert(err, ErrorMatches, "internal error: recovery system label unset")
   439  }
   440  
   441  func (s *makeBootable20Suite) TestMakeBootableImage20MultipleRecoverySystemsError(c *C) {
   442  	model := boottest.MakeMockUC20Model()
   443  
   444  	bootWith := &boot.BootableSet{Recovery: true}
   445  	err := os.MkdirAll(filepath.Join(s.rootdir, "systems/20191204"), 0755)
   446  	c.Assert(err, IsNil)
   447  	err = os.MkdirAll(filepath.Join(s.rootdir, "systems/20191205"), 0755)
   448  	c.Assert(err, IsNil)
   449  
   450  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   451  	c.Assert(err, ErrorMatches, "cannot make multiple recovery systems bootable yet")
   452  }
   453  
   454  func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) {
   455  	model := boottest.MakeMockModel()
   456  
   457  	err := boot.MakeRunnableSystem(model, nil, nil)
   458  	c.Assert(err, ErrorMatches, `internal error: cannot make pre-UC20 system runnable`)
   459  }
   460  
   461  func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, standalone, factoryReset, classic bool) {
   462  	restore := release.MockOnClassic(classic)
   463  	defer restore()
   464  	dirs.SetRootDir(dirs.GlobalRootDir)
   465  
   466  	bootloader.Force(nil)
   467  
   468  	var model *asserts.Model
   469  	if classic {
   470  		model = boottest.MakeMockUC20Model(map[string]interface{}{
   471  			"classic":      "true",
   472  			"distribution": "ubuntu",
   473  		})
   474  	} else {
   475  		model = boottest.MakeMockUC20Model()
   476  	}
   477  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   478  	err := os.MkdirAll(seedSnapsDirs, 0755)
   479  	c.Assert(err, IsNil)
   480  
   481  	// grub on ubuntu-seed
   482  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   483  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   484  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   485  	c.Assert(err, IsNil)
   486  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   487  	c.Assert(err, IsNil)
   488  	genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv"))
   489  	c.Assert(genv.Save(), IsNil)
   490  
   491  	// setup recovery boot assets
   492  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   493  	c.Assert(err, IsNil)
   494  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
   495  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
   496  		[]byte("recovery shim content"), 0644)
   497  	c.Assert(err, IsNil)
   498  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
   499  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
   500  		[]byte("recovery grub content"), 0644)
   501  	c.Assert(err, IsNil)
   502  
   503  	// grub on ubuntu-boot
   504  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   505  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   506  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   507  	c.Assert(err, IsNil)
   508  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   509  	c.Assert(err, IsNil)
   510  
   511  	unpackedGadgetDir := c.MkDir()
   512  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   513  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   514  	grubCfg := []byte("#grub cfg")
   515  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
   516  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
   517  		{"grub-recovery.conf", string(grubRecoveryCfg)},
   518  		{"grub.conf", string(grubCfg)},
   519  		{"bootx64.efi", "shim content"},
   520  		{"grubx64.efi", "grub content"},
   521  		{"meta/snap.yaml", gadgetSnapYaml},
   522  	})
   523  	restore = assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
   524  	defer restore()
   525  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
   526  	defer restore()
   527  
   528  	// make the snaps symlinks so that we can ensure that makebootable follows
   529  	// the symlinks and copies the files and not the symlinks
   530  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   531  type: base
   532  version: 5.0
   533  `, snap.R(3))
   534  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   535  	err = os.Symlink(baseFn, baseInSeed)
   536  	c.Assert(err, IsNil)
   537  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
   538  type: gadget
   539  version: 5.0
   540  `, snap.R(4))
   541  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
   542  	err = os.Symlink(gadgetFn, gadgetInSeed)
   543  	c.Assert(err, IsNil)
   544  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   545  type: kernel
   546  version: 5.0
   547  `, snap.R(5),
   548  		[][]string{
   549  			{"kernel.efi", "I'm a kernel.efi"},
   550  		},
   551  	)
   552  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   553  	err = os.Symlink(kernelFn, kernelInSeed)
   554  	c.Assert(err, IsNil)
   555  
   556  	bootWith := &boot.BootableSet{
   557  		RecoverySystemLabel: "20191216",
   558  		BasePath:            baseInSeed,
   559  		Base:                baseInfo,
   560  		Gadget:              gadgetInfo,
   561  		GadgetPath:          gadgetInSeed,
   562  		KernelPath:          kernelInSeed,
   563  		Kernel:              kernelInfo,
   564  		Recovery:            false,
   565  		UnpackedGadgetDir:   unpackedGadgetDir,
   566  	}
   567  
   568  	// set up observer state
   569  	useEncryption := true
   570  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
   571  	c.Assert(obs, NotNil)
   572  	c.Assert(err, IsNil)
   573  	runBootStruct := &gadget.LaidOutStructure{
   574  		VolumeStructure: &gadget.VolumeStructure{
   575  			Role: gadget.SystemBoot,
   576  		},
   577  	}
   578  
   579  	// only grubx64.efi gets installed to system-boot
   580  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
   581  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
   582  	c.Assert(err, IsNil)
   583  
   584  	// observe recovery assets
   585  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
   586  	c.Assert(err, IsNil)
   587  
   588  	// set encryption key
   589  	myKey := keys.EncryptionKey{}
   590  	myKey2 := keys.EncryptionKey{}
   591  	for i := range myKey {
   592  		myKey[i] = byte(i)
   593  		myKey2[i] = byte(128 + i)
   594  	}
   595  	obs.ChosenEncryptionKeys(myKey, myKey2)
   596  
   597  	// set a mock recovery kernel
   598  	readSystemEssentialCalls := 0
   599  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   600  		readSystemEssentialCalls++
   601  		return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
   602  	})
   603  	defer restore()
   604  
   605  	provisionCalls := 0
   606  	restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
   607  		provisionCalls++
   608  		c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
   609  		if factoryReset {
   610  			c.Check(mode, Equals, secboot.TPMPartialReprovision)
   611  		} else {
   612  			c.Check(mode, Equals, secboot.TPMProvisionFull)
   613  		}
   614  		return nil
   615  	})
   616  	defer restore()
   617  
   618  	pcrHandleOfKeyCalls := 0
   619  	restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) {
   620  		pcrHandleOfKeyCalls++
   621  		c.Check(provisionCalls, Equals, 0)
   622  		if !factoryReset {
   623  			c.Errorf("unexpected call in non-factory-reset scenario")
   624  			return 0, fmt.Errorf("unexpected call")
   625  		}
   626  		c.Check(p, Equals,
   627  			filepath.Join(s.rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"))
   628  		// trigger use of alt handles as current key is using the main handle
   629  		return secboot.FallbackObjectPCRPolicyCounterHandle, nil
   630  	})
   631  	defer restore()
   632  
   633  	releasePCRHandleCalls := 0
   634  	restore = boot.MockSecbootReleasePCRResourceHandles(func(handles ...uint32) error {
   635  		c.Check(factoryReset, Equals, true)
   636  		releasePCRHandleCalls++
   637  		c.Check(handles, DeepEquals, []uint32{
   638  			secboot.AltRunObjectPCRPolicyCounterHandle,
   639  			secboot.AltFallbackObjectPCRPolicyCounterHandle,
   640  		})
   641  		return nil
   642  	})
   643  	defer restore()
   644  
   645  	hasFDESetupHookCalled := false
   646  	restore = boot.MockHasFDESetupHook(func(kernel *snap.Info) (bool, error) {
   647  		c.Check(kernel, Equals, kernelInfo)
   648  		hasFDESetupHookCalled = true
   649  		return false, nil
   650  	})
   651  	defer restore()
   652  
   653  	// set mock key sealing
   654  	sealKeysCalls := 0
   655  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   656  		c.Assert(provisionCalls, Equals, 1, Commentf("TPM must have been provisioned before"))
   657  		sealKeysCalls++
   658  		switch sealKeysCalls {
   659  		case 1:
   660  			c.Check(keys, HasLen, 1)
   661  			c.Check(keys[0].Key, DeepEquals, myKey)
   662  			c.Check(keys[0].KeyFile, Equals,
   663  				filepath.Join(s.rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key"))
   664  			if factoryReset {
   665  				c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle)
   666  			} else {
   667  				c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle)
   668  			}
   669  		case 2:
   670  			c.Check(keys, HasLen, 2)
   671  			c.Check(keys[0].Key, DeepEquals, myKey)
   672  			c.Check(keys[1].Key, DeepEquals, myKey2)
   673  			c.Check(keys[0].KeyFile, Equals,
   674  				filepath.Join(s.rootdir,
   675  					"/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"))
   676  			if factoryReset {
   677  				c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle)
   678  				c.Check(keys[1].KeyFile, Equals,
   679  					filepath.Join(s.rootdir,
   680  						"/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset"))
   681  
   682  			} else {
   683  				c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle)
   684  				c.Check(keys[1].KeyFile, Equals,
   685  					filepath.Join(s.rootdir,
   686  						"/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key"))
   687  			}
   688  		default:
   689  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   690  		}
   691  		c.Assert(params.ModelParams, HasLen, 1)
   692  
   693  		shim := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   694  			"var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"),
   695  			bootloader.RoleRecovery)
   696  		grub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   697  			"var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"),
   698  			bootloader.RoleRecovery)
   699  		runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   700  			"var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"),
   701  			bootloader.RoleRunMode)
   702  		kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   703  		var runKernelPath string
   704  		var runKernel bootloader.BootFile
   705  		switch {
   706  		case !standalone:
   707  			runKernelPath = "/var/lib/snapd/snaps/pc-kernel_5.snap"
   708  		case classic:
   709  			runKernelPath = "/run/mnt/ubuntu-data/var/lib/snapd/snaps/pc-kernel_5.snap"
   710  		case !classic:
   711  			runKernelPath = "/run/mnt/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_5.snap"
   712  		}
   713  		runKernel = bootloader.NewBootFile(filepath.Join(s.rootdir, runKernelPath), "kernel.efi", bootloader.RoleRunMode)
   714  		switch sealKeysCalls {
   715  		case 1:
   716  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   717  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   718  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))),
   719  			})
   720  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   721  				"snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   722  				"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   723  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   724  			})
   725  		case 2:
   726  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   727  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   728  			})
   729  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   730  				"snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   731  				"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   732  			})
   733  		default:
   734  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   735  		}
   736  
   737  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
   738  
   739  		return nil
   740  	})
   741  	defer restore()
   742  
   743  	switch {
   744  	case standalone:
   745  		err = boot.MakeRunnableStandaloneSystem(model, bootWith, obs)
   746  	case factoryReset:
   747  		err = boot.MakeRunnableSystemAfterDataReset(model, bootWith, obs)
   748  	default:
   749  		err = boot.MakeRunnableSystem(model, bootWith, obs)
   750  	}
   751  	c.Assert(err, IsNil)
   752  
   753  	// also do the logical thing and make the next boot go to run mode
   754  	err = boot.EnsureNextBootToRunMode("20191216")
   755  	c.Assert(err, IsNil)
   756  
   757  	// ensure grub.cfg in boot was installed from internal assets
   758  	c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset))
   759  
   760  	var installHostWritableDir string
   761  	if classic {
   762  		installHostWritableDir = filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data")
   763  	} else {
   764  		installHostWritableDir = filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")
   765  	}
   766  
   767  	// ensure base/gadget/kernel got copied to /var/lib/snapd/snaps
   768  	core20Snap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "core20_3.snap")
   769  	gadgetSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc_4.snap")
   770  	pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc-kernel_5.snap")
   771  	c.Check(core20Snap, testutil.FilePresent)
   772  	c.Check(gadgetSnap, testutil.FilePresent)
   773  	c.Check(pcKernelSnap, testutil.FilePresent)
   774  	c.Check(osutil.IsSymlink(core20Snap), Equals, false)
   775  	c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false)
   776  
   777  	// ensure the bootvars got updated the right way
   778  	mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
   779  	c.Assert(mockSeedGrubenv, testutil.FilePresent)
   780  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run")
   781  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_good_recovery_systems=20191216")
   782  	mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv")
   783  	c.Check(mockBootGrubenv, testutil.FilePresent)
   784  
   785  	// ensure that kernel_status is empty, we specifically want this to be set
   786  	// to the empty string
   787  	// use (?m) to match multi-line file in the regex here, because the file is
   788  	// a grubenv with padding #### blocks
   789  	c.Check(mockBootGrubenv, testutil.FileMatches, `(?m)^kernel_status=$`)
   790  
   791  	// check that we have the extracted kernel in the right places, both in the
   792  	// old uc16/uc18 location and the new ubuntu-boot partition grub dir
   793  	extractedKernel := filepath.Join(mockBootGrubDir, "pc-kernel_5.snap", "kernel.efi")
   794  	c.Check(extractedKernel, testutil.FilePresent)
   795  
   796  	// the new uc20 location
   797  	extractedKernelSymlink := filepath.Join(mockBootGrubDir, "kernel.efi")
   798  	c.Check(extractedKernelSymlink, testutil.FilePresent)
   799  
   800  	// ensure modeenv looks correct
   801  	var ubuntuDataModeEnvPath, classicLine string
   802  	if classic {
   803  		ubuntuDataModeEnvPath = filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/var/lib/snapd/modeenv")
   804  		classicLine = "\nclassic=true"
   805  	} else {
   806  		ubuntuDataModeEnvPath = filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
   807  	}
   808  	expectedModeenv := fmt.Sprintf(`mode=run
   809  recovery_system=20191216
   810  current_recovery_systems=20191216
   811  good_recovery_systems=20191216
   812  base=core20_3.snap
   813  gadget=pc_4.snap
   814  current_kernels=pc-kernel_5.snap
   815  model=my-brand/my-model-uc20%s
   816  grade=dangerous
   817  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
   818  current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]}
   819  current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]}
   820  current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"]
   821  `, classicLine)
   822  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, expectedModeenv)
   823  	copiedGrubBin := filepath.Join(
   824  		dirs.SnapBootAssetsDirUnder(installHostWritableDir),
   825  		"grub",
   826  		"grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d",
   827  	)
   828  	copiedRecoveryGrubBin := filepath.Join(
   829  		dirs.SnapBootAssetsDirUnder(installHostWritableDir),
   830  		"grub",
   831  		"grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5",
   832  	)
   833  	copiedRecoveryShimBin := filepath.Join(
   834  		dirs.SnapBootAssetsDirUnder(installHostWritableDir),
   835  		"grub",
   836  		"bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37",
   837  	)
   838  
   839  	// only one file in the cache under new root
   840  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDirUnder(installHostWritableDir), "grub", "*"), []string{
   841  		copiedRecoveryShimBin,
   842  		copiedGrubBin,
   843  		copiedRecoveryGrubBin,
   844  	})
   845  	// with the right content
   846  	c.Check(copiedGrubBin, testutil.FileEquals, "grub content")
   847  	c.Check(copiedRecoveryGrubBin, testutil.FileEquals, "recovery grub content")
   848  	c.Check(copiedRecoveryShimBin, testutil.FileEquals, "recovery shim content")
   849  
   850  	// we checked for fde-setup-hook
   851  	c.Check(hasFDESetupHookCalled, Equals, true)
   852  	// make sure TPM was provisioned
   853  	c.Check(provisionCalls, Equals, 1)
   854  	// make sure SealKey was called for the run object and the fallback object
   855  	c.Check(sealKeysCalls, Equals, 2)
   856  	// PCR handle checks
   857  	if factoryReset {
   858  		c.Check(pcrHandleOfKeyCalls, Equals, 1)
   859  		c.Check(releasePCRHandleCalls, Equals, 1)
   860  	} else {
   861  		c.Check(pcrHandleOfKeyCalls, Equals, 0)
   862  		c.Check(releasePCRHandleCalls, Equals, 0)
   863  	}
   864  
   865  	// make sure the marker file for sealed key was created
   866  	c.Check(filepath.Join(installHostWritableDir, "/var/lib/snapd/device/fde/sealed-keys"), testutil.FilePresent)
   867  
   868  	// make sure we wrote the boot chains data file
   869  	c.Check(filepath.Join(installHostWritableDir, "/var/lib/snapd/device/fde/boot-chains"), testutil.FilePresent)
   870  }
   871  
   872  func (s *makeBootable20Suite) TestMakeSystemRunnable20Install(c *C) {
   873  	const standalone = false
   874  	const factoryReset = false
   875  	const classic = false
   876  	s.testMakeSystemRunnable20(c, standalone, factoryReset, classic)
   877  }
   878  
   879  func (s *makeBootable20Suite) TestMakeSystemRunnable20InstallOnClassic(c *C) {
   880  	const standalone = false
   881  	const factoryReset = false
   882  	const classic = true
   883  	s.testMakeSystemRunnable20(c, standalone, factoryReset, classic)
   884  }
   885  
   886  func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryReset(c *C) {
   887  	const standalone = false
   888  	const factoryReset = true
   889  	const classic = false
   890  	s.testMakeSystemRunnable20(c, standalone, factoryReset, classic)
   891  }
   892  
   893  func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryResetOnClassic(c *C) {
   894  	const standalone = false
   895  	const factoryReset = true
   896  	const classic = true
   897  	s.testMakeSystemRunnable20(c, standalone, factoryReset, classic)
   898  }
   899  
   900  func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) {
   901  	bootloader.Force(nil)
   902  
   903  	model := boottest.MakeMockUC20Model()
   904  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   905  	err := os.MkdirAll(seedSnapsDirs, 0755)
   906  	c.Assert(err, IsNil)
   907  
   908  	// grub on ubuntu-seed
   909  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   910  	err = os.MkdirAll(mockSeedGrubDir, 0755)
   911  	c.Assert(err, IsNil)
   912  	// no recovery grub.cfg so that test fails if it ever reaches that point
   913  
   914  	// grub on ubuntu-boot
   915  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   916  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   917  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   918  	c.Assert(err, IsNil)
   919  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   920  	c.Assert(err, IsNil)
   921  
   922  	unpackedGadgetDir := c.MkDir()
   923  
   924  	// make the snaps symlinks so that we can ensure that makebootable follows
   925  	// the symlinks and copies the files and not the symlinks
   926  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   927  type: base
   928  version: 5.0
   929  `, snap.R(3))
   930  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   931  	err = os.Symlink(baseFn, baseInSeed)
   932  	c.Assert(err, IsNil)
   933  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   934  type: kernel
   935  version: 5.0
   936  `, snap.R(5),
   937  		[][]string{
   938  			{"kernel.efi", "I'm a kernel.efi"},
   939  		},
   940  	)
   941  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   942  	err = os.Symlink(kernelFn, kernelInSeed)
   943  	c.Assert(err, IsNil)
   944  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
   945  type: gadget
   946  version: 5.0
   947  `, snap.R(4))
   948  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
   949  	err = os.Symlink(gadgetFn, gadgetInSeed)
   950  	c.Assert(err, IsNil)
   951  
   952  	bootWith := &boot.BootableSet{
   953  		RecoverySystemLabel: "20191216",
   954  		BasePath:            baseInSeed,
   955  		Base:                baseInfo,
   956  		KernelPath:          kernelInSeed,
   957  		Kernel:              kernelInfo,
   958  		Gadget:              gadgetInfo,
   959  		GadgetPath:          gadgetInSeed,
   960  		Recovery:            false,
   961  		UnpackedGadgetDir:   unpackedGadgetDir,
   962  	}
   963  
   964  	// no grub marker in gadget directory raises an error
   965  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   966  	c.Assert(err, ErrorMatches, "internal error: cannot identify run system bootloader: cannot determine bootloader")
   967  
   968  	// set up grub.cfg in gadget
   969  	grubCfg := []byte("#grub cfg")
   970  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   971  	c.Assert(err, IsNil)
   972  
   973  	// no write access to destination directory
   974  	restore := assets.MockInternal("grub.cfg", nil)
   975  	defer restore()
   976  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   977  	c.Assert(err, ErrorMatches, `cannot install managed bootloader assets: internal error: no boot asset for "grub.cfg"`)
   978  }
   979  
   980  func (s *makeBootable20Suite) TestMakeRunnableSystem20RunModeSealKeyErr(c *C) {
   981  	bootloader.Force(nil)
   982  
   983  	model := boottest.MakeMockUC20Model()
   984  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   985  	err := os.MkdirAll(seedSnapsDirs, 0755)
   986  	c.Assert(err, IsNil)
   987  
   988  	// grub on ubuntu-seed
   989  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   990  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   991  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   992  	c.Assert(err, IsNil)
   993  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   994  	c.Assert(err, IsNil)
   995  
   996  	// setup recovery boot assets
   997  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   998  	c.Assert(err, IsNil)
   999  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
  1000  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
  1001  		[]byte("recovery shim content"), 0644)
  1002  	c.Assert(err, IsNil)
  1003  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
  1004  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
  1005  		[]byte("recovery grub content"), 0644)
  1006  	c.Assert(err, IsNil)
  1007  
  1008  	// grub on ubuntu-boot
  1009  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
  1010  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
  1011  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
  1012  	c.Assert(err, IsNil)
  1013  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
  1014  	c.Assert(err, IsNil)
  1015  
  1016  	unpackedGadgetDir := c.MkDir()
  1017  	grubRecoveryCfg := []byte("#grub-recovery cfg")
  1018  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
  1019  	grubCfg := []byte("#grub cfg")
  1020  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
  1021  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
  1022  		{"grub-recovery.conf", string(grubRecoveryCfg)},
  1023  		{"grub.conf", string(grubCfg)},
  1024  		{"bootx64.efi", "shim content"},
  1025  		{"grubx64.efi", "grub content"},
  1026  		{"meta/snap.yaml", gadgetSnapYaml},
  1027  	})
  1028  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
  1029  	defer restore()
  1030  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
  1031  	defer restore()
  1032  
  1033  	// make the snaps symlinks so that we can ensure that makebootable follows
  1034  	// the symlinks and copies the files and not the symlinks
  1035  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1036  type: base
  1037  version: 5.0
  1038  `, snap.R(3))
  1039  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1040  	err = os.Symlink(baseFn, baseInSeed)
  1041  	c.Assert(err, IsNil)
  1042  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1043  type: kernel
  1044  version: 5.0
  1045  `, snap.R(5),
  1046  		[][]string{
  1047  			{"kernel.efi", "I'm a kernel.efi"},
  1048  		},
  1049  	)
  1050  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1051  	err = os.Symlink(kernelFn, kernelInSeed)
  1052  	c.Assert(err, IsNil)
  1053  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  1054  type: gadget
  1055  version: 5.0
  1056  `, snap.R(4))
  1057  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1058  	err = os.Symlink(gadgetFn, gadgetInSeed)
  1059  	c.Assert(err, IsNil)
  1060  
  1061  	bootWith := &boot.BootableSet{
  1062  		RecoverySystemLabel: "20191216",
  1063  		BasePath:            baseInSeed,
  1064  		Base:                baseInfo,
  1065  		KernelPath:          kernelInSeed,
  1066  		Kernel:              kernelInfo,
  1067  		Gadget:              gadgetInfo,
  1068  		GadgetPath:          gadgetInSeed,
  1069  		Recovery:            false,
  1070  		UnpackedGadgetDir:   unpackedGadgetDir,
  1071  	}
  1072  
  1073  	// set up observer state
  1074  	useEncryption := true
  1075  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
  1076  	c.Assert(obs, NotNil)
  1077  	c.Assert(err, IsNil)
  1078  	runBootStruct := &gadget.LaidOutStructure{
  1079  		VolumeStructure: &gadget.VolumeStructure{
  1080  			Role: gadget.SystemBoot,
  1081  		},
  1082  	}
  1083  
  1084  	// only grubx64.efi gets installed to system-boot
  1085  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
  1086  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
  1087  	c.Assert(err, IsNil)
  1088  
  1089  	// observe recovery assets
  1090  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
  1091  	c.Assert(err, IsNil)
  1092  
  1093  	// set encryption key
  1094  	myKey := keys.EncryptionKey{}
  1095  	myKey2 := keys.EncryptionKey{}
  1096  	for i := range myKey {
  1097  		myKey[i] = byte(i)
  1098  		myKey2[i] = byte(128 + i)
  1099  	}
  1100  	obs.ChosenEncryptionKeys(myKey, myKey2)
  1101  
  1102  	// set a mock recovery kernel
  1103  	readSystemEssentialCalls := 0
  1104  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1105  		readSystemEssentialCalls++
  1106  		return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
  1107  	})
  1108  	defer restore()
  1109  
  1110  	provisionCalls := 0
  1111  	restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
  1112  		provisionCalls++
  1113  		c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
  1114  		c.Check(mode, Equals, secboot.TPMProvisionFull)
  1115  		return nil
  1116  	})
  1117  	defer restore()
  1118  	// set mock key sealing
  1119  	sealKeysCalls := 0
  1120  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
  1121  		sealKeysCalls++
  1122  		switch sealKeysCalls {
  1123  		case 1:
  1124  			c.Check(keys, HasLen, 1)
  1125  			c.Check(keys[0].Key, DeepEquals, myKey)
  1126  		case 2:
  1127  			c.Check(keys, HasLen, 2)
  1128  			c.Check(keys[0].Key, DeepEquals, myKey)
  1129  			c.Check(keys[1].Key, DeepEquals, myKey2)
  1130  		default:
  1131  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
  1132  		}
  1133  		c.Assert(params.ModelParams, HasLen, 1)
  1134  
  1135  		shim := bootloader.NewBootFile("", filepath.Join(s.rootdir,
  1136  			"var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"),
  1137  			bootloader.RoleRecovery)
  1138  		grub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
  1139  			"var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"),
  1140  			bootloader.RoleRecovery)
  1141  		runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
  1142  			"var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"),
  1143  			bootloader.RoleRunMode)
  1144  		kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
  1145  		runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode)
  1146  
  1147  		c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
  1148  			secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
  1149  			secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))),
  1150  		})
  1151  		c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
  1152  			"snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
  1153  			"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
  1154  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
  1155  		})
  1156  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1157  
  1158  		return fmt.Errorf("seal error")
  1159  	})
  1160  	defer restore()
  1161  
  1162  	err = boot.MakeRunnableSystem(model, bootWith, obs)
  1163  	c.Assert(err, ErrorMatches, "cannot seal the encryption keys: seal error")
  1164  	// the TPM was provisioned
  1165  	c.Check(provisionCalls, Equals, 1)
  1166  }
  1167  
  1168  func (s *makeBootable20Suite) testMakeSystemRunnable20WithCustomKernelArgs(c *C, whichFile, content, errMsg string, cmdlines map[string]string) {
  1169  	if cmdlines == nil {
  1170  		cmdlines = map[string]string{}
  1171  	}
  1172  	bootloader.Force(nil)
  1173  
  1174  	model := boottest.MakeMockUC20Model()
  1175  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1176  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1177  	c.Assert(err, IsNil)
  1178  
  1179  	// grub on ubuntu-seed
  1180  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
  1181  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
  1182  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
  1183  	c.Assert(err, IsNil)
  1184  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  1185  	c.Assert(err, IsNil)
  1186  	genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv"))
  1187  	c.Assert(genv.Save(), IsNil)
  1188  
  1189  	// setup recovery boot assets
  1190  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
  1191  	c.Assert(err, IsNil)
  1192  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
  1193  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
  1194  		[]byte("recovery shim content"), 0644)
  1195  	c.Assert(err, IsNil)
  1196  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
  1197  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
  1198  		[]byte("recovery grub content"), 0644)
  1199  	c.Assert(err, IsNil)
  1200  
  1201  	// grub on ubuntu-boot
  1202  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
  1203  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
  1204  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
  1205  	c.Assert(err, IsNil)
  1206  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
  1207  	c.Assert(err, IsNil)
  1208  
  1209  	unpackedGadgetDir := c.MkDir()
  1210  	grubRecoveryCfg := []byte("#grub-recovery cfg")
  1211  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
  1212  	grubCfg := []byte("#grub cfg")
  1213  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
  1214  	gadgetFiles := [][]string{
  1215  		{"grub-recovery.conf", string(grubRecoveryCfg)},
  1216  		{"grub.conf", string(grubCfg)},
  1217  		{"bootx64.efi", "shim content"},
  1218  		{"grubx64.efi", "grub content"},
  1219  		{"meta/snap.yaml", gadgetSnapYaml},
  1220  		{whichFile, content},
  1221  	}
  1222  	snaptest.PopulateDir(unpackedGadgetDir, gadgetFiles)
  1223  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
  1224  	defer restore()
  1225  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
  1226  	defer restore()
  1227  
  1228  	// make the snaps symlinks so that we can ensure that makebootable follows
  1229  	// the symlinks and copies the files and not the symlinks
  1230  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1231  type: base
  1232  version: 5.0
  1233  `, snap.R(3))
  1234  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1235  	err = os.Symlink(baseFn, baseInSeed)
  1236  	c.Assert(err, IsNil)
  1237  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  1238  type: gadget
  1239  version: 5.0
  1240  `, snap.R(4))
  1241  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1242  	err = os.Symlink(gadgetFn, gadgetInSeed)
  1243  	c.Assert(err, IsNil)
  1244  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1245  type: kernel
  1246  version: 5.0
  1247  `, snap.R(5),
  1248  		[][]string{
  1249  			{"kernel.efi", "I'm a kernel.efi"},
  1250  		},
  1251  	)
  1252  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1253  	err = os.Symlink(kernelFn, kernelInSeed)
  1254  	c.Assert(err, IsNil)
  1255  
  1256  	bootWith := &boot.BootableSet{
  1257  		RecoverySystemLabel: "20191216",
  1258  		BasePath:            baseInSeed,
  1259  		Base:                baseInfo,
  1260  		Gadget:              gadgetInfo,
  1261  		GadgetPath:          gadgetInSeed,
  1262  		KernelPath:          kernelInSeed,
  1263  		Kernel:              kernelInfo,
  1264  		Recovery:            false,
  1265  		UnpackedGadgetDir:   unpackedGadgetDir,
  1266  	}
  1267  
  1268  	// set up observer state
  1269  	useEncryption := true
  1270  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
  1271  	c.Assert(obs, NotNil)
  1272  	c.Assert(err, IsNil)
  1273  	runBootStruct := &gadget.LaidOutStructure{
  1274  		VolumeStructure: &gadget.VolumeStructure{
  1275  			Role: gadget.SystemBoot,
  1276  		},
  1277  	}
  1278  
  1279  	// only grubx64.efi gets installed to system-boot
  1280  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
  1281  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
  1282  	c.Assert(err, IsNil)
  1283  
  1284  	// observe recovery assets
  1285  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
  1286  	c.Assert(err, IsNil)
  1287  
  1288  	// set a mock recovery kernel
  1289  	readSystemEssentialCalls := 0
  1290  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1291  		readSystemEssentialCalls++
  1292  		return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, gadgetFiles)}, nil
  1293  	})
  1294  	defer restore()
  1295  
  1296  	provisionCalls := 0
  1297  	restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
  1298  		provisionCalls++
  1299  		c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
  1300  		c.Check(mode, Equals, secboot.TPMProvisionFull)
  1301  		return nil
  1302  	})
  1303  	defer restore()
  1304  	// set mock key sealing
  1305  	sealKeysCalls := 0
  1306  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
  1307  		sealKeysCalls++
  1308  		switch sealKeysCalls {
  1309  		case 1, 2:
  1310  			// expecting only 2 calls
  1311  		default:
  1312  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
  1313  		}
  1314  		c.Assert(params.ModelParams, HasLen, 1)
  1315  
  1316  		switch sealKeysCalls {
  1317  		case 1:
  1318  			c.Assert(params.ModelParams[0].KernelCmdlines, HasLen, 3)
  1319  			c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlines["recover"])
  1320  			c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlines["factory-reset"])
  1321  			c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlines["run"])
  1322  		case 2:
  1323  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{cmdlines["factory-reset"], cmdlines["recover"]})
  1324  		default:
  1325  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
  1326  		}
  1327  
  1328  		c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20")
  1329  
  1330  		return nil
  1331  	})
  1332  	defer restore()
  1333  
  1334  	err = boot.MakeRunnableSystem(model, bootWith, obs)
  1335  	if errMsg != "" {
  1336  		c.Assert(err, ErrorMatches, errMsg)
  1337  		return
  1338  	}
  1339  	c.Assert(err, IsNil)
  1340  
  1341  	// also do the logical thing and make the next boot go to run mode
  1342  	err = boot.EnsureNextBootToRunMode("20191216")
  1343  	c.Assert(err, IsNil)
  1344  
  1345  	// ensure grub.cfg in boot was installed from internal assets
  1346  	c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset))
  1347  
  1348  	// ensure the bootvars got updated the right way
  1349  	mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
  1350  	c.Assert(mockSeedGrubenv, testutil.FilePresent)
  1351  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run")
  1352  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_good_recovery_systems=20191216")
  1353  	mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv")
  1354  	c.Check(mockBootGrubenv, testutil.FilePresent)
  1355  	systemGenv := grubenv.NewEnv(mockBootGrubenv)
  1356  	c.Assert(systemGenv.Load(), IsNil)
  1357  	switch whichFile {
  1358  	case "cmdline.extra":
  1359  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content)
  1360  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "")
  1361  	case "cmdline.full":
  1362  		c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
  1363  		c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content)
  1364  	}
  1365  
  1366  	// ensure modeenv looks correct
  1367  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
  1368  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, fmt.Sprintf(`mode=run
  1369  recovery_system=20191216
  1370  current_recovery_systems=20191216
  1371  good_recovery_systems=20191216
  1372  base=core20_3.snap
  1373  gadget=pc_4.snap
  1374  current_kernels=pc-kernel_5.snap
  1375  model=my-brand/my-model-uc20
  1376  grade=dangerous
  1377  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
  1378  current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]}
  1379  current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]}
  1380  current_kernel_command_lines=["%v"]
  1381  `, cmdlines["run"]))
  1382  	// make sure the TPM was provisioned
  1383  	c.Check(provisionCalls, Equals, 1)
  1384  	// make sure SealKey was called for the run object and the fallback object
  1385  	c.Check(sealKeysCalls, Equals, 2)
  1386  
  1387  	// make sure the marker file for sealed key was created
  1388  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "sealed-keys"), testutil.FilePresent)
  1389  
  1390  	// make sure we wrote the boot chains data file
  1391  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "boot-chains"), testutil.FilePresent)
  1392  }
  1393  
  1394  func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelExtraArgs(c *C) {
  1395  	cmdlines := map[string]string{
  1396  		"run":           "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz",
  1397  		"recover":       "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1 foo bar baz",
  1398  		"factory-reset": "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1 foo bar baz",
  1399  	}
  1400  	s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar baz", "", cmdlines)
  1401  }
  1402  
  1403  func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelFullArgs(c *C) {
  1404  	cmdlines := map[string]string{
  1405  		"run":           "snapd_recovery_mode=run foo bar baz",
  1406  		"recover":       "snapd_recovery_mode=recover snapd_recovery_system=20191216 foo bar baz",
  1407  		"factory-reset": "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 foo bar baz",
  1408  	}
  1409  	s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.full", "foo bar baz", "", cmdlines)
  1410  }
  1411  
  1412  func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelInvalidArgs(c *C) {
  1413  	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"`
  1414  	s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar snapd=unhappy", errMsg, nil)
  1415  }
  1416  
  1417  func (s *makeBootable20Suite) TestMakeSystemRunnable20UnhappyMarkRecoveryCapable(c *C) {
  1418  	bootloader.Force(nil)
  1419  
  1420  	model := boottest.MakeMockUC20Model()
  1421  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1422  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1423  	c.Assert(err, IsNil)
  1424  
  1425  	// grub on ubuntu-seed
  1426  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
  1427  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
  1428  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
  1429  	c.Assert(err, IsNil)
  1430  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  1431  	c.Assert(err, IsNil)
  1432  	// there is no grubenv in ubuntu-seed so loading from it will fail
  1433  
  1434  	// setup recovery boot assets
  1435  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
  1436  	c.Assert(err, IsNil)
  1437  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
  1438  		[]byte("recovery shim content"), 0644)
  1439  	c.Assert(err, IsNil)
  1440  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
  1441  		[]byte("recovery grub content"), 0644)
  1442  	c.Assert(err, IsNil)
  1443  
  1444  	// grub on ubuntu-boot
  1445  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
  1446  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
  1447  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
  1448  	c.Assert(err, IsNil)
  1449  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
  1450  	c.Assert(err, IsNil)
  1451  
  1452  	unpackedGadgetDir := c.MkDir()
  1453  	grubRecoveryCfg := []byte("#grub-recovery cfg")
  1454  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
  1455  	grubCfg := []byte("#grub cfg")
  1456  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
  1457  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
  1458  		{"grub-recovery.conf", string(grubRecoveryCfg)},
  1459  		{"grub.conf", string(grubCfg)},
  1460  		{"bootx64.efi", "shim content"},
  1461  		{"grubx64.efi", "grub content"},
  1462  		{"meta/snap.yaml", gadgetSnapYaml},
  1463  	})
  1464  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
  1465  	defer restore()
  1466  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
  1467  	defer restore()
  1468  
  1469  	// make the snaps symlinks so that we can ensure that makebootable follows
  1470  	// the symlinks and copies the files and not the symlinks
  1471  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1472  type: base
  1473  version: 5.0
  1474  `, snap.R(3))
  1475  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1476  	err = os.Symlink(baseFn, baseInSeed)
  1477  	c.Assert(err, IsNil)
  1478  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1479  type: kernel
  1480  version: 5.0
  1481  `, snap.R(5),
  1482  		[][]string{
  1483  			{"kernel.efi", "I'm a kernel.efi"},
  1484  		},
  1485  	)
  1486  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1487  	err = os.Symlink(kernelFn, kernelInSeed)
  1488  	c.Assert(err, IsNil)
  1489  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  1490  type: gadget
  1491  version: 5.0
  1492  `, snap.R(4))
  1493  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1494  	err = os.Symlink(gadgetFn, gadgetInSeed)
  1495  	c.Assert(err, IsNil)
  1496  
  1497  	bootWith := &boot.BootableSet{
  1498  		RecoverySystemLabel: "20191216",
  1499  		BasePath:            baseInSeed,
  1500  		Base:                baseInfo,
  1501  		KernelPath:          kernelInSeed,
  1502  		Kernel:              kernelInfo,
  1503  		Gadget:              gadgetInfo,
  1504  		GadgetPath:          gadgetInSeed,
  1505  		Recovery:            false,
  1506  		UnpackedGadgetDir:   unpackedGadgetDir,
  1507  	}
  1508  
  1509  	// set a mock recovery kernel
  1510  	readSystemEssentialCalls := 0
  1511  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
  1512  		readSystemEssentialCalls++
  1513  		return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
  1514  	})
  1515  	defer restore()
  1516  
  1517  	err = boot.MakeRunnableSystem(model, bootWith, nil)
  1518  	c.Assert(err, ErrorMatches, `cannot record "20191216" as a recovery capable system: open .*/run/mnt/ubuntu-seed/EFI/ubuntu/grubenv: no such file or directory`)
  1519  
  1520  }
  1521  
  1522  func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20TraditionalUbootenvFails(c *C) {
  1523  	bootloader.Force(nil)
  1524  	model := boottest.MakeMockUC20Model()
  1525  
  1526  	unpackedGadgetDir := c.MkDir()
  1527  	ubootEnv := []byte("#uboot env")
  1528  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), ubootEnv, 0644)
  1529  	c.Assert(err, IsNil)
  1530  
  1531  	// on uc20 the seed layout if different
  1532  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1533  	err = os.MkdirAll(seedSnapsDirs, 0755)
  1534  	c.Assert(err, IsNil)
  1535  
  1536  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1537  type: base
  1538  version: 5.0
  1539  `, snap.R(3))
  1540  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1541  	err = os.Rename(baseFn, baseInSeed)
  1542  	c.Assert(err, IsNil)
  1543  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
  1544  type: kernel
  1545  version: 5.0
  1546  `, snap.R(5), [][]string{
  1547  		{"kernel.img", "I'm a kernel"},
  1548  		{"initrd.img", "...and I'm an initrd"},
  1549  		{"dtbs/foo.dtb", "foo dtb"},
  1550  		{"dtbs/bar.dto", "bar dtbo"},
  1551  	})
  1552  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1553  	err = os.Rename(kernelFn, kernelInSeed)
  1554  	c.Assert(err, IsNil)
  1555  
  1556  	label := "20191209"
  1557  	recoverySystemDir := filepath.Join("/systems", label)
  1558  	bootWith := &boot.BootableSet{
  1559  		Base:                baseInfo,
  1560  		BasePath:            baseInSeed,
  1561  		Kernel:              kernelInfo,
  1562  		KernelPath:          kernelInSeed,
  1563  		RecoverySystemDir:   recoverySystemDir,
  1564  		RecoverySystemLabel: label,
  1565  		UnpackedGadgetDir:   unpackedGadgetDir,
  1566  		Recovery:            true,
  1567  	}
  1568  
  1569  	// TODO:UC20: enable this use case
  1570  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
  1571  	c.Assert(err, ErrorMatches, `cannot install bootloader: non-empty uboot.env not supported on UC20\+ yet`)
  1572  }
  1573  
  1574  func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20BootScr(c *C) {
  1575  	model := boottest.MakeMockUC20Model()
  1576  
  1577  	unpackedGadgetDir := c.MkDir()
  1578  	// the uboot.conf must be empty for this to work/do the right thing
  1579  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644)
  1580  	c.Assert(err, IsNil)
  1581  
  1582  	// on uc20 the seed layout if different
  1583  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1584  	err = os.MkdirAll(seedSnapsDirs, 0755)
  1585  	c.Assert(err, IsNil)
  1586  
  1587  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1588  type: base
  1589  version: 5.0
  1590  `, snap.R(3))
  1591  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1592  	err = os.Rename(baseFn, baseInSeed)
  1593  	c.Assert(err, IsNil)
  1594  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
  1595  type: kernel
  1596  version: 5.0
  1597  `, snap.R(5), [][]string{
  1598  		{"kernel.img", "I'm a kernel"},
  1599  		{"initrd.img", "...and I'm an initrd"},
  1600  		{"dtbs/foo.dtb", "foo dtb"},
  1601  		{"dtbs/bar.dto", "bar dtbo"},
  1602  	})
  1603  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1604  	err = os.Rename(kernelFn, kernelInSeed)
  1605  	c.Assert(err, IsNil)
  1606  
  1607  	label := "20191209"
  1608  	recoverySystemDir := filepath.Join("/systems", label)
  1609  	bootWith := &boot.BootableSet{
  1610  		Base:                baseInfo,
  1611  		BasePath:            baseInSeed,
  1612  		Kernel:              kernelInfo,
  1613  		KernelPath:          kernelInSeed,
  1614  		RecoverySystemDir:   recoverySystemDir,
  1615  		RecoverySystemLabel: label,
  1616  		UnpackedGadgetDir:   unpackedGadgetDir,
  1617  		Recovery:            true,
  1618  	}
  1619  
  1620  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
  1621  	c.Assert(err, IsNil)
  1622  
  1623  	// since uboot.conf was absent, we won't have installed the uboot.env, as
  1624  	// it is expected that the gadget assets would have installed boot.scr
  1625  	// instead
  1626  	c.Check(filepath.Join(s.rootdir, "uboot.env"), testutil.FileAbsent)
  1627  
  1628  	c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{
  1629  		"snapd_recovery_system": label,
  1630  		"snapd_recovery_mode":   "install",
  1631  	})
  1632  
  1633  	// ensure the correct recovery system configuration was set
  1634  	c.Check(
  1635  		s.bootloader.ExtractRecoveryKernelAssetsCalls,
  1636  		DeepEquals,
  1637  		[]bootloadertest.ExtractedRecoveryKernelCall{{
  1638  			RecoverySystemDir: recoverySystemDir,
  1639  			S:                 kernelInfo,
  1640  		}},
  1641  	)
  1642  }
  1643  
  1644  func (s *makeBootable20UbootSuite) TestUbootMakeRunnableSystem20RunModeBootSel(c *C) {
  1645  	bootloader.Force(nil)
  1646  
  1647  	model := boottest.MakeMockUC20Model()
  1648  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1649  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1650  	c.Assert(err, IsNil)
  1651  
  1652  	// uboot on ubuntu-seed
  1653  	mockSeedUbootBootSel := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel")
  1654  	err = os.MkdirAll(filepath.Dir(mockSeedUbootBootSel), 0755)
  1655  	c.Assert(err, IsNil)
  1656  	env, err := ubootenv.Create(mockSeedUbootBootSel, 4096)
  1657  	c.Assert(err, IsNil)
  1658  	c.Assert(env.Save(), IsNil)
  1659  
  1660  	// uboot on ubuntu-boot (as if it was installed when creating the partition)
  1661  	mockBootUbootBootSel := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel")
  1662  	err = os.MkdirAll(filepath.Dir(mockBootUbootBootSel), 0755)
  1663  	c.Assert(err, IsNil)
  1664  	env, err = ubootenv.Create(mockBootUbootBootSel, 4096)
  1665  	c.Assert(err, IsNil)
  1666  	c.Assert(env.Save(), IsNil)
  1667  
  1668  	unpackedGadgetDir := c.MkDir()
  1669  	c.Assert(ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644), IsNil)
  1670  
  1671  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1672  type: base
  1673  version: 5.0
  1674  `, snap.R(3))
  1675  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1676  	err = os.Rename(baseFn, baseInSeed)
  1677  	c.Assert(err, IsNil)
  1678  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  1679  type: gadget
  1680  version: 5.0
  1681  `, snap.R(4))
  1682  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1683  	err = os.Symlink(gadgetFn, gadgetInSeed)
  1684  	c.Assert(err, IsNil)
  1685  	kernelSnapFiles := [][]string{
  1686  		{"kernel.img", "I'm a kernel"},
  1687  		{"initrd.img", "...and I'm an initrd"},
  1688  		{"dtbs/foo.dtb", "foo dtb"},
  1689  		{"dtbs/bar.dto", "bar dtbo"},
  1690  	}
  1691  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
  1692  type: kernel
  1693  version: 5.0
  1694  `, snap.R(5), kernelSnapFiles)
  1695  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1696  	err = os.Rename(kernelFn, kernelInSeed)
  1697  	c.Assert(err, IsNil)
  1698  
  1699  	bootWith := &boot.BootableSet{
  1700  		RecoverySystemLabel: "20191216",
  1701  		BasePath:            baseInSeed,
  1702  		Base:                baseInfo,
  1703  		Gadget:              gadgetInfo,
  1704  		GadgetPath:          gadgetInSeed,
  1705  		KernelPath:          kernelInSeed,
  1706  		Kernel:              kernelInfo,
  1707  		Recovery:            false,
  1708  		UnpackedGadgetDir:   unpackedGadgetDir,
  1709  	}
  1710  	err = boot.MakeRunnableSystem(model, bootWith, nil)
  1711  	c.Assert(err, IsNil)
  1712  
  1713  	// also do the logical next thing which is to ensure that the system
  1714  	// reboots into run mode
  1715  	err = boot.EnsureNextBootToRunMode("20191216")
  1716  	c.Assert(err, IsNil)
  1717  
  1718  	// ensure base/kernel got copied to /var/lib/snapd/snaps
  1719  	c.Check(filepath.Join(dirs.SnapBlobDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "core20_3.snap"), testutil.FilePresent)
  1720  	c.Check(filepath.Join(dirs.SnapBlobDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "arm-kernel_5.snap"), testutil.FilePresent)
  1721  
  1722  	// ensure the bootvars on ubuntu-seed got updated the right way
  1723  	mockSeedUbootenv := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel")
  1724  	uenvSeed, err := ubootenv.Open(mockSeedUbootenv)
  1725  	c.Assert(err, IsNil)
  1726  	c.Assert(uenvSeed.Get("snapd_recovery_mode"), Equals, "run")
  1727  
  1728  	// now check ubuntu-boot boot.sel
  1729  	mockBootUbootenv := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel")
  1730  	uenvBoot, err := ubootenv.Open(mockBootUbootenv)
  1731  	c.Assert(err, IsNil)
  1732  	c.Assert(uenvBoot.Get("snap_try_kernel"), Equals, "")
  1733  	c.Assert(uenvBoot.Get("snap_kernel"), Equals, "arm-kernel_5.snap")
  1734  	c.Assert(uenvBoot.Get("kernel_status"), Equals, boot.DefaultStatus)
  1735  
  1736  	// check that we have the extracted kernel in the right places, in the
  1737  	// old uc16/uc18 location
  1738  	for _, file := range kernelSnapFiles {
  1739  		fName := file[0]
  1740  		c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/arm-kernel_5.snap", fName), testutil.FilePresent)
  1741  	}
  1742  
  1743  	// ensure modeenv looks correct
  1744  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
  1745  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run
  1746  recovery_system=20191216
  1747  current_recovery_systems=20191216
  1748  good_recovery_systems=20191216
  1749  base=core20_3.snap
  1750  gadget=pc_4.snap
  1751  current_kernels=arm-kernel_5.snap
  1752  model=my-brand/my-model-uc20
  1753  grade=dangerous
  1754  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
  1755  `)
  1756  }
  1757  
  1758  func (s *makeBootable20Suite) TestMakeRecoverySystemBootableAtRuntime20(c *C) {
  1759  	bootloader.Force(nil)
  1760  
  1761  	// on uc20 the seed layout if different
  1762  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1763  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1764  	c.Assert(err, IsNil)
  1765  
  1766  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1767  type: kernel
  1768  version: 5.0
  1769  `, snap.R(5), [][]string{
  1770  		{"kernel.efi", "I'm a kernel.efi"},
  1771  	})
  1772  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1773  	err = os.Rename(kernelFn, kernelInSeed)
  1774  	c.Assert(err, IsNil)
  1775  
  1776  	gadgets := map[string]string{}
  1777  	for _, rev := range []snap.Revision{snap.R(1), snap.R(5)} {
  1778  		gadgetFn, gadgetInfo := makeSnapWithFiles(c, "pc", gadgetSnapYaml, rev, [][]string{
  1779  			{"grub.conf", ""},
  1780  			{"meta/snap.yaml", gadgetSnapYaml},
  1781  			{"cmdline.full", fmt.Sprintf("args from gadget rev %s", rev.String())},
  1782  		})
  1783  		gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1784  		err = os.Rename(gadgetFn, gadgetInSeed)
  1785  		c.Assert(err, IsNil)
  1786  		// keep track of the gadgets
  1787  		gadgets[rev.String()] = gadgetInSeed
  1788  	}
  1789  
  1790  	snaptest.PopulateDir(s.rootdir, [][]string{
  1791  		{"EFI/ubuntu/grub.cfg", "this is grub"},
  1792  		{"EFI/ubuntu/grubenv", "canary"},
  1793  	})
  1794  
  1795  	label := "20191209"
  1796  	recoverySystemDir := filepath.Join("/systems", label)
  1797  	err = boot.MakeRecoverySystemBootable(s.rootdir, recoverySystemDir, &boot.RecoverySystemBootableSet{
  1798  		Kernel:     kernelInfo,
  1799  		KernelPath: kernelInSeed,
  1800  		// use gadget revision 1
  1801  		GadgetSnapOrDir: gadgets["1"],
  1802  		// like it's called when creating a new recovery system
  1803  		PrepareImageTime: false,
  1804  	})
  1805  	c.Assert(err, IsNil)
  1806  	// the recovery partition grubenv was not modified
  1807  	c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"), testutil.FileEquals, "canary")
  1808  
  1809  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
  1810  	c.Assert(systemGenv.Load(), IsNil)
  1811  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
  1812  	c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
  1813  	c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 1")
  1814  
  1815  	// create another system under a new label
  1816  	newLabel := "20210420"
  1817  	newRecoverySystemDir := filepath.Join("/systems", newLabel)
  1818  	// with a different gadget revision, but same kernel
  1819  	err = boot.MakeRecoverySystemBootable(s.rootdir, newRecoverySystemDir, &boot.RecoverySystemBootableSet{
  1820  		Kernel:          kernelInfo,
  1821  		KernelPath:      kernelInSeed,
  1822  		GadgetSnapOrDir: gadgets["5"],
  1823  		// like it's called when creating a new recovery system
  1824  		PrepareImageTime: false,
  1825  	})
  1826  	c.Assert(err, IsNil)
  1827  
  1828  	systemGenv = grubenv.NewEnv(filepath.Join(s.rootdir, newRecoverySystemDir, "grubenv"))
  1829  	c.Assert(systemGenv.Load(), IsNil)
  1830  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
  1831  	c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "")
  1832  	c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 5")
  1833  }
  1834  
  1835  func (s *makeBootable20Suite) TestMakeBootablePartition(c *C) {
  1836  	bootloader.Force(nil)
  1837  
  1838  	unpackedGadgetDir := c.MkDir()
  1839  	grubRecoveryCfg := "#grub-recovery cfg"
  1840  	grubRecoveryCfgAsset := "#grub-recovery cfg from assets"
  1841  	grubCfg := "#grub cfg"
  1842  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
  1843  		{"grub-recovery.conf", grubRecoveryCfg},
  1844  		{"grub.conf", grubCfg},
  1845  		{"meta/snap.yaml", gadgetSnapYaml},
  1846  	})
  1847  	restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset))
  1848  	defer restore()
  1849  
  1850  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1851  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1852  	c.Assert(err, IsNil)
  1853  
  1854  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  1855  type: gadget
  1856  version: 5.0
  1857  `, snap.R(4))
  1858  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1859  	err = os.Symlink(gadgetFn, gadgetInSeed)
  1860  	c.Assert(err, IsNil)
  1861  
  1862  	baseFn, baseInfo := makeSnap(c, "core22", `name: core22
  1863  type: base
  1864  version: 5.0
  1865  `, snap.R(3))
  1866  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1867  	err = os.Rename(baseFn, baseInSeed)
  1868  	c.Assert(err, IsNil)
  1869  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1870  type: kernel
  1871  version: 5.0
  1872  `, snap.R(5), [][]string{
  1873  		{"kernel.efi", "I'm a kernel.efi"},
  1874  	})
  1875  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1876  	err = os.Rename(kernelFn, kernelInSeed)
  1877  	c.Assert(err, IsNil)
  1878  
  1879  	bootWith := &boot.BootableSet{
  1880  		Base:                baseInfo,
  1881  		BasePath:            baseInSeed,
  1882  		Kernel:              kernelInfo,
  1883  		KernelPath:          kernelInSeed,
  1884  		Gadget:              gadgetInfo,
  1885  		GadgetPath:          gadgetInSeed,
  1886  		RecoverySystemLabel: "",
  1887  		UnpackedGadgetDir:   unpackedGadgetDir,
  1888  		Recovery:            false,
  1889  	}
  1890  
  1891  	opts := &bootloader.Options{
  1892  		PrepareImageTime: false,
  1893  		// We need the same configuration that a recovery partition,
  1894  		// as we will chainload to grub in the boot partition.
  1895  		Role: bootloader.RoleRecovery,
  1896  	}
  1897  	partMntDir := filepath.Join(s.rootdir, "/partition")
  1898  	err = os.MkdirAll(partMntDir, 0755)
  1899  	c.Assert(err, IsNil)
  1900  	err = boot.MakeBootablePartition(partMntDir, opts, bootWith, boot.ModeRun, []string{})
  1901  	c.Assert(err, IsNil)
  1902  
  1903  	// ensure we have only grub.cfg and grubenv
  1904  	files, err := filepath.Glob(filepath.Join(partMntDir, "EFI/ubuntu/*"))
  1905  	c.Assert(err, IsNil)
  1906  	c.Check(files, HasLen, 2)
  1907  	// and nothing else
  1908  	files, err = filepath.Glob(filepath.Join(partMntDir, "EFI/*"))
  1909  	c.Assert(err, IsNil)
  1910  	c.Check(files, HasLen, 1)
  1911  	files, err = filepath.Glob(filepath.Join(partMntDir, "*"))
  1912  	c.Assert(err, IsNil)
  1913  	c.Check(files, HasLen, 1)
  1914  	// check that the recovery bootloader configuration was installed with
  1915  	// the correct content
  1916  	c.Check(filepath.Join(partMntDir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset)
  1917  
  1918  	// ensure the correct recovery system configuration was set
  1919  	seedGenv := grubenv.NewEnv(filepath.Join(partMntDir, "EFI/ubuntu/grubenv"))
  1920  	c.Assert(seedGenv.Load(), IsNil)
  1921  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, "")
  1922  	c.Check(seedGenv.Get("snapd_recovery_mode"), Equals, boot.ModeRun)
  1923  	c.Check(seedGenv.Get("snapd_good_recovery_systems"), Equals, "")
  1924  }
  1925  
  1926  func (s *makeBootable20Suite) TestMakeRunnableSystemNoGoodRecoverySystems(c *C) {
  1927  	bootloader.Force(nil)
  1928  	model := boottest.MakeMockUC20Model()
  1929  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
  1930  	err := os.MkdirAll(seedSnapsDirs, 0755)
  1931  	c.Assert(err, IsNil)
  1932  
  1933  	// grub on ubuntu-seed
  1934  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
  1935  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
  1936  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
  1937  	c.Assert(err, IsNil)
  1938  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  1939  	c.Assert(err, IsNil)
  1940  	genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv"))
  1941  	c.Assert(genv.Save(), IsNil)
  1942  
  1943  	// mock grub so it is detected as the current bootloader
  1944  	unpackedGadgetDir := c.MkDir()
  1945  	grubRecoveryCfg := []byte("#grub-recovery cfg")
  1946  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
  1947  	grubCfg := []byte("#grub cfg")
  1948  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
  1949  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
  1950  		{"grub-recovery.conf", string(grubRecoveryCfg)},
  1951  		{"grub.conf", string(grubCfg)},
  1952  		{"bootx64.efi", "shim content"},
  1953  		{"grubx64.efi", "grub content"},
  1954  		{"meta/snap.yaml", gadgetSnapYaml},
  1955  	})
  1956  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
  1957  	defer restore()
  1958  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
  1959  	defer restore()
  1960  
  1961  	// make the snaps symlinks so that we can ensure that makebootable follows
  1962  	// the symlinks and copies the files and not the symlinks
  1963  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  1964  type: base
  1965  version: 5.0
  1966  `, snap.R(3))
  1967  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
  1968  	err = os.Symlink(baseFn, baseInSeed)
  1969  	c.Assert(err, IsNil)
  1970  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  1971  type: kernel
  1972  version: 5.0
  1973  `, snap.R(5),
  1974  		[][]string{
  1975  			{"kernel.efi", "I'm a kernel.efi"},
  1976  		},
  1977  	)
  1978  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
  1979  	err = os.Symlink(kernelFn, kernelInSeed)
  1980  	c.Assert(err, IsNil)
  1981  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  1982  type: gadget
  1983  version: 5.0
  1984  `, snap.R(4))
  1985  	gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
  1986  	err = os.Symlink(gadgetFn, gadgetInSeed)
  1987  	c.Assert(err, IsNil)
  1988  
  1989  	bootWith := &boot.BootableSet{
  1990  		BasePath:          baseInSeed,
  1991  		Base:              baseInfo,
  1992  		KernelPath:        kernelInSeed,
  1993  		Kernel:            kernelInfo,
  1994  		Gadget:            gadgetInfo,
  1995  		GadgetPath:        gadgetInSeed,
  1996  		Recovery:          false,
  1997  		UnpackedGadgetDir: unpackedGadgetDir,
  1998  	}
  1999  
  2000  	err = boot.MakeRunnableSystem(model, bootWith, nil)
  2001  	c.Assert(err, IsNil)
  2002  
  2003  	// ensure that there are no good recovery systems as RecoverySystemLabel was empty
  2004  	mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
  2005  	c.Check(mockSeedGrubenv, testutil.FilePresent)
  2006  	systemGenv := grubenv.NewEnv(mockSeedGrubenv)
  2007  	c.Assert(systemGenv.Load(), IsNil)
  2008  	c.Check(systemGenv.Get("snapd_good_recovery_systems"), Equals, "")
  2009  }
  2010  
  2011  func (s *makeBootable20Suite) TestMakeRunnableSystemStandaloneSnapsCopy(c *C) {
  2012  	bootloader.Force(nil)
  2013  	model := boottest.MakeMockUC20Model()
  2014  	snapsDirs := filepath.Join(s.rootdir, "/somewhere")
  2015  	err := os.MkdirAll(snapsDirs, 0755)
  2016  	c.Assert(err, IsNil)
  2017  
  2018  	// grub on ubuntu-seed
  2019  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
  2020  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
  2021  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
  2022  	c.Assert(err, IsNil)
  2023  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
  2024  	c.Assert(err, IsNil)
  2025  	genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv"))
  2026  	c.Assert(genv.Save(), IsNil)
  2027  
  2028  	// mock grub so it is detected as the current bootloader
  2029  	unpackedGadgetDir := c.MkDir()
  2030  	grubRecoveryCfg := []byte("#grub-recovery cfg")
  2031  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
  2032  	grubCfg := []byte("#grub cfg")
  2033  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
  2034  	snaptest.PopulateDir(unpackedGadgetDir, [][]string{
  2035  		{"grub-recovery.conf", string(grubRecoveryCfg)},
  2036  		{"grub.conf", string(grubCfg)},
  2037  		{"bootx64.efi", "shim content"},
  2038  		{"grubx64.efi", "grub content"},
  2039  		{"meta/snap.yaml", gadgetSnapYaml},
  2040  	})
  2041  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
  2042  	defer restore()
  2043  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
  2044  	defer restore()
  2045  
  2046  	// make the snaps symlinks so that we can ensure that makebootable follows
  2047  	// the symlinks and copies the files and not the symlinks
  2048  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
  2049  type: base
  2050  version: 5.0
  2051  `, snap.R(3))
  2052  	baseInSeed := filepath.Join(snapsDirs, "core20")
  2053  	err = os.Symlink(baseFn, baseInSeed)
  2054  	c.Assert(err, IsNil)
  2055  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
  2056  type: kernel
  2057  version: 4.1
  2058  `, snap.R(5),
  2059  		[][]string{
  2060  			{"kernel.efi", "I'm a kernel.efi"},
  2061  		},
  2062  	)
  2063  	kernelInSeed := filepath.Join(snapsDirs, "pc-kernel_4.1.snap")
  2064  	err = os.Symlink(kernelFn, kernelInSeed)
  2065  	c.Assert(err, IsNil)
  2066  	gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc
  2067  type: gadget
  2068  version: 3.0
  2069  `, snap.R(4))
  2070  	gadgetInSeed := filepath.Join(snapsDirs, "pc_3.0.snap")
  2071  	err = os.Symlink(gadgetFn, gadgetInSeed)
  2072  	c.Assert(err, IsNil)
  2073  
  2074  	bootWith := &boot.BootableSet{
  2075  		RecoverySystemLabel: "20221004",
  2076  		BasePath:            baseInSeed,
  2077  		Base:                baseInfo,
  2078  		KernelPath:          kernelInSeed,
  2079  		Kernel:              kernelInfo,
  2080  		Gadget:              gadgetInfo,
  2081  		GadgetPath:          gadgetInSeed,
  2082  		Recovery:            false,
  2083  		UnpackedGadgetDir:   unpackedGadgetDir,
  2084  	}
  2085  
  2086  	err = boot.MakeRunnableSystem(model, bootWith, nil)
  2087  	c.Assert(err, IsNil)
  2088  
  2089  	installHostWritableDir := filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")
  2090  	// ensure base/gadget/kernel got copied to /var/lib/snapd/snaps
  2091  	core20Snap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "core20_3.snap")
  2092  	gadgetSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc_4.snap")
  2093  	pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc-kernel_5.snap")
  2094  	c.Check(core20Snap, testutil.FilePresent)
  2095  	c.Check(gadgetSnap, testutil.FilePresent)
  2096  	c.Check(pcKernelSnap, testutil.FilePresent)
  2097  	c.Check(osutil.IsSymlink(core20Snap), Equals, false)
  2098  	c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false)
  2099  	c.Check(osutil.IsSymlink(gadgetSnap), Equals, false)
  2100  
  2101  	// check modeenv
  2102  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
  2103  	expectedModeenv := `mode=run
  2104  recovery_system=20221004
  2105  current_recovery_systems=20221004
  2106  good_recovery_systems=20221004
  2107  base=core20_3.snap
  2108  gadget=pc_4.snap
  2109  current_kernels=pc-kernel_5.snap
  2110  model=my-brand/my-model-uc20
  2111  grade=dangerous
  2112  model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
  2113  current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"]
  2114  `
  2115  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, expectedModeenv)
  2116  }
  2117  
  2118  func (s *makeBootable20Suite) TestMakeStandaloneSystemRunnable20Install(c *C) {
  2119  	const standalone = true
  2120  	const factoryReset = false
  2121  	const classic = false
  2122  	s.testMakeSystemRunnable20(c, standalone, factoryReset, classic)
  2123  }
  2124  
  2125  func (s *makeBootable20Suite) TestMakeStandaloneSystemRunnable20InstallOnClassic(c *C) {
  2126  	const standalone = true
  2127  	const factoryReset = false
  2128  	const classic = true
  2129  	s.testMakeSystemRunnable20(c, standalone, factoryReset, classic)
  2130  }