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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5  *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18  */
    19  
    20  package boot_test
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/boot/boottest"
    32  	"github.com/snapcore/snapd/bootloader"
    33  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    34  	"github.com/snapcore/snapd/secboot"
    35  	"github.com/snapcore/snapd/seed"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/timings"
    38  )
    39  
    40  type baseSystemsSuite struct {
    41  	baseBootenvSuite
    42  }
    43  
    44  func (s *baseSystemsSuite) SetUpTest(c *C) {
    45  	s.baseBootenvSuite.SetUpTest(c)
    46  	c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil)
    47  	c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil)
    48  }
    49  
    50  type systemsSuite struct {
    51  	baseSystemsSuite
    52  
    53  	uc20dev boot.Device
    54  
    55  	runKernelBf      bootloader.BootFile
    56  	recoveryKernelBf bootloader.BootFile
    57  	seedKernelSnap   *seed.Snap
    58  }
    59  
    60  var _ = Suite(&systemsSuite{})
    61  
    62  func (s *systemsSuite) mockTrustedBootloaderWithAssetAndChains(c *C, runKernelBf, recoveryKernelBf bootloader.BootFile) *bootloadertest.MockTrustedAssetsBootloader {
    63  	mockAssetsCache(c, s.rootdir, "trusted", []string{
    64  		"asset-asset-hash-1",
    65  	})
    66  
    67  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
    68  	mtbl.TrustedAssetsList = []string{"asset-1"}
    69  	mtbl.StaticCommandLine = "static cmdline"
    70  	mtbl.BootChainList = []bootloader.BootFile{
    71  		bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
    72  		runKernelBf,
    73  	}
    74  	mtbl.RecoveryBootChainList = []bootloader.BootFile{
    75  		bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
    76  		recoveryKernelBf,
    77  	}
    78  	bootloader.Force(mtbl)
    79  	return mtbl
    80  }
    81  
    82  func (s *systemsSuite) SetUpTest(c *C) {
    83  	s.baseBootenvSuite.SetUpTest(c)
    84  
    85  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil })
    86  	s.AddCleanup(restore)
    87  
    88  	s.uc20dev = boottest.MockUC20Device("", nil)
    89  
    90  	// run kernel
    91  	s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap",
    92  		"kernel.efi", bootloader.RoleRunMode)
    93  	// seed (recovery) kernel
    94  	s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
    95  		"kernel.efi", bootloader.RoleRecovery)
    96  
    97  	s.seedKernelSnap = &seed.Snap{
    98  		Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
    99  		SideInfo: &snap.SideInfo{
   100  			RealName: "pc-kernel",
   101  			Revision: snap.Revision{N: 1},
   102  		},
   103  	}
   104  }
   105  
   106  func (s *systemsSuite) TestSetTryRecoverySystemEncrypted(c *C) {
   107  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   108  		"asset-asset-hash-1",
   109  	})
   110  
   111  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   112  	bootloader.Force(mtbl)
   113  	defer bootloader.Force(nil)
   114  
   115  	// system is encrypted
   116  	s.stampSealedKeys(c, s.rootdir)
   117  
   118  	modeenv := &boot.Modeenv{
   119  		Mode: "run",
   120  		// keep this comment to make old gofmt happy
   121  		CurrentRecoverySystems: []string{"20200825"},
   122  		GoodRecoverySystems:    []string{"20200825"},
   123  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   124  			"asset": []string{"asset-hash-1"},
   125  		},
   126  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   127  			"asset": []string{"asset-hash-1"},
   128  		},
   129  		CurrentKernels: []string{"pc-kernel_500.snap"},
   130  	}
   131  	c.Assert(modeenv.WriteTo(""), IsNil)
   132  
   133  	var readSeedSeenLabels []string
   134  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   135  		// the mock bootloader can only mock a single recovery boot
   136  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
   137  		readSeedSeenLabels = append(readSeedSeenLabels, label)
   138  		return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   139  	})
   140  	defer restore()
   141  
   142  	resealCalls := 0
   143  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   144  		resealCalls++
   145  		// bootloader variables have already been modified
   146  		c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   147  		c.Assert(params, NotNil)
   148  		c.Assert(params.ModelParams, HasLen, 1)
   149  		switch resealCalls {
   150  		case 1:
   151  			c.Check(params.KeyFiles, DeepEquals, []string{
   152  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   153  			})
   154  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   155  				"snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline",
   156  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   157  				"snapd_recovery_mode=run static cmdline",
   158  			})
   159  			return nil
   160  		case 2:
   161  			c.Check(params.KeyFiles, DeepEquals, []string{
   162  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   163  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   164  			})
   165  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   166  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   167  			})
   168  			return nil
   169  		default:
   170  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
   171  			return fmt.Errorf("unexpected call")
   172  		}
   173  	})
   174  	defer restore()
   175  
   176  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   177  	c.Assert(err, IsNil)
   178  
   179  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   180  	c.Assert(err, IsNil)
   181  	c.Check(vars, DeepEquals, map[string]string{
   182  		"try_recovery_system":    "1234",
   183  		"recovery_system_status": "try",
   184  	})
   185  	// run and recovery keys
   186  	c.Check(resealCalls, Equals, 2)
   187  	c.Check(readSeedSeenLabels, DeepEquals, []string{
   188  		"20200825", "1234", // current recovery systems for run key
   189  		"20200825", // good recovery systems for recovery keys
   190  	})
   191  
   192  	modeenvRead, err := boot.ReadModeenv("")
   193  	c.Assert(err, IsNil)
   194  	c.Check(modeenvRead.DeepEqual(&boot.Modeenv{
   195  		Mode: "run",
   196  		// keep this comment to make old gofmt happy
   197  		CurrentRecoverySystems: []string{"20200825", "1234"},
   198  		GoodRecoverySystems:    []string{"20200825"},
   199  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   200  			"asset": []string{"asset-hash-1"},
   201  		},
   202  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   203  			"asset": []string{"asset-hash-1"},
   204  		},
   205  		CurrentKernels: []string{"pc-kernel_500.snap"},
   206  	}), Equals, true)
   207  }
   208  
   209  func (s *systemsSuite) TestSetTryRecoverySystemSimple(c *C) {
   210  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
   211  	bootloader.Force(mtbl)
   212  	defer bootloader.Force(nil)
   213  
   214  	modeenv := &boot.Modeenv{
   215  		Mode: "run",
   216  		// keep this comment to make old gofmt happy
   217  		CurrentRecoverySystems: []string{"20200825"},
   218  	}
   219  	c.Assert(modeenv.WriteTo(""), IsNil)
   220  
   221  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   222  		return fmt.Errorf("unexpected call")
   223  	})
   224  	s.AddCleanup(restore)
   225  
   226  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   227  	c.Assert(err, IsNil)
   228  
   229  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   230  	c.Assert(err, IsNil)
   231  	c.Check(vars, DeepEquals, map[string]string{
   232  		"try_recovery_system":    "1234",
   233  		"recovery_system_status": "try",
   234  	})
   235  
   236  	modeenvRead, err := boot.ReadModeenv("")
   237  	c.Assert(err, IsNil)
   238  	c.Check(modeenvRead.DeepEqual(&boot.Modeenv{
   239  		Mode: "run",
   240  		// keep this comment to make old gofmt happy
   241  		CurrentRecoverySystems: []string{"20200825", "1234"},
   242  	}), Equals, true)
   243  }
   244  
   245  func (s *systemsSuite) TestSetTryRecoverySystemSetBootVarsErr(c *C) {
   246  	mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
   247  	bootloader.Force(mtbl)
   248  	defer bootloader.Force(nil)
   249  
   250  	modeenv := &boot.Modeenv{
   251  		Mode: "run",
   252  		// keep this comment to make old gofmt happy
   253  		CurrentRecoverySystems: []string{"20200825"},
   254  	}
   255  	c.Assert(modeenv.WriteTo(""), IsNil)
   256  
   257  	restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   258  		return fmt.Errorf("unexpected call")
   259  	})
   260  	s.AddCleanup(restore)
   261  
   262  	mtbl.BootVars = map[string]string{
   263  		"try_recovery_system":    "mock",
   264  		"recovery_system_status": "mock",
   265  	}
   266  	mtbl.SetErrFunc = func() error {
   267  		switch mtbl.SetBootVarsCalls {
   268  		case 1:
   269  			return fmt.Errorf("set boot vars fails")
   270  		case 2:
   271  			// called during cleanup
   272  			return nil
   273  		default:
   274  			return fmt.Errorf("unexpected call")
   275  		}
   276  	}
   277  
   278  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   279  	c.Assert(err, ErrorMatches, "set boot vars fails")
   280  
   281  	// cleared
   282  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   283  	c.Assert(err, IsNil)
   284  	c.Check(vars, DeepEquals, map[string]string{
   285  		"try_recovery_system":    "",
   286  		"recovery_system_status": "",
   287  	})
   288  
   289  	modeenvRead, err := boot.ReadModeenv("")
   290  	c.Assert(err, IsNil)
   291  	// modeenv is unchanged
   292  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   293  }
   294  
   295  func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorBeforeReseal(c *C) {
   296  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   297  		"asset-asset-hash-1",
   298  	})
   299  
   300  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   301  	defer bootloader.Force(nil)
   302  
   303  	// system is encrypted
   304  	s.stampSealedKeys(c, s.rootdir)
   305  
   306  	modeenv := &boot.Modeenv{
   307  		Mode: "run",
   308  		// keep this comment to make old gofmt happy
   309  		CurrentRecoverySystems: []string{"20200825"},
   310  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   311  			"asset": []string{"asset-hash-1"},
   312  		},
   313  
   314  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   315  			"asset": []string{"asset-hash-1"},
   316  		},
   317  	}
   318  	c.Assert(modeenv.WriteTo(""), IsNil)
   319  
   320  	readSeedCalls := 0
   321  	cleanupTriggered := false
   322  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   323  		readSeedCalls++
   324  		// this is the reseal cleanup path
   325  		switch readSeedCalls {
   326  		case 1:
   327  			// called for the first system
   328  			c.Assert(label, Equals, "20200825")
   329  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   330  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   331  		case 2:
   332  			// called for the 'try' system
   333  			c.Assert(label, Equals, "1234")
   334  			// modeenv is updated first
   335  			modeenvRead, err := boot.ReadModeenv("")
   336  			c.Assert(err, IsNil)
   337  			c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
   338  				"20200825", "1234",
   339  			})
   340  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   341  			// we are triggering the cleanup by returning an error now
   342  			cleanupTriggered = true
   343  			return nil, nil, fmt.Errorf("seed read essential fails")
   344  		case 3:
   345  			// (cleanup) recovery boot chains for run key, called
   346  			// for the first system only
   347  			fallthrough
   348  		case 4:
   349  			// (cleanup) recovery boot chains for recovery keys
   350  			c.Assert(label, Equals, "20200825")
   351  			// boot variables already updated
   352  			c.Check(mtbl.SetBootVarsCalls, Equals, 2)
   353  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   354  		default:
   355  			return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
   356  		}
   357  	})
   358  	defer restore()
   359  
   360  	resealCalls := 0
   361  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   362  		resealCalls++
   363  		if cleanupTriggered {
   364  			return nil
   365  		}
   366  		return fmt.Errorf("unexpected call")
   367  	})
   368  	defer restore()
   369  
   370  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   371  	c.Assert(err, ErrorMatches, ".*: seed read essential fails")
   372  
   373  	// failed after the call to read the 'try' system seed
   374  	c.Check(readSeedCalls, Equals, 4)
   375  	// called twice during cleanup for run and recovery keys
   376  	c.Check(resealCalls, Equals, 2)
   377  
   378  	modeenvRead, err := boot.ReadModeenv("")
   379  	c.Assert(err, IsNil)
   380  	// modeenv is unchanged
   381  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   382  	// bootloader variables have been cleared
   383  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   384  	c.Assert(err, IsNil)
   385  	c.Check(vars, DeepEquals, map[string]string{
   386  		"try_recovery_system":    "",
   387  		"recovery_system_status": "",
   388  	})
   389  }
   390  
   391  func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorAfterReseal(c *C) {
   392  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   393  		"asset-asset-hash-1",
   394  	})
   395  
   396  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   397  	bootloader.Force(mtbl)
   398  	defer bootloader.Force(nil)
   399  
   400  	// system is encrypted
   401  	s.stampSealedKeys(c, s.rootdir)
   402  
   403  	modeenv := &boot.Modeenv{
   404  		Mode: "run",
   405  		// keep this comment to make old gofmt happy
   406  		CurrentRecoverySystems: []string{"20200825"},
   407  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   408  			"asset": []string{"asset-hash-1"},
   409  		},
   410  
   411  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   412  			"asset": []string{"asset-hash-1"},
   413  		},
   414  	}
   415  	c.Assert(modeenv.WriteTo(""), IsNil)
   416  
   417  	readSeedCalls := 0
   418  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   419  		readSeedCalls++
   420  		// this is the reseal cleanup path
   421  
   422  		switch readSeedCalls {
   423  		case 1:
   424  			// called for the first system
   425  			c.Assert(label, Equals, "20200825")
   426  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   427  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   428  		case 2:
   429  			// called for the 'try' system
   430  			c.Assert(label, Equals, "1234")
   431  			// modeenv is updated first
   432  			modeenvRead, err := boot.ReadModeenv("")
   433  			c.Assert(err, IsNil)
   434  			c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
   435  				"20200825", "1234",
   436  			})
   437  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   438  			// still good
   439  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   440  		case 3:
   441  			// recovery boot chains for a good recovery system
   442  			c.Check(mtbl.SetBootVarsCalls, Equals, 1)
   443  			fallthrough
   444  		case 4:
   445  			// (cleanup) recovery boot chains for run key, called
   446  			// for the first system only
   447  			fallthrough
   448  		case 5:
   449  			// (cleanup) recovery boot chains for recovery keys
   450  			c.Assert(label, Equals, "20200825")
   451  			// boot variables already updated
   452  			if readSeedCalls >= 4 {
   453  				c.Check(mtbl.SetBootVarsCalls, Equals, 2)
   454  			}
   455  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   456  		default:
   457  			return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
   458  		}
   459  	})
   460  	defer restore()
   461  
   462  	resealCalls := 0
   463  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   464  		resealCalls++
   465  		switch resealCalls {
   466  		case 1:
   467  			// attempt to reseal the run key
   468  			return fmt.Errorf("reseal fails")
   469  		case 2, 3:
   470  			// reseal of run and recovery keys
   471  			return nil
   472  		default:
   473  			return fmt.Errorf("unexpected call")
   474  
   475  		}
   476  	})
   477  	defer restore()
   478  
   479  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   480  	c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails")
   481  
   482  	// failed after the call to read the 'try' system seed
   483  	c.Check(readSeedCalls, Equals, 5)
   484  	// called 3 times, once when mocked failure occurs, twice during cleanup
   485  	// for run and recovery keys
   486  	c.Check(resealCalls, Equals, 3)
   487  
   488  	modeenvRead, err := boot.ReadModeenv("")
   489  	c.Assert(err, IsNil)
   490  	// modeenv is unchanged
   491  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   492  	// bootloader variables have been cleared
   493  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   494  	c.Assert(err, IsNil)
   495  	c.Check(vars, DeepEquals, map[string]string{
   496  		"try_recovery_system":    "",
   497  		"recovery_system_status": "",
   498  	})
   499  }
   500  
   501  func (s *systemsSuite) TestSetTryRecoverySystemCleanupError(c *C) {
   502  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   503  		"asset-asset-hash-1",
   504  	})
   505  
   506  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   507  	bootloader.Force(mtbl)
   508  	defer bootloader.Force(nil)
   509  
   510  	// system is encrypted
   511  	s.stampSealedKeys(c, s.rootdir)
   512  
   513  	modeenv := &boot.Modeenv{
   514  		Mode: "run",
   515  		// keep this comment to make old gofmt happy
   516  		CurrentRecoverySystems: []string{"20200825"},
   517  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   518  			"asset": []string{"asset-hash-1"},
   519  		},
   520  
   521  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   522  			"asset": []string{"asset-hash-1"},
   523  		},
   524  	}
   525  	c.Assert(modeenv.WriteTo(""), IsNil)
   526  
   527  	readSeedCalls := 0
   528  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   529  		readSeedCalls++
   530  		// this is the reseal cleanup path
   531  		switch readSeedCalls {
   532  		case 1:
   533  			// called for the first system
   534  			c.Assert(label, Equals, "20200825")
   535  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   536  		case 2:
   537  			// called for the 'try' system
   538  			c.Assert(label, Equals, "1234")
   539  			// still good
   540  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   541  		case 3:
   542  			// recovery boot chains for a good recovery system
   543  			fallthrough
   544  		case 4:
   545  			// (cleanup) recovery boot chains for run key, called
   546  			// for the first system only
   547  			fallthrough
   548  		case 5:
   549  			// (cleanup) recovery boot chains for recovery keys
   550  			c.Assert(label, Equals, "20200825")
   551  			return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   552  		default:
   553  			return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
   554  		}
   555  	})
   556  	defer restore()
   557  
   558  	resealCalls := 0
   559  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   560  		resealCalls++
   561  		switch resealCalls {
   562  		case 1:
   563  			return fmt.Errorf("reseal fails")
   564  		case 2, 3:
   565  			// reseal of run and recovery keys
   566  			return fmt.Errorf("reseal in cleanup fails too")
   567  		default:
   568  			return fmt.Errorf("unexpected call")
   569  
   570  		}
   571  	})
   572  	defer restore()
   573  
   574  	err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
   575  	c.Assert(err, ErrorMatches, `cannot reseal the encryption key: reseal fails \(cleanup failed: cannot reseal the encryption key: reseal in cleanup fails too\)`)
   576  
   577  	// failed after the call to read the 'try' system seed
   578  	c.Check(readSeedCalls, Equals, 5)
   579  	// called twice, once when enabling the try system, once on cleanup
   580  	c.Check(resealCalls, Equals, 2)
   581  
   582  	modeenvRead, err := boot.ReadModeenv("")
   583  	c.Assert(err, IsNil)
   584  	// modeenv is unchanged
   585  	c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
   586  	// bootloader variables have been cleared regardless of reseal failing
   587  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   588  	c.Assert(err, IsNil)
   589  	c.Check(vars, DeepEquals, map[string]string{
   590  		"try_recovery_system":    "",
   591  		"recovery_system_status": "",
   592  	})
   593  }
   594  
   595  func (s *systemsSuite) testInspectRecoverySystemOutcomeHappy(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, expectedOutcome boot.TryRecoverySystemOutcome, expectedErr string) {
   596  	bootloader.Force(mtbl)
   597  	defer bootloader.Force(nil)
   598  
   599  	// system is encrypted
   600  	s.stampSealedKeys(c, s.rootdir)
   601  
   602  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   603  		return nil, nil, fmt.Errorf("unexpected call")
   604  	})
   605  	defer restore()
   606  
   607  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   608  		return fmt.Errorf("unexpected call")
   609  	})
   610  	defer restore()
   611  
   612  	outcome, label, err := boot.InspectTryRecoverySystemOutcome(s.uc20dev)
   613  	if expectedErr == "" {
   614  		c.Assert(err, IsNil)
   615  	} else {
   616  		c.Assert(err, ErrorMatches, expectedErr)
   617  	}
   618  	c.Check(outcome, Equals, expectedOutcome)
   619  	switch outcome {
   620  	case boot.TryRecoverySystemOutcomeSuccess, boot.TryRecoverySystemOutcomeFailure:
   621  		c.Check(label, Equals, "1234")
   622  	default:
   623  		c.Check(label, Equals, "")
   624  	}
   625  }
   626  
   627  func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappySuccess(c *C) {
   628  	triedVars := map[string]string{
   629  		"recovery_system_status": "tried",
   630  		"try_recovery_system":    "1234",
   631  	}
   632  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   633  	err := mtbl.SetBootVars(triedVars)
   634  	c.Assert(err, IsNil)
   635  
   636  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeSuccess, "")
   637  
   638  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   639  	c.Assert(err, IsNil)
   640  	c.Check(vars, DeepEquals, triedVars)
   641  }
   642  
   643  func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappyFailure(c *C) {
   644  	tryVars := map[string]string{
   645  		"recovery_system_status": "try",
   646  		"try_recovery_system":    "1234",
   647  	}
   648  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   649  	err := mtbl.SetBootVars(tryVars)
   650  	c.Assert(err, IsNil)
   651  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, "")
   652  
   653  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   654  	c.Assert(err, IsNil)
   655  	c.Check(vars, DeepEquals, tryVars)
   656  }
   657  
   658  func (s *systemsSuite) TestInspectRecoverySystemOutcomeNotTried(c *C) {
   659  	notTriedVars := map[string]string{
   660  		"recovery_system_status": "",
   661  		"try_recovery_system":    "",
   662  	}
   663  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   664  	err := mtbl.SetBootVars(notTriedVars)
   665  	c.Assert(err, IsNil)
   666  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeNoneTried, "")
   667  }
   668  
   669  func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBogusStatus(c *C) {
   670  	badVars := map[string]string{
   671  		"recovery_system_status": "foo",
   672  		"try_recovery_system":    "1234",
   673  	}
   674  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   675  	err := mtbl.SetBootVars(badVars)
   676  	c.Assert(err, IsNil)
   677  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status "foo"`)
   678  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   679  	c.Assert(err, IsNil)
   680  	c.Check(vars, DeepEquals, badVars)
   681  }
   682  
   683  func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBadLabel(c *C) {
   684  	badVars := map[string]string{
   685  		"recovery_system_status": "tried",
   686  		"try_recovery_system":    "",
   687  	}
   688  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   689  	err := mtbl.SetBootVars(badVars)
   690  	c.Assert(err, IsNil)
   691  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `try recovery system is unset but status is "tried"`)
   692  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   693  	c.Assert(err, IsNil)
   694  	c.Check(vars, DeepEquals, badVars)
   695  }
   696  
   697  func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentUnexpectedLabel(c *C) {
   698  	badVars := map[string]string{
   699  		"recovery_system_status": "",
   700  		"try_recovery_system":    "1234",
   701  	}
   702  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   703  	err := mtbl.SetBootVars(badVars)
   704  	c.Assert(err, IsNil)
   705  	s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status ""`)
   706  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   707  	c.Assert(err, IsNil)
   708  	c.Check(vars, DeepEquals, badVars)
   709  }
   710  
   711  func (s *systemsSuite) testClearRecoverySystem(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, systemLabel string, resealErr error, expectedErr string) {
   712  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   713  		"asset-asset-hash-1",
   714  	})
   715  
   716  	bootloader.Force(mtbl)
   717  	defer bootloader.Force(nil)
   718  
   719  	// system is encrypted
   720  	s.stampSealedKeys(c, s.rootdir)
   721  
   722  	modeenv := &boot.Modeenv{
   723  		Mode: "run",
   724  		// keep this comment to make old gofmt happy
   725  		CurrentRecoverySystems: []string{"20200825"},
   726  		GoodRecoverySystems:    []string{"20200825"},
   727  		CurrentKernels:         []string{},
   728  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   729  			"asset": []string{"asset-hash-1"},
   730  		},
   731  
   732  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   733  			"asset": []string{"asset-hash-1"},
   734  		},
   735  	}
   736  	if systemLabel != "" {
   737  		modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel)
   738  	}
   739  	c.Assert(modeenv.WriteTo(""), IsNil)
   740  
   741  	var readSeedSeenLabels []string
   742  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   743  		// the mock bootloader can only mock a single recovery boot
   744  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
   745  		readSeedSeenLabels = append(readSeedSeenLabels, label)
   746  		return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   747  	})
   748  	defer restore()
   749  
   750  	resealCalls := 0
   751  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   752  		resealCalls++
   753  		c.Assert(params, NotNil)
   754  		c.Assert(params.ModelParams, HasLen, 1)
   755  		switch resealCalls {
   756  		case 1:
   757  			c.Check(params.KeyFiles, DeepEquals, []string{
   758  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   759  			})
   760  			return resealErr
   761  		case 2:
   762  			c.Check(params.KeyFiles, DeepEquals, []string{
   763  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   764  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   765  			})
   766  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   767  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   768  			})
   769  			return nil
   770  		default:
   771  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
   772  			return fmt.Errorf("unexpected call")
   773  		}
   774  	})
   775  	defer restore()
   776  
   777  	err := boot.ClearTryRecoverySystem(s.uc20dev, systemLabel)
   778  	if expectedErr == "" {
   779  		c.Assert(err, IsNil)
   780  	} else {
   781  		c.Assert(err, ErrorMatches, expectedErr)
   782  	}
   783  
   784  	// only one seed system accessed
   785  	c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
   786  	if resealErr == nil {
   787  		// called twice, for run and recovery keys
   788  		c.Check(resealCalls, Equals, 2)
   789  	} else {
   790  		// fails on run key
   791  		c.Check(resealCalls, Equals, 1)
   792  	}
   793  
   794  	modeenvRead, err := boot.ReadModeenv("")
   795  	c.Assert(err, IsNil)
   796  	// modeenv systems list has one entry only
   797  	c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
   798  		"20200825",
   799  	})
   800  }
   801  
   802  func (s *systemsSuite) TestClearRecoverySystemHappy(c *C) {
   803  	setVars := map[string]string{
   804  		"recovery_system_status": "try",
   805  		"try_recovery_system":    "1234",
   806  	}
   807  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   808  	err := mtbl.SetBootVars(setVars)
   809  	c.Assert(err, IsNil)
   810  
   811  	s.testClearRecoverySystem(c, mtbl, "1234", nil, "")
   812  	// bootloader variables have been cleared
   813  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   814  	c.Assert(err, IsNil)
   815  	c.Check(vars, DeepEquals, map[string]string{
   816  		"try_recovery_system":    "",
   817  		"recovery_system_status": "",
   818  	})
   819  }
   820  
   821  func (s *systemsSuite) TestClearRecoverySystemTriedHappy(c *C) {
   822  	setVars := map[string]string{
   823  		"recovery_system_status": "tried",
   824  		"try_recovery_system":    "1234",
   825  	}
   826  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   827  	err := mtbl.SetBootVars(setVars)
   828  	c.Assert(err, IsNil)
   829  
   830  	s.testClearRecoverySystem(c, mtbl, "1234", nil, "")
   831  	// bootloader variables have been cleared
   832  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   833  	c.Assert(err, IsNil)
   834  	c.Check(vars, DeepEquals, map[string]string{
   835  		"try_recovery_system":    "",
   836  		"recovery_system_status": "",
   837  	})
   838  }
   839  
   840  func (s *systemsSuite) TestClearRecoverySystemInconsistentStateHappy(c *C) {
   841  	setVars := map[string]string{
   842  		"recovery_system_status": "foo",
   843  		"try_recovery_system":    "",
   844  	}
   845  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   846  	err := mtbl.SetBootVars(setVars)
   847  	c.Assert(err, IsNil)
   848  
   849  	s.testClearRecoverySystem(c, mtbl, "1234", nil, "")
   850  	// bootloader variables have been cleared
   851  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   852  	c.Assert(err, IsNil)
   853  	c.Check(vars, DeepEquals, map[string]string{
   854  		"try_recovery_system":    "",
   855  		"recovery_system_status": "",
   856  	})
   857  }
   858  
   859  func (s *systemsSuite) TestClearRecoverySystemInconsistentNoLabelHappy(c *C) {
   860  	setVars := map[string]string{
   861  		"recovery_system_status": "this-will-be-gone",
   862  		"try_recovery_system":    "this-too",
   863  	}
   864  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   865  	err := mtbl.SetBootVars(setVars)
   866  	c.Assert(err, IsNil)
   867  
   868  	// clear without passing the system label, just clears the relevant boot
   869  	// variables
   870  	const noLabel = ""
   871  	s.testClearRecoverySystem(c, mtbl, noLabel, nil, "")
   872  	// bootloader variables have been cleared
   873  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   874  	c.Assert(err, IsNil)
   875  	c.Check(vars, DeepEquals, map[string]string{
   876  		"try_recovery_system":    "",
   877  		"recovery_system_status": "",
   878  	})
   879  }
   880  
   881  func (s *systemsSuite) TestClearRecoverySystemResealFails(c *C) {
   882  	setVars := map[string]string{
   883  		"recovery_system_status": "try",
   884  		"try_recovery_system":    "1234",
   885  	}
   886  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   887  	err := mtbl.SetBootVars(setVars)
   888  	c.Assert(err, IsNil)
   889  
   890  	s.testClearRecoverySystem(c, mtbl, "1234", fmt.Errorf("reseal fails"), "cannot reseal the encryption key: reseal fails")
   891  	// bootloader variables have been cleared
   892  	vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
   893  	c.Assert(err, IsNil)
   894  	// variables were cleared
   895  	c.Check(vars, DeepEquals, map[string]string{
   896  		"try_recovery_system":    "",
   897  		"recovery_system_status": "",
   898  	})
   899  }
   900  
   901  func (s *systemsSuite) TestClearRecoverySystemSetBootVarsFails(c *C) {
   902  	setVars := map[string]string{
   903  		"recovery_system_status": "try",
   904  		"try_recovery_system":    "1234",
   905  	}
   906  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   907  	err := mtbl.SetBootVars(setVars)
   908  	c.Assert(err, IsNil)
   909  	mtbl.SetErr = fmt.Errorf("set boot vars fails")
   910  
   911  	s.testClearRecoverySystem(c, mtbl, "1234", nil, "set boot vars fails")
   912  }
   913  
   914  func (s *systemsSuite) TestClearRecoverySystemReboot(c *C) {
   915  	setVars := map[string]string{
   916  		"recovery_system_status": "try",
   917  		"try_recovery_system":    "1234",
   918  	}
   919  	mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
   920  	err := mtbl.SetBootVars(setVars)
   921  	c.Assert(err, IsNil)
   922  
   923  	mockAssetsCache(c, s.rootdir, "trusted", []string{
   924  		"asset-asset-hash-1",
   925  	})
   926  
   927  	bootloader.Force(mtbl)
   928  	defer bootloader.Force(nil)
   929  
   930  	// system is encrypted
   931  	s.stampSealedKeys(c, s.rootdir)
   932  
   933  	modeenv := &boot.Modeenv{
   934  		Mode: "run",
   935  		// keep this comment to make old gofmt happy
   936  		CurrentRecoverySystems: []string{"20200825", "1234"},
   937  		CurrentKernels:         []string{},
   938  		CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
   939  			"asset": []string{"asset-hash-1"},
   940  		},
   941  
   942  		CurrentTrustedBootAssets: boot.BootAssetsMap{
   943  			"asset": []string{"asset-hash-1"},
   944  		},
   945  	}
   946  	c.Assert(modeenv.WriteTo(""), IsNil)
   947  
   948  	var readSeedSeenLabels []string
   949  	restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
   950  		// the mock bootloader can only mock a single recovery boot
   951  		// chain, so pretend both seeds use the same kernel, but keep track of the labels
   952  		readSeedSeenLabels = append(readSeedSeenLabels, label)
   953  		return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap}, nil
   954  	})
   955  	defer restore()
   956  
   957  	resealCalls := 0
   958  	restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
   959  		resealCalls++
   960  		c.Assert(params, NotNil)
   961  		c.Assert(params.ModelParams, HasLen, 1)
   962  		switch resealCalls {
   963  		case 1:
   964  			c.Check(params.KeyFiles, DeepEquals, []string{
   965  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   966  			})
   967  			panic("reseal panic")
   968  		case 2:
   969  			c.Check(params.KeyFiles, DeepEquals, []string{
   970  				filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
   971  			})
   972  			return nil
   973  		case 3:
   974  			c.Check(params.KeyFiles, DeepEquals, []string{
   975  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
   976  				filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
   977  			})
   978  			c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
   979  				"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
   980  			})
   981  			return nil
   982  		default:
   983  			c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
   984  			return fmt.Errorf("unexpected call")
   985  
   986  		}
   987  	})
   988  	defer restore()
   989  
   990  	checkGoodState := func() {
   991  		// modeenv was already written
   992  		modeenvRead, err := boot.ReadModeenv("")
   993  		c.Assert(err, IsNil)
   994  		// modeenv systems list has one entry only
   995  		c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
   996  			"20200825",
   997  		})
   998  		// bootloader variables have been cleared already
   999  		vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
  1000  		c.Assert(err, IsNil)
  1001  		// variables were cleared
  1002  		c.Check(vars, DeepEquals, map[string]string{
  1003  			"try_recovery_system":    "",
  1004  			"recovery_system_status": "",
  1005  		})
  1006  	}
  1007  
  1008  	c.Assert(func() {
  1009  		boot.ClearTryRecoverySystem(s.uc20dev, "1234")
  1010  	}, PanicMatches, "reseal panic")
  1011  	// only one seed system accessed
  1012  	c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
  1013  	// panicked on run key
  1014  	c.Check(resealCalls, Equals, 1)
  1015  	checkGoodState()
  1016  
  1017  	mtbl.SetErrFunc = func() error {
  1018  		panic("set boot vars panic")
  1019  	}
  1020  	c.Assert(func() {
  1021  		boot.ClearTryRecoverySystem(s.uc20dev, "1234")
  1022  	}, PanicMatches, "set boot vars panic")
  1023  	// we did not reach resealing yet
  1024  	c.Check(resealCalls, Equals, 1)
  1025  	checkGoodState()
  1026  
  1027  	mtbl.SetErrFunc = nil
  1028  	err = boot.ClearTryRecoverySystem(s.uc20dev, "1234")
  1029  	c.Assert(err, IsNil)
  1030  	checkGoodState()
  1031  }
  1032  
  1033  type initramfsMarkTryRecoverySystemSuite struct {
  1034  	baseSystemsSuite
  1035  
  1036  	bl *bootloadertest.MockBootloader
  1037  }
  1038  
  1039  var _ = Suite(&initramfsMarkTryRecoverySystemSuite{})
  1040  
  1041  func (s *initramfsMarkTryRecoverySystemSuite) SetUpTest(c *C) {
  1042  	s.baseSystemsSuite.SetUpTest(c)
  1043  
  1044  	s.bl = bootloadertest.Mock("bootloader", s.bootdir)
  1045  	bootloader.Force(s.bl)
  1046  	s.AddCleanup(func() { bootloader.Force(nil) })
  1047  }
  1048  
  1049  func (s *initramfsMarkTryRecoverySystemSuite) testMarkRecoverySystemForRun(c *C, outcome boot.TryRecoverySystemOutcome, expectingStatus string) {
  1050  	err := s.bl.SetBootVars(map[string]string{
  1051  		"recovery_system_status": "try",
  1052  		"try_recovery_system":    "1234",
  1053  	})
  1054  	c.Assert(err, IsNil)
  1055  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome)
  1056  	c.Assert(err, IsNil)
  1057  
  1058  	expectedVars := map[string]string{
  1059  		"snapd_recovery_mode":   "run",
  1060  		"snapd_recovery_system": "",
  1061  
  1062  		"recovery_system_status": expectingStatus,
  1063  		"try_recovery_system":    "1234",
  1064  	}
  1065  
  1066  	vars, err := s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system",
  1067  		"recovery_system_status", "try_recovery_system")
  1068  	c.Assert(err, IsNil)
  1069  	c.Check(vars, DeepEquals, expectedVars)
  1070  
  1071  	err = s.bl.SetBootVars(map[string]string{
  1072  		// the status is overwritten, even if it's completely bogus
  1073  		"recovery_system_status": "foobar",
  1074  		"try_recovery_system":    "1234",
  1075  	})
  1076  	c.Assert(err, IsNil)
  1077  
  1078  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome)
  1079  	c.Assert(err, IsNil)
  1080  
  1081  	vars, err = s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system",
  1082  		"recovery_system_status", "try_recovery_system")
  1083  	c.Assert(err, IsNil)
  1084  	c.Check(vars, DeepEquals, expectedVars)
  1085  }
  1086  
  1087  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkTryRecoverySystemSuccess(c *C) {
  1088  	s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeSuccess, "tried")
  1089  }
  1090  
  1091  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemFailure(c *C) {
  1092  	s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeFailure, "try")
  1093  }
  1094  
  1095  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemBogus(c *C) {
  1096  	s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeInconsistent, "")
  1097  }
  1098  
  1099  func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemErr(c *C) {
  1100  	s.bl.SetErr = fmt.Errorf("set fails")
  1101  	err := boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeSuccess)
  1102  	c.Assert(err, ErrorMatches, "set fails")
  1103  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeFailure)
  1104  	c.Assert(err, ErrorMatches, "set fails")
  1105  	err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeInconsistent)
  1106  	c.Assert(err, ErrorMatches, "set fails")
  1107  }
  1108  
  1109  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemUnset(c *C) {
  1110  	err := s.bl.SetBootVars(map[string]string{
  1111  		"recovery_system_status": "try",
  1112  		// system is unset
  1113  		"try_recovery_system": "",
  1114  	})
  1115  	c.Assert(err, IsNil)
  1116  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1117  	c.Assert(err, ErrorMatches, `try recovery system is unset but status is "try"`)
  1118  	c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
  1119  	c.Check(isTry, Equals, false)
  1120  }
  1121  
  1122  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemBogus(c *C) {
  1123  	err := s.bl.SetBootVars(map[string]string{
  1124  		"recovery_system_status": "foobar",
  1125  		"try_recovery_system":    "1234",
  1126  	})
  1127  	c.Assert(err, IsNil)
  1128  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1129  	c.Assert(err, ErrorMatches, `unexpected recovery system status "foobar"`)
  1130  	c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
  1131  	c.Check(isTry, Equals, false)
  1132  
  1133  	// errors out even if try recovery system label is unset
  1134  	err = s.bl.SetBootVars(map[string]string{
  1135  		"recovery_system_status": "no-label",
  1136  		"try_recovery_system":    "",
  1137  	})
  1138  	c.Assert(err, IsNil)
  1139  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1140  	c.Assert(err, ErrorMatches, `unexpected recovery system status "no-label"`)
  1141  	c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
  1142  	c.Check(isTry, Equals, false)
  1143  }
  1144  
  1145  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemNoTryingStatus(c *C) {
  1146  	err := s.bl.SetBootVars(map[string]string{
  1147  		"recovery_system_status": "",
  1148  		"try_recovery_system":    "",
  1149  	})
  1150  	c.Assert(err, IsNil)
  1151  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1152  	c.Assert(err, IsNil)
  1153  	c.Check(isTry, Equals, false)
  1154  
  1155  	err = s.bl.SetBootVars(map[string]string{
  1156  		// status is checked first
  1157  		"recovery_system_status": "",
  1158  		"try_recovery_system":    "1234",
  1159  	})
  1160  	c.Assert(err, IsNil)
  1161  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1162  	c.Assert(err, IsNil)
  1163  	c.Check(isTry, Equals, false)
  1164  }
  1165  
  1166  func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemSameSystem(c *C) {
  1167  	// the usual scenario
  1168  	err := s.bl.SetBootVars(map[string]string{
  1169  		"recovery_system_status": "try",
  1170  		"try_recovery_system":    "1234",
  1171  	})
  1172  	c.Assert(err, IsNil)
  1173  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1174  	c.Assert(err, IsNil)
  1175  	c.Check(isTry, Equals, true)
  1176  
  1177  	// pretend the system has already been tried
  1178  	err = s.bl.SetBootVars(map[string]string{
  1179  		"recovery_system_status": "tried",
  1180  		"try_recovery_system":    "1234",
  1181  	})
  1182  	c.Assert(err, IsNil)
  1183  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1184  	c.Assert(err, IsNil)
  1185  	c.Check(isTry, Equals, true)
  1186  }
  1187  
  1188  func (s *initramfsMarkTryRecoverySystemSuite) TestRecoverySystemSuccessDifferent(c *C) {
  1189  	// other system
  1190  	err := s.bl.SetBootVars(map[string]string{
  1191  		"recovery_system_status": "try",
  1192  		"try_recovery_system":    "9999",
  1193  	})
  1194  	c.Assert(err, IsNil)
  1195  	isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
  1196  	c.Assert(err, IsNil)
  1197  	c.Check(isTry, Equals, false)
  1198  
  1199  	// same when the other system has already been tried
  1200  	err = s.bl.SetBootVars(map[string]string{
  1201  		"recovery_system_status": "tried",
  1202  		"try_recovery_system":    "9999",
  1203  	})
  1204  	c.Assert(err, IsNil)
  1205  	isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
  1206  	c.Assert(err, IsNil)
  1207  	c.Check(isTry, Equals, false)
  1208  }