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