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