github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 }