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