github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-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 main_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"time"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/asserts/assertstest"
    35  	"github.com/snapcore/snapd/boot"
    36  	"github.com/snapcore/snapd/boot/boottest"
    37  	"github.com/snapcore/snapd/bootloader"
    38  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    39  	main "github.com/snapcore/snapd/cmd/snap-bootstrap"
    40  	"github.com/snapcore/snapd/dirs"
    41  	"github.com/snapcore/snapd/logger"
    42  	"github.com/snapcore/snapd/osutil"
    43  	"github.com/snapcore/snapd/osutil/disks"
    44  	"github.com/snapcore/snapd/seed"
    45  	"github.com/snapcore/snapd/seed/seedtest"
    46  	"github.com/snapcore/snapd/snap"
    47  	"github.com/snapcore/snapd/systemd"
    48  	"github.com/snapcore/snapd/testutil"
    49  )
    50  
    51  var brandPrivKey, _ = assertstest.GenerateKey(752)
    52  
    53  type initramfsMountsSuite struct {
    54  	testutil.BaseTest
    55  
    56  	// makes available a bunch of helper (like MakeAssertedSnap)
    57  	*seedtest.TestingSeed20
    58  
    59  	Stdout *bytes.Buffer
    60  
    61  	seedDir  string
    62  	sysLabel string
    63  	model    *asserts.Model
    64  	tmpDir   string
    65  
    66  	kernel   snap.PlaceInfo
    67  	kernelr2 snap.PlaceInfo
    68  	core20   snap.PlaceInfo
    69  	core20r2 snap.PlaceInfo
    70  	snapd    snap.PlaceInfo
    71  }
    72  
    73  var _ = Suite(&initramfsMountsSuite{})
    74  
    75  var (
    76  	tmpfsMountOpts = &main.SystemdMountOptions{
    77  		Tmpfs: true,
    78  	}
    79  	needsFsckDiskMountOpts = &main.SystemdMountOptions{
    80  		NeedsFsck: true,
    81  	}
    82  
    83  	defaultBootDisk = &disks.MockDiskMapping{
    84  		FilesystemLabelToPartUUID: map[string]string{
    85  			// ubuntu-boot not strictly necessary, since we mount it first we
    86  			// don't go looking for the label ubuntu-boot on a disk, we just
    87  			// mount it and hope it's what we need, unless we have UEFI vars or
    88  			// something
    89  			"ubuntu-boot": "ubuntu-boot-partuuid",
    90  			"ubuntu-seed": "ubuntu-seed-partuuid",
    91  			"ubuntu-data": "ubuntu-data-partuuid",
    92  		},
    93  		DiskHasPartitions: true,
    94  		DevNum:            "default",
    95  	}
    96  
    97  	defaultEncBootDisk = &disks.MockDiskMapping{
    98  		FilesystemLabelToPartUUID: map[string]string{
    99  			// ubuntu-boot not strictly necessary, since we mount it first we
   100  			// don't ever search a particular disk for the ubuntu-boot label,
   101  			// we just mount it and hope it's what we need, unless we have UEFI
   102  			// vars or something a la boot.PartitionUUIDForBootedKernelDisk
   103  			"ubuntu-boot":     "ubuntu-boot-partuuid",
   104  			"ubuntu-seed":     "ubuntu-seed-partuuid",
   105  			"ubuntu-data-enc": "ubuntu-data-enc-partuuid",
   106  		},
   107  		DiskHasPartitions: true,
   108  		DevNum:            "defaultEncDev",
   109  	}
   110  
   111  	mockStateContent = `{"data":{"auth":{"users":[{"id":1,"name":"mvo"}],"macaroon-key":"not-a-cookie","last-id":1}},"some":{"other":"stuff"}}`
   112  )
   113  
   114  // because 1.9 vet does not like xerrors.Errorf(".. %w")
   115  type mockedWrappedError struct {
   116  	err error
   117  	fmt string
   118  }
   119  
   120  func (m *mockedWrappedError) Unwrap() error { return m.err }
   121  
   122  func (m *mockedWrappedError) Error() string { return fmt.Sprintf(m.fmt, m.err) }
   123  
   124  func (s *initramfsMountsSuite) setupSeed(c *C, gadgetSnapFiles [][]string) {
   125  	// pretend /run/mnt/ubuntu-seed has a valid seed
   126  	s.seedDir = boot.InitramfsUbuntuSeedDir
   127  
   128  	// now create a minimal uc20 seed dir with snaps/assertions
   129  	seed20 := &seedtest.TestingSeed20{SeedDir: s.seedDir}
   130  	seed20.SetupAssertSigning("canonical")
   131  	restore := seed.MockTrusted(seed20.StoreSigning.Trusted)
   132  	s.AddCleanup(restore)
   133  
   134  	// XXX: we don't really use this but seedtest always expects my-brand
   135  	seed20.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
   136  		"verification": "verified",
   137  	})
   138  
   139  	// add a bunch of snaps
   140  	seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   141  	seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", gadgetSnapFiles, snap.R(1), "canonical", seed20.StoreSigning.Database)
   142  	seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   143  	seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
   144  
   145  	s.sysLabel = "20191118"
   146  	s.model = seed20.MakeSeed(c, s.sysLabel, "my-brand", "my-model", map[string]interface{}{
   147  		"display-name": "my model",
   148  		"architecture": "amd64",
   149  		"base":         "core20",
   150  		"snaps": []interface{}{
   151  			map[string]interface{}{
   152  				"name":            "pc-kernel",
   153  				"id":              seed20.AssertedSnapID("pc-kernel"),
   154  				"type":            "kernel",
   155  				"default-channel": "20",
   156  			},
   157  			map[string]interface{}{
   158  				"name":            "pc",
   159  				"id":              seed20.AssertedSnapID("pc"),
   160  				"type":            "gadget",
   161  				"default-channel": "20",
   162  			}},
   163  	}, nil)
   164  
   165  }
   166  
   167  func (s *initramfsMountsSuite) SetUpTest(c *C) {
   168  	s.BaseTest.SetUpTest(c)
   169  
   170  	s.Stdout = bytes.NewBuffer(nil)
   171  
   172  	_, restore := logger.MockLogger()
   173  	s.AddCleanup(restore)
   174  
   175  	s.tmpDir = c.MkDir()
   176  
   177  	// mock /run/mnt
   178  	dirs.SetRootDir(s.tmpDir)
   179  	restore = func() { dirs.SetRootDir("") }
   180  	s.AddCleanup(restore)
   181  
   182  	// setup the seed
   183  	s.setupSeed(c, nil)
   184  
   185  	// make test snap PlaceInfo's for various boot functionality
   186  	var err error
   187  	s.kernel, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
   188  	c.Assert(err, IsNil)
   189  
   190  	s.core20, err = snap.ParsePlaceInfoFromSnapFileName("core20_1.snap")
   191  	c.Assert(err, IsNil)
   192  
   193  	s.kernelr2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
   194  	c.Assert(err, IsNil)
   195  
   196  	s.core20r2, err = snap.ParsePlaceInfoFromSnapFileName("core20_2.snap")
   197  	c.Assert(err, IsNil)
   198  
   199  	s.snapd, err = snap.ParsePlaceInfoFromSnapFileName("snapd_1.snap")
   200  	c.Assert(err, IsNil)
   201  
   202  	// by default mock that we don't have UEFI vars, etc. to get the booted
   203  	// kernel partition partition uuid
   204  	s.AddCleanup(main.MockPartitionUUIDForBootedKernelDisk(""))
   205  }
   206  
   207  // makeSnapFilesOnEarlyBootUbuntuData creates the snap files on ubuntu-data as
   208  // we
   209  func makeSnapFilesOnEarlyBootUbuntuData(c *C, snaps ...snap.PlaceInfo) {
   210  	snapDir := dirs.SnapBlobDirUnder(boot.InitramfsWritableDir)
   211  	err := os.MkdirAll(snapDir, 0755)
   212  	c.Assert(err, IsNil)
   213  	for _, sn := range snaps {
   214  		snFilename := sn.Filename()
   215  		err = ioutil.WriteFile(filepath.Join(snapDir, snFilename), nil, 0644)
   216  		c.Assert(err, IsNil)
   217  	}
   218  }
   219  
   220  func (s *initramfsMountsSuite) mockProcCmdlineContent(c *C, newContent string) {
   221  	mockProcCmdline := filepath.Join(c.MkDir(), "proc-cmdline")
   222  	err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644)
   223  	c.Assert(err, IsNil)
   224  	restore := boot.MockProcCmdline(mockProcCmdline)
   225  	s.AddCleanup(restore)
   226  }
   227  
   228  func (s *initramfsMountsSuite) TestInitramfsMountsNoModeError(c *C) {
   229  	s.mockProcCmdlineContent(c, "nothing-to-see")
   230  
   231  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
   232  	c.Assert(err, ErrorMatches, "cannot detect mode nor recovery system to use")
   233  }
   234  
   235  func (s *initramfsMountsSuite) TestInitramfsMountsUnknownMode(c *C) {
   236  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=install-foo")
   237  
   238  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
   239  	c.Assert(err, ErrorMatches, `cannot use unknown mode "install-foo"`)
   240  }
   241  
   242  type systemdMount struct {
   243  	what  string
   244  	where string
   245  	opts  *main.SystemdMountOptions
   246  }
   247  
   248  // this is a function so we evaluate InitramfsUbuntuBootDir, etc at the time of
   249  // the test to pick up test-specific dirs.GlobalRootDir
   250  func ubuntuLabelMount(label string, mode string) systemdMount {
   251  	mnt := systemdMount{
   252  		opts: needsFsckDiskMountOpts,
   253  	}
   254  	switch label {
   255  	case "ubuntu-boot":
   256  		mnt.what = "/dev/disk/by-label/ubuntu-boot"
   257  		mnt.where = boot.InitramfsUbuntuBootDir
   258  	case "ubuntu-seed":
   259  		mnt.what = "/dev/disk/by-label/ubuntu-seed"
   260  		mnt.where = boot.InitramfsUbuntuSeedDir
   261  		// don't fsck in run mode
   262  		if mode == "run" {
   263  			mnt.opts = nil
   264  		}
   265  	case "ubuntu-data":
   266  		mnt.what = "/dev/disk/by-label/ubuntu-data"
   267  		mnt.where = boot.InitramfsDataDir
   268  	}
   269  
   270  	return mnt
   271  }
   272  
   273  // ubuntuPartUUIDMount returns a systemdMount for the partuuid disk, expecting
   274  // that the partuuid contains in it the expected label for easier coding
   275  func ubuntuPartUUIDMount(partuuid string, mode string) systemdMount {
   276  	mnt := systemdMount{
   277  		opts: needsFsckDiskMountOpts,
   278  	}
   279  	mnt.what = filepath.Join("/dev/disk/by-partuuid", partuuid)
   280  	switch {
   281  	case strings.Contains(partuuid, "ubuntu-boot"):
   282  		mnt.where = boot.InitramfsUbuntuBootDir
   283  	case strings.Contains(partuuid, "ubuntu-seed"):
   284  		mnt.where = boot.InitramfsUbuntuSeedDir
   285  		// don't fsck in run mode
   286  		if mode == "run" {
   287  			mnt.opts = nil
   288  		}
   289  	case strings.Contains(partuuid, "ubuntu-data"):
   290  		mnt.where = boot.InitramfsDataDir
   291  	}
   292  
   293  	return mnt
   294  }
   295  
   296  func (s *initramfsMountsSuite) makeSeedSnapSystemdMount(typ snap.Type) systemdMount {
   297  	mnt := systemdMount{}
   298  	var name, dir string
   299  	switch typ {
   300  	case snap.TypeSnapd:
   301  		name = "snapd"
   302  		dir = "snapd"
   303  	case snap.TypeBase:
   304  		name = "core20"
   305  		dir = "base"
   306  	case snap.TypeKernel:
   307  		name = "pc-kernel"
   308  		dir = "kernel"
   309  	}
   310  	mnt.what = filepath.Join(s.seedDir, "snaps", name+"_1.snap")
   311  	mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir)
   312  
   313  	return mnt
   314  }
   315  
   316  func (s *initramfsMountsSuite) makeRunSnapSystemdMount(typ snap.Type, sn snap.PlaceInfo) systemdMount {
   317  	mnt := systemdMount{}
   318  	var dir string
   319  	switch typ {
   320  	case snap.TypeSnapd:
   321  		dir = "snapd"
   322  	case snap.TypeBase:
   323  		dir = "base"
   324  	case snap.TypeKernel:
   325  		dir = "kernel"
   326  	}
   327  
   328  	mnt.what = filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename())
   329  	mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir)
   330  
   331  	return mnt
   332  }
   333  
   334  func (s *initramfsMountsSuite) mockSystemdMountSequence(c *C, mounts []systemdMount, comment CommentInterface) (restore func()) {
   335  	n := 0
   336  	if comment == nil {
   337  		comment = Commentf("")
   338  	}
   339  	s.AddCleanup(func() {
   340  		// make sure that after the test is done, we had as many mount calls as
   341  		// mocked mounts
   342  		c.Assert(n, Equals, len(mounts), comment)
   343  	})
   344  	return main.MockSystemdMount(func(what, where string, opts *main.SystemdMountOptions) error {
   345  		n++
   346  		c.Assert(n <= len(mounts), Equals, true)
   347  		if n > len(mounts) {
   348  			return fmt.Errorf("unexpected systemd-mount call: %s, %s, %+v", what, where, opts)
   349  		}
   350  		mnt := mounts[n-1]
   351  		c.Assert(what, Equals, mnt.what, comment)
   352  		c.Assert(where, Equals, mnt.where, comment)
   353  		c.Assert(opts, DeepEquals, mnt.opts, comment)
   354  		return nil
   355  	})
   356  }
   357  
   358  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeHappy(c *C) {
   359  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
   360  
   361  	restore := s.mockSystemdMountSequence(c, []systemdMount{
   362  		ubuntuLabelMount("ubuntu-seed", "install"),
   363  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
   364  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
   365  		s.makeSeedSnapSystemdMount(snap.TypeBase),
   366  		{
   367  			"tmpfs",
   368  			boot.InitramfsDataDir,
   369  			tmpfsMountOpts,
   370  		},
   371  	}, nil)
   372  	defer restore()
   373  
   374  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
   375  	c.Assert(err, IsNil)
   376  
   377  	modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir)
   378  	c.Check(modeEnv, testutil.FileEquals, `mode=install
   379  recovery_system=20191118
   380  `)
   381  	cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
   382  	c.Check(cloudInitDisable, testutil.FilePresent)
   383  }
   384  
   385  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeGadgetDefaultsHappy(c *C) {
   386  	// setup a seed with default gadget yaml
   387  	const gadgetYamlDefaults = `
   388  defaults:
   389    system:
   390      service:
   391        rsyslog.disable: true
   392        ssh.disable: true
   393        console-conf.disable: true
   394      journal.persistent: true
   395  `
   396  	c.Assert(os.RemoveAll(s.seedDir), IsNil)
   397  
   398  	s.setupSeed(c, [][]string{
   399  		{"meta/gadget.yaml", gadgetYamlDefaults},
   400  	})
   401  
   402  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
   403  
   404  	restore := s.mockSystemdMountSequence(c, []systemdMount{
   405  		ubuntuLabelMount("ubuntu-seed", "install"),
   406  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
   407  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
   408  		s.makeSeedSnapSystemdMount(snap.TypeBase),
   409  		{
   410  			"tmpfs",
   411  			boot.InitramfsDataDir,
   412  			tmpfsMountOpts,
   413  		},
   414  	}, nil)
   415  	defer restore()
   416  
   417  	// we will call out to systemctl in the initramfs, but only using --root
   418  	// which doesn't talk to systemd, just manipulates files around
   419  	var sysctlArgs [][]string
   420  	systemctlRestorer := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) {
   421  		sysctlArgs = append(sysctlArgs, args)
   422  		return nil, nil
   423  	})
   424  	defer systemctlRestorer()
   425  
   426  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
   427  	c.Assert(err, IsNil)
   428  
   429  	modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir)
   430  	c.Check(modeEnv, testutil.FileEquals, `mode=install
   431  recovery_system=20191118
   432  `)
   433  
   434  	cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
   435  	c.Check(cloudInitDisable, testutil.FilePresent)
   436  
   437  	// check that everything from the gadget defaults was setup
   438  	c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")), Equals, true)
   439  	c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/lib/console-conf/complete")), Equals, true)
   440  	exists, _, _ := osutil.DirExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/log/journal"))
   441  	c.Assert(exists, Equals, true)
   442  
   443  	// systemctl was called the way we expect
   444  	c.Assert(sysctlArgs, DeepEquals, [][]string{{"--root", filepath.Join(boot.InitramfsWritableDir, "_writable_defaults"), "mask", "rsyslog.service"}})
   445  }
   446  
   447  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeBootedKernelPartitionUUIDHappy(c *C) {
   448  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
   449  
   450  	restore := main.MockPartitionUUIDForBootedKernelDisk("specific-ubuntu-seed-partuuid")
   451  	defer restore()
   452  
   453  	restore = s.mockSystemdMountSequence(c, []systemdMount{
   454  		{
   455  			"/dev/disk/by-partuuid/specific-ubuntu-seed-partuuid",
   456  			boot.InitramfsUbuntuSeedDir,
   457  			needsFsckDiskMountOpts,
   458  		},
   459  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
   460  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
   461  		s.makeSeedSnapSystemdMount(snap.TypeBase),
   462  		{
   463  			"tmpfs",
   464  			boot.InitramfsDataDir,
   465  			tmpfsMountOpts,
   466  		},
   467  	}, nil)
   468  	defer restore()
   469  
   470  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
   471  	c.Assert(err, IsNil)
   472  
   473  	modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir)
   474  	c.Check(modeEnv, testutil.FileEquals, `mode=install
   475  recovery_system=20191118
   476  `)
   477  	cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
   478  	c.Check(cloudInitDisable, testutil.FilePresent)
   479  }
   480  
   481  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeHappy(c *C) {
   482  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
   483  
   484  	restore := disks.MockMountPointDisksToPartitionMapping(
   485  		map[disks.Mountpoint]*disks.MockDiskMapping{
   486  			{Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk,
   487  			{Mountpoint: boot.InitramfsDataDir}:       defaultBootDisk,
   488  		},
   489  	)
   490  	defer restore()
   491  
   492  	restore = s.mockSystemdMountSequence(c, []systemdMount{
   493  		ubuntuLabelMount("ubuntu-boot", "run"),
   494  		ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
   495  		ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"),
   496  		s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
   497  		s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
   498  	}, nil)
   499  	defer restore()
   500  
   501  	// mock a bootloader
   502  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
   503  	bootloader.Force(bloader)
   504  	defer bootloader.Force(nil)
   505  
   506  	// set the current kernel
   507  	restore = bloader.SetEnabledKernel(s.kernel)
   508  	defer restore()
   509  
   510  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
   511  
   512  	// write modeenv
   513  	modeEnv := boot.Modeenv{
   514  		Mode:           "run",
   515  		Base:           s.core20.Filename(),
   516  		CurrentKernels: []string{s.kernel.Filename()},
   517  	}
   518  	err := modeEnv.WriteTo(boot.InitramfsWritableDir)
   519  	c.Assert(err, IsNil)
   520  
   521  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
   522  	c.Assert(err, IsNil)
   523  }
   524  
   525  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeRealSystemdMountTimesOutNoMount(c *C) {
   526  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
   527  
   528  	testStart := time.Now()
   529  	timeCalls := 0
   530  	restore := main.MockTimeNow(func() time.Time {
   531  		timeCalls++
   532  		switch timeCalls {
   533  		case 1, 2:
   534  			return testStart
   535  		case 3:
   536  			// 1:31 later, we should time out
   537  			return testStart.Add(1*time.Minute + 31*time.Second)
   538  		default:
   539  			c.Errorf("unexpected time.Now() call (%d)", timeCalls)
   540  			// we want the test to fail at some point and not run forever, so
   541  			// move time way forward to make it for sure time out
   542  			return testStart.Add(10000 * time.Hour)
   543  		}
   544  	})
   545  	defer restore()
   546  
   547  	cmd := testutil.MockCommand(c, "systemd-mount", ``)
   548  	defer cmd.Restore()
   549  
   550  	isMountedCalls := 0
   551  	restore = main.MockOsutilIsMounted(func(where string) (bool, error) {
   552  		isMountedCalls++
   553  		switch isMountedCalls {
   554  		// always return false for the mount
   555  		case 1, 2:
   556  			c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir)
   557  			return false, nil
   558  		default:
   559  			// shouldn't be called more than twice due to the time.Now() mocking
   560  			c.Errorf("test broken, IsMounted called too many (%d) times", isMountedCalls)
   561  			return false, fmt.Errorf("test broken, IsMounted called too many (%d) times", isMountedCalls)
   562  		}
   563  	})
   564  	defer restore()
   565  
   566  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
   567  	c.Assert(err, ErrorMatches, fmt.Sprintf("timed out after 1m30s waiting for mount %s on %s", "/dev/disk/by-label/ubuntu-seed", boot.InitramfsUbuntuSeedDir))
   568  	c.Check(s.Stdout.String(), Equals, "")
   569  
   570  }
   571  
   572  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeHappyRealSystemdMount(c *C) {
   573  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
   574  
   575  	baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base")
   576  	kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel")
   577  	snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd")
   578  
   579  	// don't do anything from systemd-mount, we verify the arguments passed at
   580  	// the end with cmd.Calls
   581  	cmd := testutil.MockCommand(c, "systemd-mount", ``)
   582  	defer cmd.Restore()
   583  
   584  	// mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are
   585  	// mounted
   586  	n := 0
   587  	restore := main.MockOsutilIsMounted(func(where string) (bool, error) {
   588  		n++
   589  		switch n {
   590  		// first call for each mount returns false, then returns true, this
   591  		// tests in the case where systemd is racy / inconsistent and things
   592  		// aren't mounted by the time systemd-mount returns
   593  		case 1, 2:
   594  			c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir)
   595  			return n%2 == 0, nil
   596  		case 3, 4:
   597  			c.Assert(where, Equals, snapdMnt)
   598  			return n%2 == 0, nil
   599  		case 5, 6:
   600  			c.Assert(where, Equals, kernelMnt)
   601  			return n%2 == 0, nil
   602  		case 7, 8:
   603  			c.Assert(where, Equals, baseMnt)
   604  			return n%2 == 0, nil
   605  		case 9, 10:
   606  			c.Assert(where, Equals, boot.InitramfsDataDir)
   607  			return n%2 == 0, nil
   608  		default:
   609  			c.Errorf("unexpected IsMounted check on %s", where)
   610  			return false, fmt.Errorf("unexpected IsMounted check on %s", where)
   611  		}
   612  	})
   613  	defer restore()
   614  
   615  	// mock a bootloader
   616  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
   617  	bootloader.Force(bloader)
   618  	defer bootloader.Force(nil)
   619  
   620  	// set the current kernel
   621  	restore = bloader.SetEnabledKernel(s.kernel)
   622  	defer restore()
   623  
   624  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
   625  
   626  	// write modeenv
   627  	modeEnv := boot.Modeenv{
   628  		Mode:           "run",
   629  		Base:           s.core20.Filename(),
   630  		CurrentKernels: []string{s.kernel.Filename()},
   631  	}
   632  	err := modeEnv.WriteTo(boot.InitramfsWritableDir)
   633  	c.Assert(err, IsNil)
   634  
   635  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
   636  	c.Assert(err, IsNil)
   637  	c.Check(s.Stdout.String(), Equals, "")
   638  
   639  	// check that all of the override files are present
   640  	for _, initrdUnit := range []string{
   641  		"initrd.target",
   642  		"initrd-fs.target",
   643  		"initrd-switch-root.target",
   644  		"local-fs.target",
   645  	} {
   646  		for _, mountUnit := range []string{
   647  			systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir),
   648  			systemd.EscapeUnitNamePath(snapdMnt),
   649  			systemd.EscapeUnitNamePath(kernelMnt),
   650  			systemd.EscapeUnitNamePath(baseMnt),
   651  			systemd.EscapeUnitNamePath(boot.InitramfsDataDir),
   652  		} {
   653  			fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit)
   654  			unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname)
   655  			c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   656  Requires=%[1]s
   657  After=%[1]s
   658  `, mountUnit+".mount"))
   659  		}
   660  	}
   661  
   662  	// 2 IsMounted calls per mount point, so 10 total IsMounted calls
   663  	c.Assert(n, Equals, 10)
   664  
   665  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   666  		{
   667  			"systemd-mount",
   668  			"/dev/disk/by-label/ubuntu-seed",
   669  			boot.InitramfsUbuntuSeedDir,
   670  			"--no-pager",
   671  			"--no-ask-password",
   672  			"--fsck=yes",
   673  		},
   674  		{
   675  			"systemd-mount",
   676  			filepath.Join(s.seedDir, "snaps", s.snapd.Filename()),
   677  			snapdMnt,
   678  			"--no-pager",
   679  			"--no-ask-password",
   680  			"--fsck=no",
   681  		},
   682  		{
   683  			"systemd-mount",
   684  			filepath.Join(s.seedDir, "snaps", s.kernel.Filename()),
   685  			kernelMnt,
   686  			"--no-pager",
   687  			"--no-ask-password",
   688  			"--fsck=no",
   689  		},
   690  		{
   691  			"systemd-mount",
   692  			filepath.Join(s.seedDir, "snaps", s.core20.Filename()),
   693  			baseMnt,
   694  			"--no-pager",
   695  			"--no-ask-password",
   696  			"--fsck=no",
   697  		},
   698  		{
   699  			"systemd-mount",
   700  			"tmpfs",
   701  			boot.InitramfsDataDir,
   702  			"--no-pager",
   703  			"--no-ask-password",
   704  			"--type=tmpfs",
   705  			"--fsck=no",
   706  		},
   707  	})
   708  }
   709  
   710  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyRealSystemdMount(c *C) {
   711  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
   712  
   713  	baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base")
   714  	kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel")
   715  	snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd")
   716  
   717  	restore := disks.MockMountPointDisksToPartitionMapping(
   718  		map[disks.Mountpoint]*disks.MockDiskMapping{
   719  			{Mountpoint: boot.InitramfsUbuntuSeedDir}:     defaultBootDisk,
   720  			{Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk,
   721  		},
   722  	)
   723  	defer restore()
   724  
   725  	// don't do anything from systemd-mount, we verify the arguments passed at
   726  	// the end with cmd.Calls
   727  	cmd := testutil.MockCommand(c, "systemd-mount", ``)
   728  	defer cmd.Restore()
   729  
   730  	// mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are
   731  	// mounted
   732  	n := 0
   733  	restore = main.MockOsutilIsMounted(func(where string) (bool, error) {
   734  		n++
   735  		switch n {
   736  		// first call for each mount returns false, then returns true, this
   737  		// tests in the case where systemd is racy / inconsistent and things
   738  		// aren't mounted by the time systemd-mount returns
   739  		case 1, 2:
   740  			c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir)
   741  			return n%2 == 0, nil
   742  		case 3, 4:
   743  			c.Assert(where, Equals, snapdMnt)
   744  			return n%2 == 0, nil
   745  		case 5, 6:
   746  			c.Assert(where, Equals, kernelMnt)
   747  			return n%2 == 0, nil
   748  		case 7, 8:
   749  			c.Assert(where, Equals, baseMnt)
   750  			return n%2 == 0, nil
   751  		case 9, 10:
   752  			c.Assert(where, Equals, boot.InitramfsDataDir)
   753  			return n%2 == 0, nil
   754  		case 11, 12:
   755  			c.Assert(where, Equals, boot.InitramfsHostUbuntuDataDir)
   756  			return n%2 == 0, nil
   757  		default:
   758  			c.Errorf("unexpected IsMounted check on %s", where)
   759  			return false, fmt.Errorf("unexpected IsMounted check on %s", where)
   760  		}
   761  	})
   762  	defer restore()
   763  
   764  	// mock a bootloader
   765  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
   766  	bootloader.Force(bloader)
   767  	defer bootloader.Force(nil)
   768  
   769  	// set the current kernel
   770  	restore = bloader.SetEnabledKernel(s.kernel)
   771  	defer restore()
   772  
   773  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
   774  
   775  	// write modeenv
   776  	modeEnv := boot.Modeenv{
   777  		Mode:           "run",
   778  		Base:           s.core20.Filename(),
   779  		CurrentKernels: []string{s.kernel.Filename()},
   780  	}
   781  	err := modeEnv.WriteTo(boot.InitramfsWritableDir)
   782  	c.Assert(err, IsNil)
   783  
   784  	s.testRecoverModeHappy(c)
   785  
   786  	c.Check(s.Stdout.String(), Equals, "")
   787  
   788  	// check that all of the override files are present
   789  	for _, initrdUnit := range []string{
   790  		"initrd.target",
   791  		"initrd-fs.target",
   792  		"initrd-switch-root.target",
   793  		"local-fs.target",
   794  	} {
   795  		for _, mountUnit := range []string{
   796  			systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir),
   797  			systemd.EscapeUnitNamePath(snapdMnt),
   798  			systemd.EscapeUnitNamePath(kernelMnt),
   799  			systemd.EscapeUnitNamePath(baseMnt),
   800  			systemd.EscapeUnitNamePath(boot.InitramfsDataDir),
   801  			systemd.EscapeUnitNamePath(boot.InitramfsHostUbuntuDataDir),
   802  		} {
   803  			fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit)
   804  			unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname)
   805  			c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   806  Requires=%[1]s
   807  After=%[1]s
   808  `, mountUnit+".mount"))
   809  		}
   810  	}
   811  
   812  	// 2 IsMounted calls per mount point, so 12 total IsMounted calls
   813  	c.Assert(n, Equals, 12)
   814  
   815  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   816  		{
   817  			"systemd-mount",
   818  			"/dev/disk/by-label/ubuntu-seed",
   819  			boot.InitramfsUbuntuSeedDir,
   820  			"--no-pager",
   821  			"--no-ask-password",
   822  			"--fsck=yes",
   823  		},
   824  		{
   825  			"systemd-mount",
   826  			filepath.Join(s.seedDir, "snaps", s.snapd.Filename()),
   827  			snapdMnt,
   828  			"--no-pager",
   829  			"--no-ask-password",
   830  			"--fsck=no",
   831  		},
   832  		{
   833  			"systemd-mount",
   834  			filepath.Join(s.seedDir, "snaps", s.kernel.Filename()),
   835  			kernelMnt,
   836  			"--no-pager",
   837  			"--no-ask-password",
   838  			"--fsck=no",
   839  		},
   840  		{
   841  			"systemd-mount",
   842  			filepath.Join(s.seedDir, "snaps", s.core20.Filename()),
   843  			baseMnt,
   844  			"--no-pager",
   845  			"--no-ask-password",
   846  			"--fsck=no",
   847  		},
   848  		{
   849  			"systemd-mount",
   850  			"tmpfs",
   851  			boot.InitramfsDataDir,
   852  			"--no-pager",
   853  			"--no-ask-password",
   854  			"--type=tmpfs",
   855  			"--fsck=no",
   856  		},
   857  		{
   858  			"systemd-mount",
   859  			"/dev/disk/by-partuuid/ubuntu-data-partuuid",
   860  			boot.InitramfsHostUbuntuDataDir,
   861  			"--no-pager",
   862  			"--no-ask-password",
   863  			"--fsck=no",
   864  		},
   865  	})
   866  }
   867  
   868  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeHappyRealSystemdMount(c *C) {
   869  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
   870  
   871  	restore := disks.MockMountPointDisksToPartitionMapping(
   872  		map[disks.Mountpoint]*disks.MockDiskMapping{
   873  			{Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk,
   874  			{Mountpoint: boot.InitramfsDataDir}:       defaultBootDisk,
   875  		},
   876  	)
   877  	defer restore()
   878  
   879  	baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base")
   880  	kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel")
   881  
   882  	// don't do anything from systemd-mount, we verify the arguments passed at
   883  	// the end with cmd.Calls
   884  	cmd := testutil.MockCommand(c, "systemd-mount", ``)
   885  	defer cmd.Restore()
   886  
   887  	// mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are
   888  	// mounted
   889  	n := 0
   890  	restore = main.MockOsutilIsMounted(func(where string) (bool, error) {
   891  		n++
   892  		switch n {
   893  		// first call for each mount returns false, then returns true, this
   894  		// tests in the case where systemd is racy / inconsistent and things
   895  		// aren't mounted by the time systemd-mount returns
   896  		case 1, 2:
   897  			c.Assert(where, Equals, boot.InitramfsUbuntuBootDir)
   898  			return n%2 == 0, nil
   899  		case 3, 4:
   900  			c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir)
   901  			return n%2 == 0, nil
   902  		case 5, 6:
   903  			c.Assert(where, Equals, boot.InitramfsDataDir)
   904  			return n%2 == 0, nil
   905  		case 7, 8:
   906  			c.Assert(where, Equals, baseMnt)
   907  			return n%2 == 0, nil
   908  		case 9, 10:
   909  			c.Assert(where, Equals, kernelMnt)
   910  			return n%2 == 0, nil
   911  		default:
   912  			c.Errorf("unexpected IsMounted check on %s", where)
   913  			return false, fmt.Errorf("unexpected IsMounted check on %s", where)
   914  		}
   915  	})
   916  	defer restore()
   917  
   918  	// mock a bootloader
   919  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
   920  	bootloader.Force(bloader)
   921  	defer bootloader.Force(nil)
   922  
   923  	// set the current kernel
   924  	restore = bloader.SetEnabledKernel(s.kernel)
   925  	defer restore()
   926  
   927  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
   928  
   929  	// write modeenv
   930  	modeEnv := boot.Modeenv{
   931  		Mode:           "run",
   932  		Base:           s.core20.Filename(),
   933  		CurrentKernels: []string{s.kernel.Filename()},
   934  	}
   935  	err := modeEnv.WriteTo(boot.InitramfsWritableDir)
   936  	c.Assert(err, IsNil)
   937  
   938  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
   939  	c.Assert(err, IsNil)
   940  	c.Check(s.Stdout.String(), Equals, "")
   941  
   942  	// check that all of the override files are present
   943  	for _, initrdUnit := range []string{
   944  		"initrd.target",
   945  		"initrd-fs.target",
   946  		"initrd-switch-root.target",
   947  		"local-fs.target",
   948  	} {
   949  		for _, mountUnit := range []string{
   950  			systemd.EscapeUnitNamePath(boot.InitramfsUbuntuBootDir),
   951  			systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir),
   952  			systemd.EscapeUnitNamePath(boot.InitramfsDataDir),
   953  			systemd.EscapeUnitNamePath(baseMnt),
   954  			systemd.EscapeUnitNamePath(kernelMnt),
   955  		} {
   956  			fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit)
   957  			unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname)
   958  			c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   959  Requires=%[1]s
   960  After=%[1]s
   961  `, mountUnit+".mount"))
   962  		}
   963  	}
   964  
   965  	// 2 IsMounted calls per mount point, so 10 total IsMounted calls
   966  	c.Assert(n, Equals, 10)
   967  
   968  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   969  		{
   970  			"systemd-mount",
   971  			"/dev/disk/by-label/ubuntu-boot",
   972  			boot.InitramfsUbuntuBootDir,
   973  			"--no-pager",
   974  			"--no-ask-password",
   975  			"--fsck=yes",
   976  		},
   977  		{
   978  			"systemd-mount",
   979  			"/dev/disk/by-partuuid/ubuntu-seed-partuuid",
   980  			boot.InitramfsUbuntuSeedDir,
   981  			"--no-pager",
   982  			"--no-ask-password",
   983  			"--fsck=no",
   984  		},
   985  		{
   986  			"systemd-mount",
   987  			"/dev/disk/by-partuuid/ubuntu-data-partuuid",
   988  			boot.InitramfsDataDir,
   989  			"--no-pager",
   990  			"--no-ask-password",
   991  			"--fsck=yes",
   992  		},
   993  		{
   994  			"systemd-mount",
   995  			filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.core20.Filename()),
   996  			baseMnt,
   997  			"--no-pager",
   998  			"--no-ask-password",
   999  			"--fsck=no",
  1000  		},
  1001  		{
  1002  			"systemd-mount",
  1003  			filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.kernel.Filename()),
  1004  			kernelMnt,
  1005  			"--no-pager",
  1006  			"--no-ask-password",
  1007  			"--fsck=no",
  1008  		},
  1009  	})
  1010  }
  1011  
  1012  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeFirstBootRecoverySystemSetHappy(c *C) {
  1013  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
  1014  
  1015  	restore := disks.MockMountPointDisksToPartitionMapping(
  1016  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1017  			{Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk,
  1018  			{Mountpoint: boot.InitramfsDataDir}:       defaultBootDisk,
  1019  		},
  1020  	)
  1021  	defer restore()
  1022  
  1023  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1024  		ubuntuLabelMount("ubuntu-boot", "run"),
  1025  		ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
  1026  		ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"),
  1027  		s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1028  		s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1029  		// RecoverySystem set makes us mount the snapd snap here
  1030  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  1031  	}, nil)
  1032  	defer restore()
  1033  
  1034  	// mock a bootloader
  1035  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
  1036  	bootloader.Force(bloader)
  1037  	defer bootloader.Force(nil)
  1038  
  1039  	// set the current kernel
  1040  	restore = bloader.SetEnabledKernel(s.kernel)
  1041  	defer restore()
  1042  
  1043  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
  1044  
  1045  	// write modeenv
  1046  	modeEnv := boot.Modeenv{
  1047  		Mode:           "run",
  1048  		RecoverySystem: "20191118",
  1049  		Base:           s.core20.Filename(),
  1050  		CurrentKernels: []string{s.kernel.Filename()},
  1051  	}
  1052  	err := modeEnv.WriteTo(boot.InitramfsWritableDir)
  1053  	c.Assert(err, IsNil)
  1054  
  1055  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
  1056  	c.Assert(err, IsNil)
  1057  }
  1058  
  1059  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithBootedKernelPartUUIDHappy(c *C) {
  1060  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
  1061  
  1062  	restore := main.MockPartitionUUIDForBootedKernelDisk("ubuntu-boot-partuuid")
  1063  	defer restore()
  1064  
  1065  	restore = disks.MockMountPointDisksToPartitionMapping(
  1066  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1067  			{Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk,
  1068  			{Mountpoint: boot.InitramfsDataDir}:       defaultBootDisk,
  1069  		},
  1070  	)
  1071  	defer restore()
  1072  
  1073  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1074  		{
  1075  			"/dev/disk/by-partuuid/ubuntu-boot-partuuid",
  1076  			boot.InitramfsUbuntuBootDir,
  1077  			needsFsckDiskMountOpts,
  1078  		},
  1079  		ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
  1080  		ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"),
  1081  		s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1082  		s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1083  	}, nil)
  1084  	defer restore()
  1085  
  1086  	// mock a bootloader
  1087  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
  1088  	bootloader.Force(bloader)
  1089  	defer bootloader.Force(nil)
  1090  
  1091  	// set the current kernel
  1092  	restore = bloader.SetEnabledKernel(s.kernel)
  1093  	defer restore()
  1094  
  1095  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
  1096  
  1097  	// write modeenv
  1098  	modeEnv := boot.Modeenv{
  1099  		Mode:           "run",
  1100  		Base:           s.core20.Filename(),
  1101  		CurrentKernels: []string{s.kernel.Filename()},
  1102  	}
  1103  	err := modeEnv.WriteTo(boot.InitramfsWritableDir)
  1104  	c.Assert(err, IsNil)
  1105  
  1106  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
  1107  	c.Assert(err, IsNil)
  1108  }
  1109  
  1110  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataHappy(c *C) {
  1111  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
  1112  
  1113  	restore := disks.MockMountPointDisksToPartitionMapping(
  1114  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1115  			{Mountpoint: boot.InitramfsUbuntuBootDir}:                    defaultEncBootDisk,
  1116  			{Mountpoint: boot.InitramfsDataDir, IsDecryptedDevice: true}: defaultEncBootDisk,
  1117  		},
  1118  	)
  1119  	defer restore()
  1120  
  1121  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1122  		ubuntuLabelMount("ubuntu-boot", "run"),
  1123  		ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
  1124  		{
  1125  			"path-to-device",
  1126  			boot.InitramfsDataDir,
  1127  			needsFsckDiskMountOpts,
  1128  		},
  1129  		s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1130  		s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1131  	}, nil)
  1132  	defer restore()
  1133  
  1134  	// write the installed model like makebootable does it
  1135  	err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755)
  1136  	c.Assert(err, IsNil)
  1137  	mf, err := os.Create(filepath.Join(boot.InitramfsUbuntuBootDir, "model"))
  1138  	c.Assert(err, IsNil)
  1139  	defer mf.Close()
  1140  	err = asserts.NewEncoder(mf).Encode(s.model)
  1141  	c.Assert(err, IsNil)
  1142  
  1143  	activated := false
  1144  	restore = main.MockSecbootUnlockVolumeIfEncrypted(func(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) {
  1145  		c.Assert(name, Equals, "ubuntu-data")
  1146  		c.Assert(encryptionKeyDir, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde"))
  1147  		c.Assert(lockKeysOnFinish, Equals, true)
  1148  		activated = true
  1149  		// return true because we are using an encrypted device
  1150  		return "path-to-device", true, nil
  1151  	})
  1152  	defer restore()
  1153  
  1154  	measureEpochCalls := 0
  1155  	measureModelCalls := 0
  1156  	restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error {
  1157  		measureEpochCalls++
  1158  		return nil
  1159  	})
  1160  	defer restore()
  1161  
  1162  	var measuredModel *asserts.Model
  1163  	restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error {
  1164  		measureModelCalls++
  1165  		var err error
  1166  		measuredModel, err = findModel()
  1167  		if err != nil {
  1168  			return err
  1169  		}
  1170  		return nil
  1171  	})
  1172  	defer restore()
  1173  
  1174  	// mock a bootloader
  1175  	bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
  1176  	bootloader.Force(bloader)
  1177  	defer bootloader.Force(nil)
  1178  
  1179  	// set the current kernel
  1180  	restore = bloader.SetEnabledKernel(s.kernel)
  1181  	defer restore()
  1182  
  1183  	makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20)
  1184  
  1185  	// write modeenv
  1186  	modeEnv := boot.Modeenv{
  1187  		Mode:           "run",
  1188  		Base:           s.core20.Filename(),
  1189  		CurrentKernels: []string{s.kernel.Filename()},
  1190  	}
  1191  	err = modeEnv.WriteTo(boot.InitramfsWritableDir)
  1192  	c.Assert(err, IsNil)
  1193  
  1194  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
  1195  	c.Assert(err, IsNil)
  1196  	c.Check(activated, Equals, true)
  1197  	c.Check(measureEpochCalls, Equals, 1)
  1198  	c.Check(measureModelCalls, Equals, 1)
  1199  	c.Check(measuredModel, DeepEquals, s.model)
  1200  
  1201  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
  1202  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "run-model-measured"), testutil.FilePresent)
  1203  }
  1204  
  1205  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedNoModel(c *C) {
  1206  	s.testInitramfsMountsEncryptedNoModel(c, "run", "", 1)
  1207  }
  1208  
  1209  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeEncryptedNoModel(c *C) {
  1210  	s.testInitramfsMountsEncryptedNoModel(c, "install", s.sysLabel, 0)
  1211  }
  1212  
  1213  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedNoModel(c *C) {
  1214  	s.testInitramfsMountsEncryptedNoModel(c, "recover", s.sysLabel, 0)
  1215  }
  1216  
  1217  func (s *initramfsMountsSuite) testInitramfsMountsEncryptedNoModel(c *C, mode, label string, expectedMeasureModelCalls int) {
  1218  	s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s", mode))
  1219  
  1220  	// install and recover mounts are just ubuntu-seed before we fail
  1221  	var restore func()
  1222  	if mode == "run" {
  1223  		// run mode will mount ubuntu-boot and ubuntu-seed
  1224  		restore = s.mockSystemdMountSequence(c, []systemdMount{
  1225  			ubuntuLabelMount("ubuntu-boot", mode),
  1226  			ubuntuPartUUIDMount("ubuntu-seed-partuuid", mode),
  1227  		}, nil)
  1228  		restore2 := disks.MockMountPointDisksToPartitionMapping(
  1229  			map[disks.Mountpoint]*disks.MockDiskMapping{
  1230  				{Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk,
  1231  			},
  1232  		)
  1233  		defer restore2()
  1234  	} else {
  1235  		restore = s.mockSystemdMountSequence(c, []systemdMount{
  1236  			ubuntuLabelMount("ubuntu-seed", mode),
  1237  		}, nil)
  1238  
  1239  		// in install / recover mode the code doesn't make it far enough to do
  1240  		// any disk cross checking
  1241  	}
  1242  	defer restore()
  1243  
  1244  	if label != "" {
  1245  		s.mockProcCmdlineContent(c,
  1246  			fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, label))
  1247  		// break the seed
  1248  		err := os.Remove(filepath.Join(s.seedDir, "systems", label, "model"))
  1249  		c.Assert(err, IsNil)
  1250  	}
  1251  
  1252  	measureEpochCalls := 0
  1253  	restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error {
  1254  		measureEpochCalls++
  1255  		return nil
  1256  	})
  1257  	defer restore()
  1258  
  1259  	measureModelCalls := 0
  1260  	restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error {
  1261  		measureModelCalls++
  1262  		_, err := findModel()
  1263  		if err != nil {
  1264  			return err
  1265  		}
  1266  		return fmt.Errorf("unexpected call")
  1267  	})
  1268  	defer restore()
  1269  
  1270  	_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
  1271  	where := "/run/mnt/ubuntu-boot/model"
  1272  	if mode != "run" {
  1273  		where = fmt.Sprintf("/run/mnt/ubuntu-seed/systems/%s/model", label)
  1274  	}
  1275  	c.Assert(err, ErrorMatches, fmt.Sprintf(".*cannot read model assertion: open .*%s: no such file or directory", where))
  1276  	c.Assert(measureEpochCalls, Equals, 1)
  1277  	c.Assert(measureModelCalls, Equals, expectedMeasureModelCalls)
  1278  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
  1279  	gl, err := filepath.Glob(filepath.Join(dirs.SnapBootstrapRunDir, "*-model-measured"))
  1280  	c.Assert(err, IsNil)
  1281  	c.Assert(gl, HasLen, 0)
  1282  }
  1283  
  1284  func (s *initramfsMountsSuite) TestInitramfsMountsRunModeUpgradeScenarios(c *C) {
  1285  	tt := []struct {
  1286  		modeenv *boot.Modeenv
  1287  		// this is a function so we can have delayed execution, typical values
  1288  		// depend on the root dir which changes for each test case
  1289  		additionalMountsFunc func() []systemdMount
  1290  		enableKernel         snap.PlaceInfo
  1291  		enableTryKernel      snap.PlaceInfo
  1292  		snapFiles            []snap.PlaceInfo
  1293  		kernelStatus         string
  1294  
  1295  		expRebootPanic string
  1296  		expLog         string
  1297  		expError       string
  1298  		expModeenv     *boot.Modeenv
  1299  		comment        string
  1300  	}{
  1301  		// default case no upgrades
  1302  		{
  1303  			modeenv: &boot.Modeenv{
  1304  				Mode:           "run",
  1305  				Base:           s.core20.Filename(),
  1306  				CurrentKernels: []string{s.kernel.Filename()},
  1307  			},
  1308  			additionalMountsFunc: func() []systemdMount {
  1309  				return []systemdMount{
  1310  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1311  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1312  				}
  1313  			},
  1314  			enableKernel: s.kernel,
  1315  			snapFiles:    []snap.PlaceInfo{s.core20, s.kernel},
  1316  			comment:      "happy default no upgrades",
  1317  		},
  1318  
  1319  		// happy upgrade cases
  1320  		{
  1321  			modeenv: &boot.Modeenv{
  1322  				Mode:           "run",
  1323  				Base:           s.core20.Filename(),
  1324  				CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()},
  1325  			},
  1326  			additionalMountsFunc: func() []systemdMount {
  1327  				return []systemdMount{
  1328  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1329  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernelr2),
  1330  				}
  1331  			},
  1332  			kernelStatus:    boot.TryingStatus,
  1333  			enableKernel:    s.kernel,
  1334  			enableTryKernel: s.kernelr2,
  1335  			snapFiles:       []snap.PlaceInfo{s.core20, s.kernel, s.kernelr2},
  1336  			comment:         "happy kernel snap upgrade",
  1337  		},
  1338  		{
  1339  			modeenv: &boot.Modeenv{
  1340  				Mode:           "run",
  1341  				Base:           s.core20.Filename(),
  1342  				TryBase:        s.core20r2.Filename(),
  1343  				BaseStatus:     boot.TryStatus,
  1344  				CurrentKernels: []string{s.kernel.Filename()},
  1345  			},
  1346  			additionalMountsFunc: func() []systemdMount {
  1347  				return []systemdMount{
  1348  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20r2),
  1349  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1350  				}
  1351  			},
  1352  			enableKernel: s.kernel,
  1353  			snapFiles:    []snap.PlaceInfo{s.kernel, s.core20, s.core20r2},
  1354  			expModeenv: &boot.Modeenv{
  1355  				Mode:           "run",
  1356  				Base:           s.core20.Filename(),
  1357  				TryBase:        s.core20r2.Filename(),
  1358  				BaseStatus:     boot.TryingStatus,
  1359  				CurrentKernels: []string{s.kernel.Filename()},
  1360  			},
  1361  			comment: "happy base snap upgrade",
  1362  		},
  1363  		{
  1364  			modeenv: &boot.Modeenv{
  1365  				Mode:           "run",
  1366  				Base:           s.core20.Filename(),
  1367  				TryBase:        s.core20r2.Filename(),
  1368  				BaseStatus:     boot.TryStatus,
  1369  				CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()},
  1370  			},
  1371  			additionalMountsFunc: func() []systemdMount {
  1372  				return []systemdMount{
  1373  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20r2),
  1374  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernelr2),
  1375  				}
  1376  			},
  1377  			enableKernel:    s.kernel,
  1378  			enableTryKernel: s.kernelr2,
  1379  			snapFiles:       []snap.PlaceInfo{s.kernel, s.kernelr2, s.core20, s.core20r2},
  1380  			kernelStatus:    boot.TryingStatus,
  1381  			expModeenv: &boot.Modeenv{
  1382  				Mode:           "run",
  1383  				Base:           s.core20.Filename(),
  1384  				TryBase:        s.core20r2.Filename(),
  1385  				BaseStatus:     boot.TryingStatus,
  1386  				CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()},
  1387  			},
  1388  			comment: "happy simultaneous base snap and kernel snap upgrade",
  1389  		},
  1390  
  1391  		// fallback cases
  1392  		{
  1393  			modeenv: &boot.Modeenv{
  1394  				Mode:           "run",
  1395  				Base:           s.core20.Filename(),
  1396  				TryBase:        s.core20r2.Filename(),
  1397  				BaseStatus:     boot.TryStatus,
  1398  				CurrentKernels: []string{s.kernel.Filename()},
  1399  			},
  1400  			additionalMountsFunc: func() []systemdMount {
  1401  				return []systemdMount{
  1402  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1403  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1404  				}
  1405  			},
  1406  			enableKernel: s.kernel,
  1407  			snapFiles:    []snap.PlaceInfo{s.kernel, s.core20},
  1408  			comment:      "happy fallback try base not existing",
  1409  		},
  1410  		{
  1411  			modeenv: &boot.Modeenv{
  1412  				Mode:           "run",
  1413  				Base:           s.core20.Filename(),
  1414  				BaseStatus:     boot.TryStatus,
  1415  				TryBase:        "",
  1416  				CurrentKernels: []string{s.kernel.Filename()},
  1417  			},
  1418  			additionalMountsFunc: func() []systemdMount {
  1419  				return []systemdMount{
  1420  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1421  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1422  				}
  1423  			},
  1424  			enableKernel: s.kernel,
  1425  			snapFiles:    []snap.PlaceInfo{s.kernel, s.core20},
  1426  			comment:      "happy fallback base_status try, empty try_base",
  1427  		},
  1428  		{
  1429  			modeenv: &boot.Modeenv{
  1430  				Mode:           "run",
  1431  				Base:           s.core20.Filename(),
  1432  				TryBase:        s.core20r2.Filename(),
  1433  				BaseStatus:     boot.TryingStatus,
  1434  				CurrentKernels: []string{s.kernel.Filename()},
  1435  			},
  1436  			additionalMountsFunc: func() []systemdMount {
  1437  				return []systemdMount{
  1438  					s.makeRunSnapSystemdMount(snap.TypeBase, s.core20),
  1439  					s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel),
  1440  				}
  1441  			},
  1442  			enableKernel: s.kernel,
  1443  			snapFiles:    []snap.PlaceInfo{s.kernel, s.core20, s.core20r2},
  1444  			expModeenv: &boot.Modeenv{
  1445  				Mode:           "run",
  1446  				Base:           s.core20.Filename(),
  1447  				TryBase:        s.core20r2.Filename(),
  1448  				BaseStatus:     boot.DefaultStatus,
  1449  				CurrentKernels: []string{s.kernel.Filename()},
  1450  			},
  1451  			comment: "happy fallback failed boot with try snap",
  1452  		},
  1453  		{
  1454  			modeenv: &boot.Modeenv{
  1455  				Mode:           "run",
  1456  				Base:           s.core20.Filename(),
  1457  				CurrentKernels: []string{s.kernel.Filename()},
  1458  			},
  1459  			enableKernel:    s.kernel,
  1460  			enableTryKernel: s.kernelr2,
  1461  			snapFiles:       []snap.PlaceInfo{s.core20, s.kernel, s.kernelr2},
  1462  			kernelStatus:    boot.TryingStatus,
  1463  			expRebootPanic:  "reboot due to untrusted try kernel snap",
  1464  			comment:         "happy fallback untrusted try kernel snap",
  1465  		},
  1466  		// TODO:UC20: if we ever have a way to compare what kernel was booted,
  1467  		//            and we compute that the booted kernel was the try kernel,
  1468  		//            but the try kernel is not enabled on the bootloader
  1469  		//            (somehow??), then this should become a reboot case rather
  1470  		//            than mount the old kernel snap
  1471  		{
  1472  			modeenv: &boot.Modeenv{
  1473  				Mode:           "run",
  1474  				Base:           s.core20.Filename(),
  1475  				CurrentKernels: []string{s.kernel.Filename()},
  1476  			},
  1477  			kernelStatus:   boot.TryingStatus,
  1478  			enableKernel:   s.kernel,
  1479  			snapFiles:      []snap.PlaceInfo{s.core20, s.kernel},
  1480  			expRebootPanic: "reboot due to no try kernel snap",
  1481  			comment:        "happy fallback kernel_status trying no try kernel",
  1482  		},
  1483  
  1484  		// unhappy cases
  1485  		{
  1486  			modeenv: &boot.Modeenv{
  1487  				Mode: "run",
  1488  			},
  1489  			expError: "fallback base snap unusable: cannot get snap revision: modeenv base boot variable is empty",
  1490  			comment:  "unhappy empty modeenv",
  1491  		},
  1492  		// TODO:UC20: in this case snap-bootstrap should request a reboot, since we
  1493  		//            already booted the try snap, so mounting the fallback kernel will
  1494  		//            not match in some cases
  1495  		{
  1496  			modeenv: &boot.Modeenv{
  1497  				Mode:           "run",
  1498  				Base:           s.core20.Filename(),
  1499  				CurrentKernels: []string{s.kernel.Filename()},
  1500  			},
  1501  			enableKernel: s.kernelr2,
  1502  			snapFiles:    []snap.PlaceInfo{s.core20, s.kernelr2},
  1503  			expError:     fmt.Sprintf("fallback kernel snap %q is not trusted in the modeenv", s.kernelr2.Filename()),
  1504  			comment:      "unhappy untrusted main kernel snap",
  1505  		},
  1506  	}
  1507  
  1508  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
  1509  
  1510  	for _, t := range tt {
  1511  		comment := Commentf(t.comment)
  1512  
  1513  		var cleanups []func()
  1514  
  1515  		if t.expRebootPanic != "" {
  1516  			r := boot.MockInitramfsReboot(func() error {
  1517  				panic(t.expRebootPanic)
  1518  			})
  1519  			cleanups = append(cleanups, r)
  1520  		}
  1521  
  1522  		// setup unique root dir per test
  1523  		rootDir := c.MkDir()
  1524  		cleanups = append(cleanups, func() { dirs.SetRootDir(dirs.GlobalRootDir) })
  1525  		dirs.SetRootDir(rootDir)
  1526  
  1527  		restore := disks.MockMountPointDisksToPartitionMapping(
  1528  			map[disks.Mountpoint]*disks.MockDiskMapping{
  1529  				{Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk,
  1530  				{Mountpoint: boot.InitramfsDataDir}:       defaultBootDisk,
  1531  			},
  1532  		)
  1533  		cleanups = append(cleanups, restore)
  1534  
  1535  		// setup expected systemd-mount calls - every test case has ubuntu-boot,
  1536  		// ubuntu-seed and ubuntu-data mounts because all those mounts happen
  1537  		// before any boot logic
  1538  		mnts := []systemdMount{
  1539  			ubuntuLabelMount("ubuntu-boot", "run"),
  1540  			ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"),
  1541  			ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"),
  1542  		}
  1543  		if t.additionalMountsFunc != nil {
  1544  			mnts = append(mnts, t.additionalMountsFunc()...)
  1545  		}
  1546  		cleanups = append(cleanups, s.mockSystemdMountSequence(c, mnts, comment))
  1547  
  1548  		// mock a bootloader
  1549  		bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
  1550  		bootloader.Force(bloader)
  1551  		cleanups = append(cleanups, func() { bootloader.Force(nil) })
  1552  
  1553  		if t.enableKernel != nil {
  1554  			// don't need to restore since each test case has a unique bloader
  1555  			bloader.SetEnabledKernel(t.enableKernel)
  1556  		}
  1557  
  1558  		if t.enableTryKernel != nil {
  1559  			bloader.SetEnabledTryKernel(t.enableTryKernel)
  1560  		}
  1561  
  1562  		// set the kernel_status boot var
  1563  		err := bloader.SetBootVars(map[string]string{"kernel_status": t.kernelStatus})
  1564  		c.Assert(err, IsNil, comment)
  1565  
  1566  		// write the initial modeenv
  1567  		err = t.modeenv.WriteTo(boot.InitramfsWritableDir)
  1568  		c.Assert(err, IsNil, comment)
  1569  
  1570  		// make the snap files - no restore needed because we use a unique root
  1571  		// dir for each test case
  1572  		makeSnapFilesOnEarlyBootUbuntuData(c, t.snapFiles...)
  1573  
  1574  		if t.expRebootPanic != "" {
  1575  			f := func() { main.Parser().ParseArgs([]string{"initramfs-mounts"}) }
  1576  			c.Assert(f, PanicMatches, t.expRebootPanic, comment)
  1577  		} else {
  1578  			_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
  1579  			if t.expError != "" {
  1580  				c.Assert(err, ErrorMatches, t.expError, comment)
  1581  			} else {
  1582  				c.Assert(err, IsNil, comment)
  1583  
  1584  				// check the resultant modeenv
  1585  				// if the expModeenv is nil, we just compare to the start
  1586  				newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
  1587  				c.Assert(err, IsNil, comment)
  1588  				m := t.modeenv
  1589  				if t.expModeenv != nil {
  1590  					m = t.expModeenv
  1591  				}
  1592  				c.Assert(newModeenv.BaseStatus, DeepEquals, m.BaseStatus, comment)
  1593  				c.Assert(newModeenv.TryBase, DeepEquals, m.TryBase, comment)
  1594  				c.Assert(newModeenv.Base, DeepEquals, m.Base, comment)
  1595  			}
  1596  		}
  1597  
  1598  		for _, r := range cleanups {
  1599  			r()
  1600  		}
  1601  	}
  1602  }
  1603  
  1604  func (s *initramfsMountsSuite) testRecoverModeHappy(c *C) {
  1605  	// mock various files that are copied around during recover mode (and files
  1606  	// that shouldn't be copied around)
  1607  	ephemeralUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "data/")
  1608  	err := os.MkdirAll(ephemeralUbuntuData, 0755)
  1609  	c.Assert(err, IsNil)
  1610  	// mock a auth data in the host's ubuntu-data
  1611  	hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/")
  1612  	err = os.MkdirAll(hostUbuntuData, 0755)
  1613  	c.Assert(err, IsNil)
  1614  	mockCopiedFiles := []string{
  1615  		// extrausers
  1616  		"system-data/var/lib/extrausers/passwd",
  1617  		"system-data/var/lib/extrausers/shadow",
  1618  		"system-data/var/lib/extrausers/group",
  1619  		"system-data/var/lib/extrausers/gshadow",
  1620  		// sshd
  1621  		"system-data/etc/ssh/ssh_host_rsa.key",
  1622  		"system-data/etc/ssh/ssh_host_rsa.key.pub",
  1623  		// user ssh
  1624  		"user-data/user1/.ssh/authorized_keys",
  1625  		"user-data/user2/.ssh/authorized_keys",
  1626  		// user snap authentication
  1627  		"user-data/user1/.snap/auth.json",
  1628  		// sudoers
  1629  		"system-data/etc/sudoers.d/create-user-test",
  1630  		// netplan networking
  1631  		"system-data/etc/netplan/00-snapd-config.yaml", // example console-conf filename
  1632  		"system-data/etc/netplan/50-cloud-init.yaml",   // example cloud-init filename
  1633  		// systemd clock file
  1634  		"system-data/var/lib/systemd/timesync/clock",
  1635  		"system-data/etc/machine-id", // machine-id for systemd-networkd
  1636  	}
  1637  	mockUnrelatedFiles := []string{
  1638  		"system-data/var/lib/foo",
  1639  		"system-data/etc/passwd",
  1640  		"user-data/user1/some-random-data",
  1641  		"user-data/user2/other-random-data",
  1642  		"user-data/user2/.snap/sneaky-not-auth.json",
  1643  		"system-data/etc/not-networking/netplan",
  1644  		"system-data/var/lib/systemd/timesync/clock-not-the-clock",
  1645  		"system-data/etc/machine-id-except-not",
  1646  	}
  1647  	for _, mockFile := range append(mockCopiedFiles, mockUnrelatedFiles...) {
  1648  		p := filepath.Join(hostUbuntuData, mockFile)
  1649  		err = os.MkdirAll(filepath.Dir(p), 0750)
  1650  		c.Assert(err, IsNil)
  1651  		mockContent := fmt.Sprintf("content of %s", filepath.Base(mockFile))
  1652  		err = ioutil.WriteFile(p, []byte(mockContent), 0640)
  1653  		c.Assert(err, IsNil)
  1654  	}
  1655  	// create a mock state
  1656  	mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json")
  1657  	err = os.MkdirAll(filepath.Dir(mockedState), 0750)
  1658  	c.Assert(err, IsNil)
  1659  	err = ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640)
  1660  	c.Assert(err, IsNil)
  1661  
  1662  	_, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
  1663  	c.Assert(err, IsNil)
  1664  
  1665  	modeEnv := filepath.Join(ephemeralUbuntuData, "/system-data/var/lib/snapd/modeenv")
  1666  	c.Check(modeEnv, testutil.FileEquals, `mode=recover
  1667  recovery_system=20191118
  1668  `)
  1669  	for _, p := range mockUnrelatedFiles {
  1670  		c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FileAbsent)
  1671  	}
  1672  	for _, p := range mockCopiedFiles {
  1673  		c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FilePresent)
  1674  		fi, err := os.Stat(filepath.Join(ephemeralUbuntuData, p))
  1675  		// check file mode is set
  1676  		c.Assert(err, IsNil)
  1677  		c.Check(fi.Mode(), Equals, os.FileMode(0640))
  1678  		// check dir mode is set in parent dir
  1679  		fiParent, err := os.Stat(filepath.Dir(filepath.Join(ephemeralUbuntuData, p)))
  1680  		c.Assert(err, IsNil)
  1681  		c.Check(fiParent.Mode(), Equals, os.FileMode(os.ModeDir|0750))
  1682  	}
  1683  
  1684  	c.Check(filepath.Join(ephemeralUbuntuData, "system-data/var/lib/snapd/state.json"), testutil.FileEquals, `{"data":{"auth":{"last-id":1,"macaroon-key":"not-a-cookie","users":[{"id":1,"name":"mvo"}]}},"changes":{},"tasks":{},"last-change-id":0,"last-task-id":0,"last-lane-id":0}`)
  1685  
  1686  	// finally check that the recovery system bootenv was updated to be in run
  1687  	// mode
  1688  	bloader, err := bootloader.Find("", nil)
  1689  	c.Assert(err, IsNil)
  1690  	m, err := bloader.GetBootVars("snapd_recovery_system", "snapd_recovery_mode")
  1691  	c.Assert(err, IsNil)
  1692  	c.Assert(m, DeepEquals, map[string]string{
  1693  		"snapd_recovery_system": "20191118",
  1694  		"snapd_recovery_mode":   "run",
  1695  	})
  1696  }
  1697  
  1698  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappy(c *C) {
  1699  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
  1700  
  1701  	// setup a bootloader for setting the bootenv after we are done
  1702  	bloader := bootloadertest.Mock("mock", c.MkDir())
  1703  	bootloader.Force(bloader)
  1704  	defer bootloader.Force(nil)
  1705  
  1706  	// mock that we don't know which partition uuid the kernel was booted from
  1707  	restore := main.MockPartitionUUIDForBootedKernelDisk("")
  1708  	defer restore()
  1709  
  1710  	restore = disks.MockMountPointDisksToPartitionMapping(
  1711  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1712  			{Mountpoint: boot.InitramfsUbuntuSeedDir}:     defaultBootDisk,
  1713  			{Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk,
  1714  		},
  1715  	)
  1716  	defer restore()
  1717  
  1718  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1719  		ubuntuLabelMount("ubuntu-seed", "recover"),
  1720  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  1721  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
  1722  		s.makeSeedSnapSystemdMount(snap.TypeBase),
  1723  		{
  1724  			"tmpfs",
  1725  			boot.InitramfsDataDir,
  1726  			tmpfsMountOpts,
  1727  		},
  1728  		{
  1729  			"/dev/disk/by-partuuid/ubuntu-data-partuuid",
  1730  			boot.InitramfsHostUbuntuDataDir,
  1731  			nil,
  1732  		},
  1733  	}, nil)
  1734  	defer restore()
  1735  
  1736  	s.testRecoverModeHappy(c)
  1737  }
  1738  
  1739  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeGadgetDefaultsHappy(c *C) {
  1740  	// setup a seed with default gadget yaml
  1741  	const gadgetYamlDefaults = `
  1742  defaults:
  1743    system:
  1744      service:
  1745        rsyslog.disable: true
  1746        ssh.disable: true
  1747        console-conf.disable: true
  1748      journal.persistent: true
  1749  `
  1750  	c.Assert(os.RemoveAll(s.seedDir), IsNil)
  1751  
  1752  	s.setupSeed(c, [][]string{
  1753  		{"meta/gadget.yaml", gadgetYamlDefaults},
  1754  	})
  1755  
  1756  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
  1757  
  1758  	// setup a bootloader for setting the bootenv after we are done
  1759  	bloader := bootloadertest.Mock("mock", c.MkDir())
  1760  	bootloader.Force(bloader)
  1761  	defer bootloader.Force(nil)
  1762  
  1763  	// mock that we don't know which partition uuid the kernel was booted from
  1764  	restore := main.MockPartitionUUIDForBootedKernelDisk("")
  1765  	defer restore()
  1766  
  1767  	restore = disks.MockMountPointDisksToPartitionMapping(
  1768  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1769  			{Mountpoint: boot.InitramfsUbuntuSeedDir}:     defaultBootDisk,
  1770  			{Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk,
  1771  		},
  1772  	)
  1773  	defer restore()
  1774  
  1775  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1776  		ubuntuLabelMount("ubuntu-seed", "recover"),
  1777  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  1778  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
  1779  		s.makeSeedSnapSystemdMount(snap.TypeBase),
  1780  		{
  1781  			"tmpfs",
  1782  			boot.InitramfsDataDir,
  1783  			tmpfsMountOpts,
  1784  		},
  1785  		{
  1786  			"/dev/disk/by-partuuid/ubuntu-data-partuuid",
  1787  			boot.InitramfsHostUbuntuDataDir,
  1788  			nil,
  1789  		},
  1790  	}, nil)
  1791  	defer restore()
  1792  
  1793  	// we will call out to systemctl in the initramfs, but only using --root
  1794  	// which doesn't talk to systemd, just manipulates files around
  1795  	var sysctlArgs [][]string
  1796  	systemctlRestorer := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) {
  1797  		sysctlArgs = append(sysctlArgs, args)
  1798  		return nil, nil
  1799  	})
  1800  	defer systemctlRestorer()
  1801  
  1802  	s.testRecoverModeHappy(c)
  1803  
  1804  	c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")), Equals, true)
  1805  
  1806  	// check that everything from the gadget defaults was setup
  1807  	c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")), Equals, true)
  1808  	c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/lib/console-conf/complete")), Equals, true)
  1809  	exists, _, _ := osutil.DirExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/log/journal"))
  1810  	c.Assert(exists, Equals, true)
  1811  
  1812  	// systemctl was called the way we expect
  1813  	c.Assert(sysctlArgs, DeepEquals, [][]string{{"--root", filepath.Join(boot.InitramfsWritableDir, "_writable_defaults"), "mask", "rsyslog.service"}})
  1814  }
  1815  
  1816  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyBootedKernelPartitionUUID(c *C) {
  1817  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
  1818  
  1819  	restore := main.MockPartitionUUIDForBootedKernelDisk("specific-ubuntu-seed-partuuid")
  1820  	defer restore()
  1821  
  1822  	// setup a bootloader for setting the bootenv after we are done
  1823  	bloader := bootloadertest.Mock("mock", c.MkDir())
  1824  	bootloader.Force(bloader)
  1825  	defer bootloader.Force(nil)
  1826  
  1827  	restore = disks.MockMountPointDisksToPartitionMapping(
  1828  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1829  			{Mountpoint: boot.InitramfsUbuntuSeedDir}:     defaultBootDisk,
  1830  			{Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk,
  1831  		},
  1832  	)
  1833  	defer restore()
  1834  
  1835  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1836  		{
  1837  			"/dev/disk/by-partuuid/specific-ubuntu-seed-partuuid",
  1838  			boot.InitramfsUbuntuSeedDir,
  1839  			needsFsckDiskMountOpts,
  1840  		},
  1841  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  1842  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
  1843  		s.makeSeedSnapSystemdMount(snap.TypeBase),
  1844  		{
  1845  			"tmpfs",
  1846  			boot.InitramfsDataDir,
  1847  			tmpfsMountOpts,
  1848  		},
  1849  		{
  1850  			"/dev/disk/by-partuuid/ubuntu-data-partuuid",
  1851  			boot.InitramfsHostUbuntuDataDir,
  1852  			nil,
  1853  		},
  1854  	}, nil)
  1855  	defer restore()
  1856  
  1857  	s.testRecoverModeHappy(c)
  1858  }
  1859  
  1860  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyEncrypted(c *C) {
  1861  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
  1862  
  1863  	restore := main.MockPartitionUUIDForBootedKernelDisk("")
  1864  	defer restore()
  1865  
  1866  	// setup a bootloader for setting the bootenv after we are done
  1867  	bloader := bootloadertest.Mock("mock", c.MkDir())
  1868  	bootloader.Force(bloader)
  1869  	defer bootloader.Force(nil)
  1870  
  1871  	restore = disks.MockMountPointDisksToPartitionMapping(
  1872  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1873  			{Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk,
  1874  			{
  1875  				Mountpoint:        boot.InitramfsHostUbuntuDataDir,
  1876  				IsDecryptedDevice: true,
  1877  			}: defaultEncBootDisk,
  1878  		},
  1879  	)
  1880  	defer restore()
  1881  
  1882  	activated := false
  1883  	restore = main.MockSecbootUnlockVolumeIfEncrypted(func(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) {
  1884  		c.Assert(name, Equals, "ubuntu-data")
  1885  		c.Assert(encryptionKeyDir, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde"))
  1886  		encDevPartUUID, err := disk.FindMatchingPartitionUUID(name + "-enc")
  1887  		c.Assert(err, IsNil)
  1888  		c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid")
  1889  		c.Assert(lockKeysOnFinish, Equals, true)
  1890  		activated = true
  1891  		return filepath.Join("/dev/disk/by-partuuid", encDevPartUUID), true, nil
  1892  	})
  1893  	defer restore()
  1894  
  1895  	measureEpochCalls := 0
  1896  	measureModelCalls := 0
  1897  	restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error {
  1898  		measureEpochCalls++
  1899  		return nil
  1900  	})
  1901  	defer restore()
  1902  
  1903  	var measuredModel *asserts.Model
  1904  	restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error {
  1905  		measureModelCalls++
  1906  		var err error
  1907  		measuredModel, err = findModel()
  1908  		if err != nil {
  1909  			return err
  1910  		}
  1911  		return nil
  1912  	})
  1913  	defer restore()
  1914  
  1915  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  1916  		ubuntuLabelMount("ubuntu-seed", "recover"),
  1917  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  1918  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
  1919  		s.makeSeedSnapSystemdMount(snap.TypeBase),
  1920  		{
  1921  			"tmpfs",
  1922  			boot.InitramfsDataDir,
  1923  			tmpfsMountOpts,
  1924  		},
  1925  		{
  1926  			"/dev/disk/by-partuuid/ubuntu-data-enc-partuuid",
  1927  			boot.InitramfsHostUbuntuDataDir,
  1928  			nil,
  1929  		},
  1930  	}, nil)
  1931  	defer restore()
  1932  
  1933  	s.testRecoverModeHappy(c)
  1934  
  1935  	c.Check(activated, Equals, true)
  1936  	c.Check(measureEpochCalls, Equals, 1)
  1937  	c.Check(measureModelCalls, Equals, 1)
  1938  	c.Check(measuredModel, DeepEquals, s.model)
  1939  
  1940  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
  1941  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent)
  1942  }
  1943  
  1944  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFSAttachedHappy(c *C) {
  1945  	s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
  1946  
  1947  	restore := main.MockPartitionUUIDForBootedKernelDisk("")
  1948  	defer restore()
  1949  
  1950  	// setup a bootloader for setting the bootenv
  1951  	bloader := bootloadertest.Mock("mock", c.MkDir())
  1952  	bootloader.Force(bloader)
  1953  	defer bootloader.Force(nil)
  1954  
  1955  	mockDisk := &disks.MockDiskMapping{
  1956  		FilesystemLabelToPartUUID: map[string]string{
  1957  			"ubuntu-seed":     "ubuntu-seed-partuuid",
  1958  			"ubuntu-data-enc": "ubuntu-data-enc-partuuid",
  1959  		},
  1960  		DiskHasPartitions: true,
  1961  		DevNum:            "bootDev",
  1962  	}
  1963  
  1964  	restore = disks.MockMountPointDisksToPartitionMapping(
  1965  		map[disks.Mountpoint]*disks.MockDiskMapping{
  1966  			{Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDisk,
  1967  			{
  1968  				Mountpoint:        boot.InitramfsHostUbuntuDataDir,
  1969  				IsDecryptedDevice: true,
  1970  			}: mockDisk,
  1971  			// this is the attacker fs on a different disk
  1972  			{Mountpoint: "somewhere-else"}: {
  1973  				FilesystemLabelToPartUUID: map[string]string{
  1974  					"ubuntu-seed":     "ubuntu-seed-attacker-partuuid",
  1975  					"ubuntu-data-enc": "ubuntu-data-enc-attacker-partuuid",
  1976  				},
  1977  				DiskHasPartitions: true,
  1978  				DevNum:            "attackerDev",
  1979  			},
  1980  		},
  1981  	)
  1982  	defer restore()
  1983  
  1984  	activated := false
  1985  	restore = main.MockSecbootUnlockVolumeIfEncrypted(func(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) {
  1986  		c.Assert(name, Equals, "ubuntu-data")
  1987  		encDevPartUUID, err := disk.FindMatchingPartitionUUID(name + "-enc")
  1988  		c.Assert(err, IsNil)
  1989  		c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid")
  1990  		c.Assert(lockKeysOnFinish, Equals, true)
  1991  		activated = true
  1992  		return filepath.Join("/dev/disk/by-partuuid", encDevPartUUID), true, nil
  1993  	})
  1994  	defer restore()
  1995  
  1996  	measureEpochCalls := 0
  1997  	measureModelCalls := 0
  1998  	restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error {
  1999  		measureEpochCalls++
  2000  		return nil
  2001  	})
  2002  	defer restore()
  2003  
  2004  	var measuredModel *asserts.Model
  2005  	restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error {
  2006  		measureModelCalls++
  2007  		var err error
  2008  		measuredModel, err = findModel()
  2009  		if err != nil {
  2010  			return err
  2011  		}
  2012  		return nil
  2013  	})
  2014  	defer restore()
  2015  
  2016  	restore = s.mockSystemdMountSequence(c, []systemdMount{
  2017  		ubuntuLabelMount("ubuntu-seed", "recover"),
  2018  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  2019  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
  2020  		s.makeSeedSnapSystemdMount(snap.TypeBase),
  2021  		{
  2022  			"tmpfs",
  2023  			boot.InitramfsDataDir,
  2024  			tmpfsMountOpts,
  2025  		},
  2026  		{
  2027  			"/dev/disk/by-partuuid/ubuntu-data-enc-partuuid",
  2028  			boot.InitramfsHostUbuntuDataDir,
  2029  			nil,
  2030  		},
  2031  	}, nil)
  2032  	defer restore()
  2033  
  2034  	s.testRecoverModeHappy(c)
  2035  
  2036  	c.Check(activated, Equals, true)
  2037  	c.Check(measureEpochCalls, Equals, 1)
  2038  	c.Check(measureModelCalls, Equals, 1)
  2039  	c.Check(measuredModel, DeepEquals, s.model)
  2040  
  2041  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
  2042  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent)
  2043  }
  2044  
  2045  func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C, mode string) {
  2046  	s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, s.sysLabel))
  2047  
  2048  	modeMnts := []systemdMount{
  2049  		ubuntuLabelMount("ubuntu-seed", mode),
  2050  		s.makeSeedSnapSystemdMount(snap.TypeSnapd),
  2051  		s.makeSeedSnapSystemdMount(snap.TypeKernel),
  2052  		s.makeSeedSnapSystemdMount(snap.TypeBase),
  2053  		{
  2054  			"tmpfs",
  2055  			boot.InitramfsDataDir,
  2056  			tmpfsMountOpts,
  2057  		},
  2058  	}
  2059  
  2060  	mockDiskMapping := map[disks.Mountpoint]*disks.MockDiskMapping{
  2061  		{Mountpoint: boot.InitramfsUbuntuSeedDir}: {
  2062  			FilesystemLabelToPartUUID: map[string]string{
  2063  				"ubuntu-seed": "ubuntu-seed-partuuid",
  2064  			},
  2065  			DiskHasPartitions: true,
  2066  		},
  2067  	}
  2068  
  2069  	if mode == "recover" {
  2070  		// setup a bootloader for setting the bootenv after we are done
  2071  		bloader := bootloadertest.Mock("mock", c.MkDir())
  2072  		bootloader.Force(bloader)
  2073  		defer bootloader.Force(nil)
  2074  
  2075  		// add the expected mount of ubuntu-data onto the host data dir
  2076  		modeMnts = append(modeMnts, systemdMount{
  2077  			"/dev/disk/by-partuuid/ubuntu-data-partuuid",
  2078  			boot.InitramfsHostUbuntuDataDir,
  2079  			nil,
  2080  		})
  2081  
  2082  		// also add the ubuntu-data fs label to the disk referenced by the
  2083  		// ubuntu-seed partition
  2084  		disk := mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSeedDir}]
  2085  		disk.FilesystemLabelToPartUUID["ubuntu-data"] = "ubuntu-data-partuuid"
  2086  
  2087  		// and also add the /run/mnt/host/ubuntu-data mountpoint for
  2088  		// cross-checking after it is mounted
  2089  		mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsHostUbuntuDataDir}] = disk
  2090  	}
  2091  
  2092  	restore := disks.MockMountPointDisksToPartitionMapping(mockDiskMapping)
  2093  	defer restore()
  2094  
  2095  	measureEpochCalls := 0
  2096  	measureModelCalls := 0
  2097  	restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error {
  2098  		measureEpochCalls++
  2099  		return nil
  2100  	})
  2101  	defer restore()
  2102  
  2103  	var measuredModel *asserts.Model
  2104  	restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error {
  2105  		measureModelCalls++
  2106  		var err error
  2107  		measuredModel, err = findModel()
  2108  		if err != nil {
  2109  			return err
  2110  		}
  2111  		return nil
  2112  	})
  2113  	defer restore()
  2114  
  2115  	restore = s.mockSystemdMountSequence(c, modeMnts, nil)
  2116  	defer restore()
  2117  
  2118  	if mode == "recover" {
  2119  		// use the helper
  2120  		s.testRecoverModeHappy(c)
  2121  	} else {
  2122  		_, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
  2123  		c.Assert(err, IsNil)
  2124  
  2125  		modeEnv := filepath.Join(boot.InitramfsDataDir, "/system-data/var/lib/snapd/modeenv")
  2126  		c.Check(modeEnv, testutil.FileEquals, `mode=install
  2127  recovery_system=20191118
  2128  `)
  2129  	}
  2130  
  2131  	c.Check(measuredModel, NotNil)
  2132  	c.Check(measuredModel, DeepEquals, s.model)
  2133  	c.Check(measureEpochCalls, Equals, 1)
  2134  	c.Check(measureModelCalls, Equals, 1)
  2135  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
  2136  	c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, s.sysLabel+"-model-measured"), testutil.FilePresent)
  2137  }
  2138  
  2139  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeMeasure(c *C) {
  2140  	s.testInitramfsMountsInstallRecoverModeMeasure(c, "install")
  2141  }
  2142  
  2143  func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeUnsetMeasure(c *C) {
  2144  	// TODO:UC20: eventually we should require snapd_recovery_mode to be set to
  2145  	// explicitly "install" for install mode, but we originally allowed
  2146  	// snapd_recovery_mode="" and interpreted it as install mode, so test that
  2147  	// case too
  2148  	s.testInitramfsMountsInstallRecoverModeMeasure(c, "")
  2149  }
  2150  
  2151  func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeMeasure(c *C) {
  2152  	s.testInitramfsMountsInstallRecoverModeMeasure(c, "recover")
  2153  }