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 }