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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package boot_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/boot/boottest"
    33  	"github.com/snapcore/snapd/bootloader"
    34  	"github.com/snapcore/snapd/bootloader/assets"
    35  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    36  	"github.com/snapcore/snapd/bootloader/grubenv"
    37  	"github.com/snapcore/snapd/bootloader/ubootenv"
    38  	"github.com/snapcore/snapd/dirs"
    39  	"github.com/snapcore/snapd/gadget"
    40  	"github.com/snapcore/snapd/osutil"
    41  	"github.com/snapcore/snapd/secboot"
    42  	"github.com/snapcore/snapd/seed"
    43  	"github.com/snapcore/snapd/snap"
    44  	"github.com/snapcore/snapd/snap/snapfile"
    45  	"github.com/snapcore/snapd/snap/snaptest"
    46  	"github.com/snapcore/snapd/testutil"
    47  	"github.com/snapcore/snapd/timings"
    48  )
    49  
    50  type makeBootableSuite struct {
    51  	baseBootenvSuite
    52  
    53  	bootloader *bootloadertest.MockBootloader
    54  }
    55  
    56  var _ = Suite(&makeBootableSuite{})
    57  
    58  func (s *makeBootableSuite) SetUpTest(c *C) {
    59  	s.baseBootenvSuite.SetUpTest(c)
    60  
    61  	s.bootloader = bootloadertest.Mock("mock", c.MkDir())
    62  	s.forceBootloader(s.bootloader)
    63  }
    64  
    65  func makeSnap(c *C, name, yaml string, revno snap.Revision) (fn string, info *snap.Info) {
    66  	return makeSnapWithFiles(c, name, yaml, revno, nil)
    67  }
    68  
    69  func makeSnapWithFiles(c *C, name, yaml string, revno snap.Revision, files [][]string) (fn string, info *snap.Info) {
    70  	si := &snap.SideInfo{
    71  		RealName: name,
    72  		Revision: revno,
    73  	}
    74  	fn = snaptest.MakeTestSnapWithFiles(c, yaml, files)
    75  	snapf, err := snapfile.Open(fn)
    76  	c.Assert(err, IsNil)
    77  	info, err = snap.ReadInfoFromSnapFile(snapf, si)
    78  	c.Assert(err, IsNil)
    79  	return fn, info
    80  }
    81  
    82  func (s *makeBootableSuite) TestMakeBootableImage(c *C) {
    83  	bootloader.Force(nil)
    84  	model := boottest.MakeMockModel()
    85  
    86  	grubCfg := []byte("#grub cfg")
    87  	unpackedGadgetDir := c.MkDir()
    88  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
    89  	c.Assert(err, IsNil)
    90  
    91  	seedSnapsDirs := filepath.Join(s.rootdir, "/var/lib/snapd/seed", "snaps")
    92  	err = os.MkdirAll(seedSnapsDirs, 0755)
    93  	c.Assert(err, IsNil)
    94  
    95  	baseFn, baseInfo := makeSnap(c, "core18", `name: core18
    96  type: base
    97  version: 4.0
    98  `, snap.R(3))
    99  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   100  	err = os.Rename(baseFn, baseInSeed)
   101  	c.Assert(err, IsNil)
   102  	kernelFn, kernelInfo := makeSnap(c, "pc-kernel", `name: pc-kernel
   103  type: kernel
   104  version: 4.0
   105  `, snap.R(5))
   106  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   107  	err = os.Rename(kernelFn, kernelInSeed)
   108  	c.Assert(err, IsNil)
   109  
   110  	bootWith := &boot.BootableSet{
   111  		Base:              baseInfo,
   112  		BasePath:          baseInSeed,
   113  		Kernel:            kernelInfo,
   114  		KernelPath:        kernelInSeed,
   115  		UnpackedGadgetDir: unpackedGadgetDir,
   116  	}
   117  
   118  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   119  	c.Assert(err, IsNil)
   120  
   121  	// check the bootloader config
   122  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "boot/grub/grubenv"))
   123  	c.Assert(seedGenv.Load(), IsNil)
   124  	c.Check(seedGenv.Get("snap_kernel"), Equals, "pc-kernel_5.snap")
   125  	c.Check(seedGenv.Get("snap_core"), Equals, "core18_3.snap")
   126  	c.Check(seedGenv.Get("snap_menuentry"), Equals, "My Model")
   127  
   128  	// check symlinks from snap blob dir
   129  	kernelBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename())
   130  	dst, err := os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename()))
   131  	c.Assert(err, IsNil)
   132  	c.Check(dst, Equals, "../seed/snaps/pc-kernel_5.snap")
   133  	c.Check(kernelBlob, testutil.FilePresent)
   134  
   135  	baseBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename())
   136  	dst, err = os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename()))
   137  	c.Assert(err, IsNil)
   138  	c.Check(dst, Equals, "../seed/snaps/core18_3.snap")
   139  	c.Check(baseBlob, testutil.FilePresent)
   140  
   141  	// check that the bootloader (grub here) configuration was copied
   142  	c.Check(filepath.Join(s.rootdir, "boot", "grub/grub.cfg"), testutil.FileEquals, grubCfg)
   143  }
   144  
   145  type makeBootable20Suite struct {
   146  	baseBootenvSuite
   147  
   148  	bootloader *bootloadertest.MockRecoveryAwareBootloader
   149  }
   150  
   151  type makeBootable20UbootSuite struct {
   152  	baseBootenvSuite
   153  
   154  	bootloader *bootloadertest.MockExtractedRecoveryKernelImageBootloader
   155  }
   156  
   157  var _ = Suite(&makeBootable20Suite{})
   158  var _ = Suite(&makeBootable20UbootSuite{})
   159  
   160  func (s *makeBootable20Suite) SetUpTest(c *C) {
   161  	s.baseBootenvSuite.SetUpTest(c)
   162  
   163  	s.bootloader = bootloadertest.Mock("mock", c.MkDir()).RecoveryAware()
   164  	s.forceBootloader(s.bootloader)
   165  }
   166  
   167  func (s *makeBootable20UbootSuite) SetUpTest(c *C) {
   168  	s.baseBootenvSuite.SetUpTest(c)
   169  
   170  	s.bootloader = bootloadertest.Mock("mock", c.MkDir()).ExtractedRecoveryKernelImage()
   171  	s.forceBootloader(s.bootloader)
   172  }
   173  
   174  func (s *makeBootable20Suite) TestMakeBootableImage20(c *C) {
   175  	bootloader.Force(nil)
   176  	model := boottest.MakeMockUC20Model()
   177  
   178  	unpackedGadgetDir := c.MkDir()
   179  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   180  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   181  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644)
   182  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
   183  	defer restore()
   184  
   185  	c.Assert(err, IsNil)
   186  	grubCfg := []byte("#grub cfg")
   187  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   188  	c.Assert(err, IsNil)
   189  
   190  	// on uc20 the seed layout if different
   191  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   192  	err = os.MkdirAll(seedSnapsDirs, 0755)
   193  	c.Assert(err, IsNil)
   194  
   195  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   196  type: base
   197  version: 5.0
   198  `, snap.R(3))
   199  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   200  	err = os.Rename(baseFn, baseInSeed)
   201  	c.Assert(err, IsNil)
   202  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   203  type: kernel
   204  version: 5.0
   205  `, snap.R(5), [][]string{
   206  		{"kernel.efi", "I'm a kernel.efi"},
   207  	})
   208  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   209  	err = os.Rename(kernelFn, kernelInSeed)
   210  	c.Assert(err, IsNil)
   211  
   212  	label := "20191209"
   213  	recoverySystemDir := filepath.Join("/systems", label)
   214  	bootWith := &boot.BootableSet{
   215  		Base:                baseInfo,
   216  		BasePath:            baseInSeed,
   217  		Kernel:              kernelInfo,
   218  		KernelPath:          kernelInSeed,
   219  		RecoverySystemDir:   recoverySystemDir,
   220  		RecoverySystemLabel: label,
   221  		UnpackedGadgetDir:   unpackedGadgetDir,
   222  		Recovery:            true,
   223  	}
   224  
   225  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   226  	c.Assert(err, IsNil)
   227  
   228  	// ensure only a single file got copied (the grub.cfg)
   229  	files, err := filepath.Glob(filepath.Join(s.rootdir, "EFI/ubuntu/*"))
   230  	c.Assert(err, IsNil)
   231  	// grub.cfg and grubenv
   232  	c.Check(files, HasLen, 2)
   233  	// check that the recovery bootloader configuration was installed with
   234  	// the correct content
   235  	c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset)
   236  
   237  	// ensure no /boot was setup
   238  	c.Check(filepath.Join(s.rootdir, "boot"), testutil.FileAbsent)
   239  
   240  	// ensure the correct recovery system configuration was set
   241  	seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"))
   242  	c.Assert(seedGenv.Load(), IsNil)
   243  	c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label)
   244  
   245  	systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv"))
   246  	c.Assert(systemGenv.Load(), IsNil)
   247  	c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap")
   248  }
   249  
   250  func (s *makeBootable20Suite) TestMakeBootableImage20UnsetRecoverySystemLabelError(c *C) {
   251  	model := boottest.MakeMockUC20Model()
   252  
   253  	unpackedGadgetDir := c.MkDir()
   254  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   255  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644)
   256  	c.Assert(err, IsNil)
   257  	grubCfg := []byte("#grub cfg")
   258  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   259  	c.Assert(err, IsNil)
   260  
   261  	label := "20191209"
   262  	recoverySystemDir := filepath.Join("/systems", label)
   263  	bootWith := &boot.BootableSet{
   264  		RecoverySystemDir: recoverySystemDir,
   265  		UnpackedGadgetDir: unpackedGadgetDir,
   266  		Recovery:          true,
   267  	}
   268  
   269  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   270  	c.Assert(err, ErrorMatches, "internal error: recovery system label unset")
   271  }
   272  
   273  func (s *makeBootable20Suite) TestMakeBootableImage20MultipleRecoverySystemsError(c *C) {
   274  	model := boottest.MakeMockUC20Model()
   275  
   276  	bootWith := &boot.BootableSet{Recovery: true}
   277  	err := os.MkdirAll(filepath.Join(s.rootdir, "systems/20191204"), 0755)
   278  	c.Assert(err, IsNil)
   279  	err = os.MkdirAll(filepath.Join(s.rootdir, "systems/20191205"), 0755)
   280  	c.Assert(err, IsNil)
   281  
   282  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   283  	c.Assert(err, ErrorMatches, "cannot make multiple recovery systems bootable yet")
   284  }
   285  
   286  func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) {
   287  	model := boottest.MakeMockModel()
   288  
   289  	err := boot.MakeRunnableSystem(model, nil, nil)
   290  	c.Assert(err, ErrorMatches, "internal error: cannot make non-uc20 system runnable")
   291  }
   292  
   293  func (s *makeBootable20Suite) TestMakeSystemRunnable20(c *C) {
   294  	bootloader.Force(nil)
   295  
   296  	model := boottest.MakeMockUC20Model()
   297  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   298  	err := os.MkdirAll(seedSnapsDirs, 0755)
   299  	c.Assert(err, IsNil)
   300  
   301  	// grub on ubuntu-seed
   302  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   303  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   304  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   305  	c.Assert(err, IsNil)
   306  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   307  	c.Assert(err, IsNil)
   308  
   309  	// setup recovery boot assets
   310  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   311  	c.Assert(err, IsNil)
   312  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
   313  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
   314  		[]byte("recovery shim content"), 0644)
   315  	c.Assert(err, IsNil)
   316  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
   317  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
   318  		[]byte("recovery grub content"), 0644)
   319  	c.Assert(err, IsNil)
   320  
   321  	// grub on ubuntu-boot
   322  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   323  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   324  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   325  	c.Assert(err, IsNil)
   326  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   327  	c.Assert(err, IsNil)
   328  
   329  	unpackedGadgetDir := c.MkDir()
   330  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   331  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   332  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644)
   333  	c.Assert(err, IsNil)
   334  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
   335  	defer restore()
   336  	grubCfg := []byte("#grub cfg")
   337  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   338  	c.Assert(err, IsNil)
   339  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
   340  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
   341  	defer restore()
   342  
   343  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "bootx64.efi"), []byte("shim content"), 0644)
   344  	c.Assert(err, IsNil)
   345  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grubx64.efi"), []byte("grub content"), 0644)
   346  	c.Assert(err, IsNil)
   347  
   348  	// make the snaps symlinks so that we can ensure that makebootable follows
   349  	// the symlinks and copies the files and not the symlinks
   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.Symlink(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),
   361  		[][]string{
   362  			{"kernel.efi", "I'm a kernel.efi"},
   363  		},
   364  	)
   365  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   366  	err = os.Symlink(kernelFn, kernelInSeed)
   367  	c.Assert(err, IsNil)
   368  
   369  	bootWith := &boot.BootableSet{
   370  		RecoverySystemDir: "20191216",
   371  		BasePath:          baseInSeed,
   372  		Base:              baseInfo,
   373  		KernelPath:        kernelInSeed,
   374  		Kernel:            kernelInfo,
   375  		Recovery:          false,
   376  		UnpackedGadgetDir: unpackedGadgetDir,
   377  	}
   378  
   379  	// set up observer state
   380  	useEncryption := true
   381  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
   382  	c.Assert(obs, NotNil)
   383  	c.Assert(err, IsNil)
   384  	runBootStruct := &gadget.LaidOutStructure{
   385  		VolumeStructure: &gadget.VolumeStructure{
   386  			Role: gadget.SystemBoot,
   387  		},
   388  	}
   389  
   390  	// only grubx64.efi gets installed to system-boot
   391  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
   392  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
   393  	c.Assert(err, IsNil)
   394  
   395  	// observe recovery assets
   396  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
   397  	c.Assert(err, IsNil)
   398  
   399  	// set encryption key
   400  	myKey := secboot.EncryptionKey{}
   401  	myKey2 := secboot.EncryptionKey{}
   402  	for i := range myKey {
   403  		myKey[i] = byte(i)
   404  		myKey2[i] = byte(128 + i)
   405  	}
   406  	obs.ChosenEncryptionKeys(myKey, myKey2)
   407  
   408  	// set a mock recovery kernel
   409  	readSystemEssentialCalls := 0
   410  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   411  		readSystemEssentialCalls++
   412  		kernelSnap := &seed.Snap{
   413  			Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   414  			SideInfo: &snap.SideInfo{
   415  				Revision: snap.Revision{N: 1},
   416  				RealName: "pc-kernel",
   417  			},
   418  		}
   419  		return model, []*seed.Snap{kernelSnap}, nil
   420  	})
   421  	defer restore()
   422  
   423  	// set mock key sealing
   424  	sealKeysCalls := 0
   425  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   426  		sealKeysCalls++
   427  		switch sealKeysCalls {
   428  		case 1:
   429  			c.Check(keys, HasLen, 1)
   430  			c.Check(keys[0].Key, DeepEquals, myKey)
   431  		case 2:
   432  			c.Check(keys, HasLen, 2)
   433  			c.Check(keys[0].Key, DeepEquals, myKey)
   434  			c.Check(keys[1].Key, DeepEquals, myKey2)
   435  		default:
   436  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   437  		}
   438  		c.Assert(params.ModelParams, HasLen, 1)
   439  
   440  		shim := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   441  			"var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"),
   442  			bootloader.RoleRecovery)
   443  		grub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   444  			"var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"),
   445  			bootloader.RoleRecovery)
   446  		runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   447  			"var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"),
   448  			bootloader.RoleRunMode)
   449  		kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   450  		runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode)
   451  
   452  		switch sealKeysCalls {
   453  		case 1:
   454  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   455  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   456  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))),
   457  			})
   458  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   459  				"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   460  				"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   461  			})
   462  		case 2:
   463  			c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   464  				secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   465  			})
   466  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   467  				"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   468  			})
   469  		default:
   470  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   471  		}
   472  
   473  		c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model")
   474  
   475  		return nil
   476  	})
   477  	defer restore()
   478  
   479  	err = boot.MakeRunnableSystem(model, bootWith, obs)
   480  	c.Assert(err, IsNil)
   481  
   482  	// also do the logical thing and make the next boot go to run mode
   483  	err = boot.EnsureNextBootToRunMode("20191216")
   484  	c.Assert(err, IsNil)
   485  
   486  	// ensure grub.cfg in boot was installed from internal assets
   487  	c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset))
   488  
   489  	// ensure base/kernel got copied to /var/lib/snapd/snaps
   490  	core20Snap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap")
   491  	pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "pc-kernel_5.snap")
   492  	c.Check(core20Snap, testutil.FilePresent)
   493  	c.Check(pcKernelSnap, testutil.FilePresent)
   494  	c.Check(osutil.IsSymlink(core20Snap), Equals, false)
   495  	c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false)
   496  
   497  	// ensure the bootvars got updated the right way
   498  	mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
   499  	c.Check(mockSeedGrubenv, testutil.FilePresent)
   500  	c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run")
   501  	mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv")
   502  	c.Check(mockBootGrubenv, testutil.FilePresent)
   503  
   504  	// ensure that kernel_status is empty, we specifically want this to be set
   505  	// to the empty string
   506  	// use (?m) to match multi-line file in the regex here, because the file is
   507  	// a grubenv with padding #### blocks
   508  	c.Check(mockBootGrubenv, testutil.FileMatches, `(?m)^kernel_status=$`)
   509  
   510  	// check that we have the extracted kernel in the right places, both in the
   511  	// old uc16/uc18 location and the new ubuntu-boot partition grub dir
   512  	extractedKernel := filepath.Join(mockBootGrubDir, "pc-kernel_5.snap", "kernel.efi")
   513  	c.Check(extractedKernel, testutil.FilePresent)
   514  
   515  	// the new uc20 location
   516  	extractedKernelSymlink := filepath.Join(mockBootGrubDir, "kernel.efi")
   517  	c.Check(extractedKernelSymlink, testutil.FilePresent)
   518  
   519  	// ensure modeenv looks correct
   520  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
   521  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run
   522  recovery_system=20191216
   523  current_recovery_systems=20191216
   524  good_recovery_systems=20191216
   525  base=core20_3.snap
   526  current_kernels=pc-kernel_5.snap
   527  model=my-brand/my-model-uc20
   528  grade=dangerous
   529  current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]}
   530  current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]}
   531  current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"]
   532  `)
   533  	copiedGrubBin := filepath.Join(
   534  		dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir),
   535  		"grub",
   536  		"grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d",
   537  	)
   538  	copiedRecoveryGrubBin := filepath.Join(
   539  		dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir),
   540  		"grub",
   541  		"grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5",
   542  	)
   543  	copiedRecoveryShimBin := filepath.Join(
   544  		dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir),
   545  		"grub",
   546  		"bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37",
   547  	)
   548  
   549  	// only one file in the cache under new root
   550  	checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDirUnder(boot.InstallHostWritableDir), "grub", "*"), []string{
   551  		copiedRecoveryShimBin,
   552  		copiedGrubBin,
   553  		copiedRecoveryGrubBin,
   554  	})
   555  	// with the right content
   556  	c.Check(copiedGrubBin, testutil.FileEquals, "grub content")
   557  	c.Check(copiedRecoveryGrubBin, testutil.FileEquals, "recovery grub content")
   558  	c.Check(copiedRecoveryShimBin, testutil.FileEquals, "recovery shim content")
   559  
   560  	// make sure SealKey was called for the run object and the fallback object
   561  	c.Check(sealKeysCalls, Equals, 2)
   562  
   563  	// make sure the marker file for sealed key was created
   564  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent)
   565  
   566  	// make sure we wrote the boot chains data file
   567  	c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent)
   568  }
   569  
   570  func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) {
   571  	bootloader.Force(nil)
   572  
   573  	model := boottest.MakeMockUC20Model()
   574  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   575  	err := os.MkdirAll(seedSnapsDirs, 0755)
   576  	c.Assert(err, IsNil)
   577  
   578  	// grub on ubuntu-seed
   579  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   580  	err = os.MkdirAll(mockSeedGrubDir, 0755)
   581  	c.Assert(err, IsNil)
   582  	// no recovery grub.cfg so that test fails if it ever reaches that point
   583  
   584  	// grub on ubuntu-boot
   585  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   586  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   587  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   588  	c.Assert(err, IsNil)
   589  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   590  	c.Assert(err, IsNil)
   591  
   592  	unpackedGadgetDir := c.MkDir()
   593  
   594  	// make the snaps symlinks so that we can ensure that makebootable follows
   595  	// the symlinks and copies the files and not the symlinks
   596  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   597  type: base
   598  version: 5.0
   599  `, snap.R(3))
   600  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   601  	err = os.Symlink(baseFn, baseInSeed)
   602  	c.Assert(err, IsNil)
   603  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   604  type: kernel
   605  version: 5.0
   606  `, snap.R(5),
   607  		[][]string{
   608  			{"kernel.efi", "I'm a kernel.efi"},
   609  		},
   610  	)
   611  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   612  	err = os.Symlink(kernelFn, kernelInSeed)
   613  	c.Assert(err, IsNil)
   614  
   615  	bootWith := &boot.BootableSet{
   616  		RecoverySystemDir: "20191216",
   617  		BasePath:          baseInSeed,
   618  		Base:              baseInfo,
   619  		KernelPath:        kernelInSeed,
   620  		Kernel:            kernelInfo,
   621  		Recovery:          false,
   622  		UnpackedGadgetDir: unpackedGadgetDir,
   623  	}
   624  
   625  	// no grub marker in gadget directory raises an error
   626  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   627  	c.Assert(err, ErrorMatches, "internal error: cannot identify run system bootloader: cannot determine bootloader")
   628  
   629  	// set up grub.cfg in gadget
   630  	grubCfg := []byte("#grub cfg")
   631  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   632  	c.Assert(err, IsNil)
   633  
   634  	// no write access to destination directory
   635  	restore := assets.MockInternal("grub.cfg", nil)
   636  	defer restore()
   637  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   638  	c.Assert(err, ErrorMatches, `cannot install managed bootloader assets: internal error: no boot asset for "grub.cfg"`)
   639  }
   640  
   641  func (s *makeBootable20Suite) TestMakeRunnableSystem20RunModeSealKeyErr(c *C) {
   642  	bootloader.Force(nil)
   643  
   644  	model := boottest.MakeMockUC20Model()
   645  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   646  	err := os.MkdirAll(seedSnapsDirs, 0755)
   647  	c.Assert(err, IsNil)
   648  
   649  	// grub on ubuntu-seed
   650  	mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
   651  	mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
   652  	err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
   653  	c.Assert(err, IsNil)
   654  	err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
   655  	c.Assert(err, IsNil)
   656  
   657  	// setup recovery boot assets
   658  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755)
   659  	c.Assert(err, IsNil)
   660  	// SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37
   661  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"),
   662  		[]byte("recovery shim content"), 0644)
   663  	c.Assert(err, IsNil)
   664  	// SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5
   665  	err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"),
   666  		[]byte("recovery grub content"), 0644)
   667  	c.Assert(err, IsNil)
   668  
   669  	// grub on ubuntu-boot
   670  	mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
   671  	mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
   672  	err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
   673  	c.Assert(err, IsNil)
   674  	err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644)
   675  	c.Assert(err, IsNil)
   676  
   677  	unpackedGadgetDir := c.MkDir()
   678  	grubRecoveryCfg := []byte("#grub-recovery cfg")
   679  	grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets")
   680  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644)
   681  	c.Assert(err, IsNil)
   682  	restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset)
   683  	defer restore()
   684  	grubCfg := []byte("#grub cfg")
   685  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
   686  	c.Assert(err, IsNil)
   687  	grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets")
   688  	restore = assets.MockInternal("grub.cfg", grubCfgAsset)
   689  	defer restore()
   690  
   691  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "bootx64.efi"), []byte("shim content"), 0644)
   692  	c.Assert(err, IsNil)
   693  	err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grubx64.efi"), []byte("grub content"), 0644)
   694  	c.Assert(err, IsNil)
   695  
   696  	// make the snaps symlinks so that we can ensure that makebootable follows
   697  	// the symlinks and copies the files and not the symlinks
   698  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   699  type: base
   700  version: 5.0
   701  `, snap.R(3))
   702  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   703  	err = os.Symlink(baseFn, baseInSeed)
   704  	c.Assert(err, IsNil)
   705  	kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
   706  type: kernel
   707  version: 5.0
   708  `, snap.R(5),
   709  		[][]string{
   710  			{"kernel.efi", "I'm a kernel.efi"},
   711  		},
   712  	)
   713  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   714  	err = os.Symlink(kernelFn, kernelInSeed)
   715  	c.Assert(err, IsNil)
   716  
   717  	bootWith := &boot.BootableSet{
   718  		RecoverySystemDir: "20191216",
   719  		BasePath:          baseInSeed,
   720  		Base:              baseInfo,
   721  		KernelPath:        kernelInSeed,
   722  		Kernel:            kernelInfo,
   723  		Recovery:          false,
   724  		UnpackedGadgetDir: unpackedGadgetDir,
   725  	}
   726  
   727  	// set up observer state
   728  	useEncryption := true
   729  	obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption)
   730  	c.Assert(obs, NotNil)
   731  	c.Assert(err, IsNil)
   732  	runBootStruct := &gadget.LaidOutStructure{
   733  		VolumeStructure: &gadget.VolumeStructure{
   734  			Role: gadget.SystemBoot,
   735  		},
   736  	}
   737  
   738  	// only grubx64.efi gets installed to system-boot
   739  	_, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi",
   740  		&gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")})
   741  	c.Assert(err, IsNil)
   742  
   743  	// observe recovery assets
   744  	err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir)
   745  	c.Assert(err, IsNil)
   746  
   747  	// set encryption key
   748  	myKey := secboot.EncryptionKey{}
   749  	myKey2 := secboot.EncryptionKey{}
   750  	for i := range myKey {
   751  		myKey[i] = byte(i)
   752  		myKey2[i] = byte(128 + i)
   753  	}
   754  	obs.ChosenEncryptionKeys(myKey, myKey2)
   755  
   756  	// set a mock recovery kernel
   757  	readSystemEssentialCalls := 0
   758  	restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   759  		readSystemEssentialCalls++
   760  		kernelSnap := &seed.Snap{
   761  			Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
   762  			SideInfo: &snap.SideInfo{
   763  				Revision: snap.Revision{N: 0},
   764  				RealName: "pc-kernel",
   765  			},
   766  		}
   767  		return model, []*seed.Snap{kernelSnap}, nil
   768  	})
   769  	defer restore()
   770  
   771  	// set mock key sealing
   772  	sealKeysCalls := 0
   773  	restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error {
   774  		sealKeysCalls++
   775  		switch sealKeysCalls {
   776  		case 1:
   777  			c.Check(keys, HasLen, 1)
   778  			c.Check(keys[0].Key, DeepEquals, myKey)
   779  		case 2:
   780  			c.Check(keys, HasLen, 2)
   781  			c.Check(keys[0].Key, DeepEquals, myKey)
   782  			c.Check(keys[1].Key, DeepEquals, myKey2)
   783  		default:
   784  			c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls)
   785  		}
   786  		c.Assert(params.ModelParams, HasLen, 1)
   787  
   788  		shim := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   789  			"var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"),
   790  			bootloader.RoleRecovery)
   791  		grub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   792  			"var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"),
   793  			bootloader.RoleRecovery)
   794  		runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir,
   795  			"var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"),
   796  			bootloader.RoleRunMode)
   797  		kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
   798  		runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode)
   799  
   800  		c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{
   801  			secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))),
   802  			secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))),
   803  		})
   804  		c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   805  			"snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1",
   806  			"snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
   807  		})
   808  		c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model")
   809  
   810  		return fmt.Errorf("seal error")
   811  	})
   812  	defer restore()
   813  
   814  	err = boot.MakeRunnableSystem(model, bootWith, obs)
   815  	c.Assert(err, ErrorMatches, "cannot seal the encryption keys: seal error")
   816  }
   817  
   818  func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20TraditionalUbootenvFails(c *C) {
   819  	bootloader.Force(nil)
   820  	model := boottest.MakeMockUC20Model()
   821  
   822  	unpackedGadgetDir := c.MkDir()
   823  	ubootEnv := []byte("#uboot env")
   824  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), ubootEnv, 0644)
   825  	c.Assert(err, IsNil)
   826  
   827  	// on uc20 the seed layout if different
   828  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   829  	err = os.MkdirAll(seedSnapsDirs, 0755)
   830  	c.Assert(err, IsNil)
   831  
   832  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   833  type: base
   834  version: 5.0
   835  `, snap.R(3))
   836  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   837  	err = os.Rename(baseFn, baseInSeed)
   838  	c.Assert(err, IsNil)
   839  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
   840  type: kernel
   841  version: 5.0
   842  `, snap.R(5), [][]string{
   843  		{"kernel.img", "I'm a kernel"},
   844  		{"initrd.img", "...and I'm an initrd"},
   845  		{"dtbs/foo.dtb", "foo dtb"},
   846  		{"dtbs/bar.dto", "bar dtbo"},
   847  	})
   848  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   849  	err = os.Rename(kernelFn, kernelInSeed)
   850  	c.Assert(err, IsNil)
   851  
   852  	label := "20191209"
   853  	recoverySystemDir := filepath.Join("/systems", label)
   854  	bootWith := &boot.BootableSet{
   855  		Base:                baseInfo,
   856  		BasePath:            baseInSeed,
   857  		Kernel:              kernelInfo,
   858  		KernelPath:          kernelInSeed,
   859  		RecoverySystemDir:   recoverySystemDir,
   860  		RecoverySystemLabel: label,
   861  		UnpackedGadgetDir:   unpackedGadgetDir,
   862  		Recovery:            true,
   863  	}
   864  
   865  	// TODO:UC20: enable this use case
   866  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   867  	c.Assert(err, ErrorMatches, "non-empty uboot.env not supported on UC20 yet")
   868  }
   869  
   870  func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20BootScr(c *C) {
   871  	model := boottest.MakeMockUC20Model()
   872  
   873  	unpackedGadgetDir := c.MkDir()
   874  	// the uboot.conf must be empty for this to work/do the right thing
   875  	err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644)
   876  	c.Assert(err, IsNil)
   877  
   878  	// on uc20 the seed layout if different
   879  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   880  	err = os.MkdirAll(seedSnapsDirs, 0755)
   881  	c.Assert(err, IsNil)
   882  
   883  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   884  type: base
   885  version: 5.0
   886  `, snap.R(3))
   887  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   888  	err = os.Rename(baseFn, baseInSeed)
   889  	c.Assert(err, IsNil)
   890  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
   891  type: kernel
   892  version: 5.0
   893  `, snap.R(5), [][]string{
   894  		{"kernel.img", "I'm a kernel"},
   895  		{"initrd.img", "...and I'm an initrd"},
   896  		{"dtbs/foo.dtb", "foo dtb"},
   897  		{"dtbs/bar.dto", "bar dtbo"},
   898  	})
   899  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   900  	err = os.Rename(kernelFn, kernelInSeed)
   901  	c.Assert(err, IsNil)
   902  
   903  	label := "20191209"
   904  	recoverySystemDir := filepath.Join("/systems", label)
   905  	bootWith := &boot.BootableSet{
   906  		Base:                baseInfo,
   907  		BasePath:            baseInSeed,
   908  		Kernel:              kernelInfo,
   909  		KernelPath:          kernelInSeed,
   910  		RecoverySystemDir:   recoverySystemDir,
   911  		RecoverySystemLabel: label,
   912  		UnpackedGadgetDir:   unpackedGadgetDir,
   913  		Recovery:            true,
   914  	}
   915  
   916  	err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil)
   917  	c.Assert(err, IsNil)
   918  
   919  	// since uboot.conf was absent, we won't have installed the uboot.env, as
   920  	// it is expected that the gadget assets would have installed boot.scr
   921  	// instead
   922  	c.Check(filepath.Join(s.rootdir, "uboot.env"), testutil.FileAbsent)
   923  
   924  	c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{
   925  		"snapd_recovery_system": label,
   926  		"snapd_recovery_mode":   "install",
   927  	})
   928  
   929  	// ensure the correct recovery system configuration was set
   930  	c.Check(
   931  		s.bootloader.ExtractRecoveryKernelAssetsCalls,
   932  		DeepEquals,
   933  		[]bootloadertest.ExtractedRecoveryKernelCall{{
   934  			RecoverySystemDir: recoverySystemDir,
   935  			S:                 kernelInfo,
   936  		}},
   937  	)
   938  }
   939  
   940  func (s *makeBootable20UbootSuite) TestUbootMakeRunnableSystem20RunModeBootSel(c *C) {
   941  	bootloader.Force(nil)
   942  
   943  	model := boottest.MakeMockUC20Model()
   944  	seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
   945  	err := os.MkdirAll(seedSnapsDirs, 0755)
   946  	c.Assert(err, IsNil)
   947  
   948  	// uboot on ubuntu-seed
   949  	mockSeedUbootBootSel := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel")
   950  	err = os.MkdirAll(filepath.Dir(mockSeedUbootBootSel), 0755)
   951  	c.Assert(err, IsNil)
   952  	env, err := ubootenv.Create(mockSeedUbootBootSel, 4096)
   953  	c.Assert(err, IsNil)
   954  	c.Assert(env.Save(), IsNil)
   955  
   956  	// uboot on ubuntu-boot (as if it was installed when creating the partition)
   957  	mockBootUbootBootSel := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel")
   958  	err = os.MkdirAll(filepath.Dir(mockBootUbootBootSel), 0755)
   959  	c.Assert(err, IsNil)
   960  	env, err = ubootenv.Create(mockBootUbootBootSel, 4096)
   961  	c.Assert(err, IsNil)
   962  	c.Assert(env.Save(), IsNil)
   963  
   964  	unpackedGadgetDir := c.MkDir()
   965  	c.Assert(ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644), IsNil)
   966  
   967  	baseFn, baseInfo := makeSnap(c, "core20", `name: core20
   968  type: base
   969  version: 5.0
   970  `, snap.R(3))
   971  	baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename())
   972  	err = os.Rename(baseFn, baseInSeed)
   973  	c.Assert(err, IsNil)
   974  	kernelSnapFiles := [][]string{
   975  		{"kernel.img", "I'm a kernel"},
   976  		{"initrd.img", "...and I'm an initrd"},
   977  		{"dtbs/foo.dtb", "foo dtb"},
   978  		{"dtbs/bar.dto", "bar dtbo"},
   979  	}
   980  	kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel
   981  type: kernel
   982  version: 5.0
   983  `, snap.R(5), kernelSnapFiles)
   984  	kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename())
   985  	err = os.Rename(kernelFn, kernelInSeed)
   986  	c.Assert(err, IsNil)
   987  
   988  	bootWith := &boot.BootableSet{
   989  		RecoverySystemDir: "20191216",
   990  		BasePath:          baseInSeed,
   991  		Base:              baseInfo,
   992  		KernelPath:        kernelInSeed,
   993  		Kernel:            kernelInfo,
   994  		Recovery:          false,
   995  		UnpackedGadgetDir: unpackedGadgetDir,
   996  	}
   997  	err = boot.MakeRunnableSystem(model, bootWith, nil)
   998  	c.Assert(err, IsNil)
   999  
  1000  	// also do the logical next thing which is to ensure that the system
  1001  	// reboots into run mode
  1002  	err = boot.EnsureNextBootToRunMode("20191216")
  1003  	c.Assert(err, IsNil)
  1004  
  1005  	// ensure base/kernel got copied to /var/lib/snapd/snaps
  1006  	c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "core20_3.snap"), testutil.FilePresent)
  1007  	c.Check(filepath.Join(dirs.SnapBlobDirUnder(boot.InstallHostWritableDir), "arm-kernel_5.snap"), testutil.FilePresent)
  1008  
  1009  	// ensure the bootvars on ubuntu-seed got updated the right way
  1010  	mockSeedUbootenv := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel")
  1011  	uenvSeed, err := ubootenv.Open(mockSeedUbootenv)
  1012  	c.Assert(err, IsNil)
  1013  	c.Assert(uenvSeed.Get("snapd_recovery_mode"), Equals, "run")
  1014  
  1015  	// now check ubuntu-boot boot.sel
  1016  	mockBootUbootenv := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel")
  1017  	uenvBoot, err := ubootenv.Open(mockBootUbootenv)
  1018  	c.Assert(err, IsNil)
  1019  	c.Assert(uenvBoot.Get("snap_try_kernel"), Equals, "")
  1020  	c.Assert(uenvBoot.Get("snap_kernel"), Equals, "arm-kernel_5.snap")
  1021  	c.Assert(uenvBoot.Get("kernel_status"), Equals, boot.DefaultStatus)
  1022  
  1023  	// check that we have the extracted kernel in the right places, in the
  1024  	// old uc16/uc18 location
  1025  	for _, file := range kernelSnapFiles {
  1026  		fName := file[0]
  1027  		c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/arm-kernel_5.snap", fName), testutil.FilePresent)
  1028  	}
  1029  
  1030  	// ensure modeenv looks correct
  1031  	ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv")
  1032  	c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run
  1033  recovery_system=20191216
  1034  current_recovery_systems=20191216
  1035  good_recovery_systems=20191216
  1036  base=core20_3.snap
  1037  current_kernels=arm-kernel_5.snap
  1038  model=my-brand/my-model-uc20
  1039  grade=dangerous
  1040  `)
  1041  }