github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/bootloader/bootloadertest/bootloadertest.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package bootloadertest
    21  
    22  import (
    23  	"fmt"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/bootloader"
    28  	"github.com/snapcore/snapd/snap"
    29  )
    30  
    31  // MockBootloader mocks the bootloader interface and records all
    32  // set/get calls.
    33  type MockBootloader struct {
    34  	BootVars         map[string]string
    35  	SetBootVarsCalls int
    36  	SetErr           error
    37  	GetErr           error
    38  
    39  	name    string
    40  	bootdir string
    41  
    42  	ExtractKernelAssetsCalls []snap.PlaceInfo
    43  	RemoveKernelAssetsCalls  []snap.PlaceInfo
    44  
    45  	InstallBootConfigCalled []string
    46  	InstallBootConfigResult bool
    47  	InstallBootConfigErr    error
    48  
    49  	enabledKernel    snap.PlaceInfo
    50  	enabledTryKernel snap.PlaceInfo
    51  
    52  	panicMethods map[string]bool
    53  }
    54  
    55  // ensure MockBootloader(s) implement the Bootloader interface
    56  var _ bootloader.Bootloader = (*MockBootloader)(nil)
    57  var _ bootloader.RecoveryAwareBootloader = (*MockRecoveryAwareBootloader)(nil)
    58  var _ bootloader.TrustedAssetsBootloader = (*MockTrustedAssetsBootloader)(nil)
    59  var _ bootloader.ExtractedRunKernelImageBootloader = (*MockExtractedRunKernelImageBootloader)(nil)
    60  var _ bootloader.ExtractedRecoveryKernelImageBootloader = (*MockExtractedRecoveryKernelImageBootloader)(nil)
    61  
    62  func Mock(name, bootdir string) *MockBootloader {
    63  	return &MockBootloader{
    64  		name:    name,
    65  		bootdir: bootdir,
    66  
    67  		BootVars: make(map[string]string),
    68  
    69  		panicMethods: make(map[string]bool),
    70  	}
    71  }
    72  
    73  func (b *MockBootloader) maybePanic(which string) {
    74  	if b.panicMethods[which] {
    75  		panic(fmt.Sprintf("mocked reboot panic in %s", which))
    76  	}
    77  }
    78  
    79  func (b *MockBootloader) SetBootVars(values map[string]string) error {
    80  	b.maybePanic("SetBootVars")
    81  	b.SetBootVarsCalls++
    82  	for k, v := range values {
    83  		b.BootVars[k] = v
    84  	}
    85  	return b.SetErr
    86  }
    87  
    88  func (b *MockBootloader) GetBootVars(keys ...string) (map[string]string, error) {
    89  	b.maybePanic("GetBootVars")
    90  
    91  	out := map[string]string{}
    92  	for _, k := range keys {
    93  		out[k] = b.BootVars[k]
    94  	}
    95  
    96  	return out, b.GetErr
    97  }
    98  
    99  func (b *MockBootloader) Name() string {
   100  	return b.name
   101  }
   102  
   103  func (b *MockBootloader) ConfigFile() string {
   104  	return filepath.Join(b.bootdir, "mockboot/mockboot.cfg")
   105  }
   106  
   107  func (b *MockBootloader) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
   108  	b.ExtractKernelAssetsCalls = append(b.ExtractKernelAssetsCalls, s)
   109  	return nil
   110  }
   111  
   112  func (b *MockBootloader) RemoveKernelAssets(s snap.PlaceInfo) error {
   113  	b.RemoveKernelAssetsCalls = append(b.RemoveKernelAssetsCalls, s)
   114  	return nil
   115  }
   116  
   117  func (b *MockBootloader) SetEnabledKernel(s snap.PlaceInfo) (restore func()) {
   118  	oldSn := b.enabledTryKernel
   119  	oldVar := b.BootVars["snap_kernel"]
   120  	b.enabledKernel = s
   121  	b.BootVars["snap_kernel"] = s.Filename()
   122  	return func() {
   123  		b.BootVars["snap_kernel"] = oldVar
   124  		b.enabledKernel = oldSn
   125  	}
   126  }
   127  
   128  func (b *MockBootloader) SetEnabledTryKernel(s snap.PlaceInfo) (restore func()) {
   129  	oldSn := b.enabledTryKernel
   130  	oldVar := b.BootVars["snap_try_kernel"]
   131  	b.enabledTryKernel = s
   132  	b.BootVars["snap_try_kernel"] = s.Filename()
   133  	return func() {
   134  		b.BootVars["snap_try_kernel"] = oldVar
   135  		b.enabledTryKernel = oldSn
   136  	}
   137  }
   138  
   139  // InstallBootConfig installs the boot config in the gadget directory to the
   140  // mock bootloader's root directory.
   141  func (b *MockBootloader) InstallBootConfig(gadgetDir string, opts *bootloader.Options) (bool, error) {
   142  	b.InstallBootConfigCalled = append(b.InstallBootConfigCalled, gadgetDir)
   143  	return b.InstallBootConfigResult, b.InstallBootConfigErr
   144  }
   145  
   146  // MockRecoveryAwareBootloader mocks a bootloader implementing the
   147  // RecoveryAware interface.
   148  type MockRecoveryAwareBootloader struct {
   149  	*MockBootloader
   150  
   151  	RecoverySystemDir      string
   152  	RecoverySystemBootVars map[string]string
   153  }
   154  
   155  type ExtractedRecoveryKernelCall struct {
   156  	RecoverySystemDir string
   157  	S                 snap.PlaceInfo
   158  }
   159  
   160  // MockExtractedRecoveryKernelImageBootloader mocks a bootloader implementing
   161  // the ExtractedRecoveryKernelImage interface.
   162  type MockExtractedRecoveryKernelImageBootloader struct {
   163  	*MockBootloader
   164  
   165  	ExtractRecoveryKernelAssetsCalls []ExtractedRecoveryKernelCall
   166  }
   167  
   168  // ExtractedRecoveryKernelImage derives a MockRecoveryAwareBootloader from a base
   169  // MockBootloader.
   170  func (b *MockBootloader) ExtractedRecoveryKernelImage() *MockExtractedRecoveryKernelImageBootloader {
   171  	return &MockExtractedRecoveryKernelImageBootloader{MockBootloader: b}
   172  }
   173  
   174  // ExtractRecoveryKernelAssets extracts the kernel assets for the provided
   175  // kernel snap into the specified recovery system dir; part of
   176  // RecoveryAwareBootloader.
   177  func (b *MockExtractedRecoveryKernelImageBootloader) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error {
   178  	if recoverySystemDir == "" {
   179  		panic("MockBootloader.ExtractRecoveryKernelAssets called without recoverySystemDir")
   180  	}
   181  
   182  	b.ExtractRecoveryKernelAssetsCalls = append(
   183  		b.ExtractRecoveryKernelAssetsCalls,
   184  		ExtractedRecoveryKernelCall{
   185  			S:                 s,
   186  			RecoverySystemDir: recoverySystemDir},
   187  	)
   188  	return nil
   189  }
   190  
   191  // RecoveryAware derives a MockRecoveryAwareBootloader from a base
   192  // MockBootloader.
   193  func (b *MockBootloader) RecoveryAware() *MockRecoveryAwareBootloader {
   194  	return &MockRecoveryAwareBootloader{MockBootloader: b}
   195  }
   196  
   197  // SetRecoverySystemEnv sets the recovery system environment bootloader
   198  // variables; part of RecoveryAwareBootloader.
   199  func (b *MockRecoveryAwareBootloader) SetRecoverySystemEnv(recoverySystemDir string, blVars map[string]string) error {
   200  	if recoverySystemDir == "" {
   201  		panic("MockBootloader.SetRecoverySystemEnv called without recoverySystemDir")
   202  	}
   203  	b.RecoverySystemDir = recoverySystemDir
   204  	b.RecoverySystemBootVars = blVars
   205  	return nil
   206  }
   207  
   208  // GetRecoverySystemEnv gets the recovery system environment bootloader
   209  // variables; part of RecoveryAwareBootloader.
   210  func (b *MockRecoveryAwareBootloader) GetRecoverySystemEnv(recoverySystemDir, key string) (string, error) {
   211  	if recoverySystemDir == "" {
   212  		panic("MockBootloader.GetRecoverySystemEnv called without recoverySystemDir")
   213  	}
   214  	b.RecoverySystemDir = recoverySystemDir
   215  	return b.RecoverySystemBootVars[key], nil
   216  }
   217  
   218  // MockExtractedRunKernelImageBootloader mocks a bootloader
   219  // implementing the ExtractedRunKernelImageBootloader interface.
   220  type MockExtractedRunKernelImageBootloader struct {
   221  	*MockBootloader
   222  
   223  	runKernelImageEnableKernelCalls     []snap.PlaceInfo
   224  	runKernelImageEnableTryKernelCalls  []snap.PlaceInfo
   225  	runKernelImageDisableTryKernelCalls []snap.PlaceInfo
   226  	runKernelImageEnabledKernel         snap.PlaceInfo
   227  	runKernelImageEnabledTryKernel      snap.PlaceInfo
   228  
   229  	runKernelImageMockedErrs     map[string]error
   230  	runKernelImageMockedNumCalls map[string]int
   231  }
   232  
   233  // WithExtractedRunKernelImage derives a MockExtractedRunKernelImageBootloader
   234  // from a base MockBootloader.
   235  func (b *MockBootloader) WithExtractedRunKernelImage() *MockExtractedRunKernelImageBootloader {
   236  	return &MockExtractedRunKernelImageBootloader{
   237  		MockBootloader: b,
   238  
   239  		runKernelImageMockedErrs:     make(map[string]error),
   240  		runKernelImageMockedNumCalls: make(map[string]int),
   241  	}
   242  }
   243  
   244  // SetEnabledKernel sets the current kernel "symlink" as returned
   245  // by Kernel(); returns' a restore function to set it back to what it was
   246  // before.
   247  func (b *MockExtractedRunKernelImageBootloader) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) {
   248  	old := b.runKernelImageEnabledKernel
   249  	b.runKernelImageEnabledKernel = kernel
   250  	return func() {
   251  		b.runKernelImageEnabledKernel = old
   252  	}
   253  }
   254  
   255  // SetEnabledTryKernel sets the current try-kernel "symlink" as
   256  // returned by TryKernel(). If set to nil, TryKernel()'s second return value
   257  // will be false; returns' a restore function to set it back to what it was
   258  // before.
   259  func (b *MockExtractedRunKernelImageBootloader) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) {
   260  	old := b.runKernelImageEnabledTryKernel
   261  	b.runKernelImageEnabledTryKernel = kernel
   262  	return func() {
   263  		b.runKernelImageEnabledTryKernel = old
   264  	}
   265  }
   266  
   267  // SetRunKernelImageFunctionError allows setting an error to be returned for the
   268  // specified function; it returns a restore function to set it back to what it
   269  // was before.
   270  func (b *MockExtractedRunKernelImageBootloader) SetRunKernelImageFunctionError(f string, err error) (restore func()) {
   271  	// check the function
   272  	switch f {
   273  	case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel":
   274  		old := b.runKernelImageMockedErrs[f]
   275  		b.runKernelImageMockedErrs[f] = err
   276  		return func() {
   277  			b.runKernelImageMockedErrs[f] = old
   278  		}
   279  	default:
   280  		panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock error for", f))
   281  	}
   282  }
   283  
   284  // SetRunKernelImagePanic allows setting any method in the
   285  // ExtractedRunKernelImageBootloader interface on
   286  // MockExtractedRunKernelImageBootloader to panic instead of
   287  // returning. This allows one to test what would happen if the system
   288  // was rebooted during execution of a particular
   289  // function. Specifically, the panic will be done immediately entering
   290  // the function so setting SetBootVars to panic will emulate a reboot
   291  // before any boot vars are set persistently
   292  func (b *MockExtractedRunKernelImageBootloader) SetRunKernelImagePanic(f string) (restore func()) {
   293  	switch f {
   294  	case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel", "SetBootVars", "GetBootVars":
   295  		old := b.panicMethods[f]
   296  		b.panicMethods[f] = true
   297  		return func() {
   298  			b.panicMethods[f] = old
   299  		}
   300  	default:
   301  		panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock reboot via panic for", f))
   302  	}
   303  }
   304  
   305  // GetRunKernelImageFunctionSnapCalls returns which snaps were specified during
   306  // execution, in order of calls, as well as the number of calls for methods that
   307  // don't take a snap to set.
   308  func (b *MockExtractedRunKernelImageBootloader) GetRunKernelImageFunctionSnapCalls(f string) ([]snap.PlaceInfo, int) {
   309  	switch f {
   310  	case "EnableKernel":
   311  		l := b.runKernelImageEnableKernelCalls
   312  		return l, len(l)
   313  	case "EnableTryKernel":
   314  		l := b.runKernelImageEnableTryKernelCalls
   315  		return l, len(l)
   316  	case "Kernel", "TryKernel", "DisableTryKernel":
   317  		return nil, b.runKernelImageMockedNumCalls[f]
   318  	default:
   319  		panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to return snap args for", f))
   320  	}
   321  }
   322  
   323  // EnableKernel enables the kernel; part of ExtractedRunKernelImageBootloader.
   324  func (b *MockExtractedRunKernelImageBootloader) EnableKernel(s snap.PlaceInfo) error {
   325  	b.maybePanic("EnableKernel")
   326  	b.runKernelImageEnableKernelCalls = append(b.runKernelImageEnableKernelCalls, s)
   327  	b.runKernelImageEnabledKernel = s
   328  	return b.runKernelImageMockedErrs["EnableKernel"]
   329  }
   330  
   331  // EnableTryKernel enables a try-kernel; part of
   332  // ExtractedRunKernelImageBootloader.
   333  func (b *MockExtractedRunKernelImageBootloader) EnableTryKernel(s snap.PlaceInfo) error {
   334  	b.maybePanic("EnableTryKernel")
   335  	b.runKernelImageEnableTryKernelCalls = append(b.runKernelImageEnableTryKernelCalls, s)
   336  	b.runKernelImageEnabledTryKernel = s
   337  	return b.runKernelImageMockedErrs["EnableTryKernel"]
   338  }
   339  
   340  // Kernel returns the current kernel set in the bootloader; part of
   341  // ExtractedRunKernelImageBootloader.
   342  func (b *MockExtractedRunKernelImageBootloader) Kernel() (snap.PlaceInfo, error) {
   343  	b.maybePanic("Kernel")
   344  	b.runKernelImageMockedNumCalls["Kernel"]++
   345  	err := b.runKernelImageMockedErrs["Kernel"]
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  	return b.runKernelImageEnabledKernel, nil
   350  }
   351  
   352  // TryKernel returns the current kernel set in the bootloader; part of
   353  // ExtractedRunKernelImageBootloader.
   354  func (b *MockExtractedRunKernelImageBootloader) TryKernel() (snap.PlaceInfo, error) {
   355  	b.maybePanic("TryKernel")
   356  	b.runKernelImageMockedNumCalls["TryKernel"]++
   357  	err := b.runKernelImageMockedErrs["TryKernel"]
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	if b.runKernelImageEnabledTryKernel == nil {
   362  		return nil, bootloader.ErrNoTryKernelRef
   363  	}
   364  	return b.runKernelImageEnabledTryKernel, nil
   365  }
   366  
   367  // DisableTryKernel removes the current try-kernel "symlink" set in the
   368  // bootloader; part of ExtractedRunKernelImageBootloader.
   369  func (b *MockExtractedRunKernelImageBootloader) DisableTryKernel() error {
   370  	b.maybePanic("DisableTryKernel")
   371  	b.runKernelImageMockedNumCalls["DisableTryKernel"]++
   372  	b.runKernelImageEnabledTryKernel = nil
   373  	return b.runKernelImageMockedErrs["DisableTryKernel"]
   374  }
   375  
   376  // MockTrustedAssetsBootloader mocks a bootloader implementing the
   377  // bootloader.TrustedAssetsBootloader interface.
   378  type MockTrustedAssetsBootloader struct {
   379  	*MockBootloader
   380  
   381  	TrustedAssetsList  []string
   382  	TrustedAssetsErr   error
   383  	TrustedAssetsCalls int
   384  
   385  	RecoveryBootChainList []bootloader.BootFile
   386  	RecoveryBootChainErr  error
   387  	BootChainList         []bootloader.BootFile
   388  	BootChainErr          error
   389  
   390  	RecoveryBootChainCalls []string
   391  	BootChainRunBl         []bootloader.Bootloader
   392  	BootChainKernelPath    []string
   393  
   394  	UpdateErr                  error
   395  	UpdateCalls                int
   396  	ManagedAssetsList          []string
   397  	StaticCommandLine          string
   398  	CandidateStaticCommandLine string
   399  	CommandLineErr             error
   400  }
   401  
   402  func (b *MockBootloader) WithTrustedAssets() *MockTrustedAssetsBootloader {
   403  	return &MockTrustedAssetsBootloader{
   404  		MockBootloader: b,
   405  	}
   406  }
   407  
   408  func (b *MockTrustedAssetsBootloader) ManagedAssets() []string {
   409  	return b.ManagedAssetsList
   410  }
   411  
   412  func (b *MockTrustedAssetsBootloader) UpdateBootConfig(opts *bootloader.Options) error {
   413  	b.UpdateCalls++
   414  	return b.UpdateErr
   415  }
   416  
   417  func glueCommandLine(modeArg, systemArg, staticArgs, extraArgs string) string {
   418  	args := []string(nil)
   419  	for _, argSet := range []string{modeArg, systemArg, staticArgs, extraArgs} {
   420  		if argSet != "" {
   421  			args = append(args, argSet)
   422  		}
   423  	}
   424  	line := strings.Join(args, " ")
   425  	return strings.TrimSpace(line)
   426  }
   427  
   428  func (b *MockTrustedAssetsBootloader) CommandLine(modeArg, systemArg, extraArgs string) (string, error) {
   429  	if b.CommandLineErr != nil {
   430  		return "", b.CommandLineErr
   431  	}
   432  	return glueCommandLine(modeArg, systemArg, b.StaticCommandLine, extraArgs), nil
   433  }
   434  
   435  func (b *MockTrustedAssetsBootloader) CandidateCommandLine(modeArg, systemArg, extraArgs string) (string, error) {
   436  	if b.CommandLineErr != nil {
   437  		return "", b.CommandLineErr
   438  	}
   439  	return glueCommandLine(modeArg, systemArg, b.CandidateStaticCommandLine, extraArgs), nil
   440  }
   441  
   442  func (b *MockTrustedAssetsBootloader) TrustedAssets() ([]string, error) {
   443  	b.TrustedAssetsCalls++
   444  	return b.TrustedAssetsList, b.TrustedAssetsErr
   445  }
   446  
   447  func (b *MockTrustedAssetsBootloader) RecoveryBootChain(kernelPath string) ([]bootloader.BootFile, error) {
   448  	b.RecoveryBootChainCalls = append(b.RecoveryBootChainCalls, kernelPath)
   449  	return b.RecoveryBootChainList, b.RecoveryBootChainErr
   450  }
   451  
   452  func (b *MockTrustedAssetsBootloader) BootChain(runBl bootloader.Bootloader, kernelPath string) ([]bootloader.BootFile, error) {
   453  	b.BootChainRunBl = append(b.BootChainRunBl, runBl)
   454  	b.BootChainKernelPath = append(b.BootChainKernelPath, kernelPath)
   455  	return b.BootChainList, b.BootChainErr
   456  }