github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/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 runKernelImageEnabledKernel snap.PlaceInfo 258 runKernelImageEnabledTryKernel snap.PlaceInfo 259 260 runKernelImageMockedErrs map[string]error 261 runKernelImageMockedNumCalls map[string]int 262 263 maybePanic func(name string) 264 } 265 266 // MockExtractedRunKernelImageBootloader mocks a bootloader 267 // implementing the ExtractedRunKernelImageBootloader interface. 268 type MockExtractedRunKernelImageBootloader struct { 269 *MockBootloader 270 271 MockExtractedRunKernelImageMixin 272 } 273 274 func (b *MockExtractedRunKernelImageBootloader) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) { 275 // pick the right implementation 276 return b.MockExtractedRunKernelImageMixin.SetEnabledKernel(kernel) 277 } 278 279 func (b *MockExtractedRunKernelImageBootloader) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) { 280 // pick the right implementation 281 return b.MockExtractedRunKernelImageMixin.SetEnabledTryKernel(kernel) 282 } 283 284 // WithExtractedRunKernelImage derives a MockExtractedRunKernelImageBootloader 285 // from a base MockBootloader. 286 func (b *MockBootloader) WithExtractedRunKernelImage() *MockExtractedRunKernelImageBootloader { 287 return &MockExtractedRunKernelImageBootloader{ 288 MockBootloader: b, 289 290 MockExtractedRunKernelImageMixin: MockExtractedRunKernelImageMixin{ 291 runKernelImageMockedErrs: make(map[string]error), 292 runKernelImageMockedNumCalls: make(map[string]int), 293 maybePanic: b.maybePanic, 294 }, 295 } 296 } 297 298 // SetEnabledKernel sets the current kernel "symlink" as returned 299 // by Kernel(); returns' a restore function to set it back to what it was 300 // before. 301 func (b *MockExtractedRunKernelImageMixin) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) { 302 old := b.runKernelImageEnabledKernel 303 b.runKernelImageEnabledKernel = kernel 304 return func() { 305 b.runKernelImageEnabledKernel = old 306 } 307 } 308 309 // SetEnabledTryKernel sets the current try-kernel "symlink" as 310 // returned by TryKernel(). If set to nil, TryKernel()'s second return value 311 // will be false; returns' a restore function to set it back to what it was 312 // before. 313 func (b *MockExtractedRunKernelImageMixin) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) { 314 old := b.runKernelImageEnabledTryKernel 315 b.runKernelImageEnabledTryKernel = kernel 316 return func() { 317 b.runKernelImageEnabledTryKernel = old 318 } 319 } 320 321 // SetRunKernelImageFunctionError allows setting an error to be returned for the 322 // specified function; it returns a restore function to set it back to what it 323 // was before. 324 func (b *MockExtractedRunKernelImageMixin) SetRunKernelImageFunctionError(f string, err error) (restore func()) { 325 // check the function 326 switch f { 327 case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel": 328 old := b.runKernelImageMockedErrs[f] 329 b.runKernelImageMockedErrs[f] = err 330 return func() { 331 b.runKernelImageMockedErrs[f] = old 332 } 333 default: 334 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock error for", f)) 335 } 336 } 337 338 // GetRunKernelImageFunctionSnapCalls returns which snaps were specified during 339 // execution, in order of calls, as well as the number of calls for methods that 340 // don't take a snap to set. 341 func (b *MockExtractedRunKernelImageMixin) GetRunKernelImageFunctionSnapCalls(f string) ([]snap.PlaceInfo, int) { 342 switch f { 343 case "EnableKernel": 344 l := b.runKernelImageEnableKernelCalls 345 return l, len(l) 346 case "EnableTryKernel": 347 l := b.runKernelImageEnableTryKernelCalls 348 return l, len(l) 349 case "Kernel", "TryKernel", "DisableTryKernel": 350 return nil, b.runKernelImageMockedNumCalls[f] 351 default: 352 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to return snap args for", f)) 353 } 354 } 355 356 // EnableKernel enables the kernel; part of ExtractedRunKernelImageBootloader. 357 func (b *MockExtractedRunKernelImageMixin) EnableKernel(s snap.PlaceInfo) error { 358 b.maybePanic("EnableKernel") 359 b.runKernelImageEnableKernelCalls = append(b.runKernelImageEnableKernelCalls, s) 360 b.runKernelImageEnabledKernel = s 361 return b.runKernelImageMockedErrs["EnableKernel"] 362 } 363 364 // EnableTryKernel enables a try-kernel; part of 365 // ExtractedRunKernelImageBootloader. 366 func (b *MockExtractedRunKernelImageMixin) EnableTryKernel(s snap.PlaceInfo) error { 367 b.maybePanic("EnableTryKernel") 368 b.runKernelImageEnableTryKernelCalls = append(b.runKernelImageEnableTryKernelCalls, s) 369 b.runKernelImageEnabledTryKernel = s 370 return b.runKernelImageMockedErrs["EnableTryKernel"] 371 } 372 373 // Kernel returns the current kernel set in the bootloader; part of 374 // ExtractedRunKernelImageBootloader. 375 func (b *MockExtractedRunKernelImageMixin) Kernel() (snap.PlaceInfo, error) { 376 b.maybePanic("Kernel") 377 b.runKernelImageMockedNumCalls["Kernel"]++ 378 err := b.runKernelImageMockedErrs["Kernel"] 379 if err != nil { 380 return nil, err 381 } 382 return b.runKernelImageEnabledKernel, nil 383 } 384 385 // TryKernel returns the current kernel set in the bootloader; part of 386 // ExtractedRunKernelImageBootloader. 387 func (b *MockExtractedRunKernelImageMixin) TryKernel() (snap.PlaceInfo, error) { 388 b.maybePanic("TryKernel") 389 b.runKernelImageMockedNumCalls["TryKernel"]++ 390 err := b.runKernelImageMockedErrs["TryKernel"] 391 if err != nil { 392 return nil, err 393 } 394 if b.runKernelImageEnabledTryKernel == nil { 395 return nil, bootloader.ErrNoTryKernelRef 396 } 397 return b.runKernelImageEnabledTryKernel, nil 398 } 399 400 // DisableTryKernel removes the current try-kernel "symlink" set in the 401 // bootloader; part of ExtractedRunKernelImageBootloader. 402 func (b *MockExtractedRunKernelImageMixin) DisableTryKernel() error { 403 b.maybePanic("DisableTryKernel") 404 b.runKernelImageMockedNumCalls["DisableTryKernel"]++ 405 b.runKernelImageEnabledTryKernel = nil 406 return b.runKernelImageMockedErrs["DisableTryKernel"] 407 } 408 409 // MockTrustedAssetsMixin implements the bootloader.TrustedAssetsBootloader 410 // interface. 411 type MockTrustedAssetsMixin struct { 412 TrustedAssetsList []string 413 TrustedAssetsErr error 414 TrustedAssetsCalls int 415 416 RecoveryBootChainList []bootloader.BootFile 417 RecoveryBootChainErr error 418 BootChainList []bootloader.BootFile 419 BootChainErr error 420 421 RecoveryBootChainCalls []string 422 BootChainRunBl []bootloader.Bootloader 423 BootChainKernelPath []string 424 425 UpdateErr error 426 UpdateCalls int 427 Updated bool 428 ManagedAssetsList []string 429 StaticCommandLine string 430 CandidateStaticCommandLine string 431 CommandLineErr error 432 } 433 434 // MockTrustedAssetsBootloader mocks a bootloader implementing the 435 // bootloader.TrustedAssetsBootloader interface. 436 type MockTrustedAssetsBootloader struct { 437 *MockBootloader 438 439 MockTrustedAssetsMixin 440 } 441 442 func (b *MockBootloader) WithTrustedAssets() *MockTrustedAssetsBootloader { 443 return &MockTrustedAssetsBootloader{ 444 MockBootloader: b, 445 } 446 } 447 448 func (b *MockTrustedAssetsMixin) ManagedAssets() []string { 449 return b.ManagedAssetsList 450 } 451 452 func (b *MockTrustedAssetsMixin) UpdateBootConfig() (bool, error) { 453 b.UpdateCalls++ 454 return b.Updated, b.UpdateErr 455 } 456 457 func glueCommandLine(pieces bootloader.CommandLineComponents, staticArgs string) (string, error) { 458 if err := pieces.Validate(); err != nil { 459 return "", err 460 } 461 462 args := []string(nil) 463 extraOrFull := []string{staticArgs, pieces.ExtraArgs} 464 if pieces.FullArgs != "" { 465 extraOrFull = []string{pieces.FullArgs} 466 } 467 for _, argSet := range append([]string{pieces.ModeArg, pieces.SystemArg}, extraOrFull...) { 468 if argSet != "" { 469 args = append(args, argSet) 470 } 471 } 472 line := strings.Join(args, " ") 473 return strings.TrimSpace(line), nil 474 } 475 476 func (b *MockTrustedAssetsMixin) CommandLine(pieces bootloader.CommandLineComponents) (string, error) { 477 if b.CommandLineErr != nil { 478 return "", b.CommandLineErr 479 } 480 return glueCommandLine(pieces, b.StaticCommandLine) 481 } 482 483 func (b *MockTrustedAssetsMixin) CandidateCommandLine(pieces bootloader.CommandLineComponents) (string, error) { 484 if b.CommandLineErr != nil { 485 return "", b.CommandLineErr 486 } 487 return glueCommandLine(pieces, b.CandidateStaticCommandLine) 488 } 489 490 func (b *MockTrustedAssetsMixin) TrustedAssets() ([]string, error) { 491 b.TrustedAssetsCalls++ 492 return b.TrustedAssetsList, b.TrustedAssetsErr 493 } 494 495 func (b *MockTrustedAssetsMixin) RecoveryBootChain(kernelPath string) ([]bootloader.BootFile, error) { 496 b.RecoveryBootChainCalls = append(b.RecoveryBootChainCalls, kernelPath) 497 return b.RecoveryBootChainList, b.RecoveryBootChainErr 498 } 499 500 func (b *MockTrustedAssetsMixin) BootChain(runBl bootloader.Bootloader, kernelPath string) ([]bootloader.BootFile, error) { 501 b.BootChainRunBl = append(b.BootChainRunBl, runBl) 502 b.BootChainKernelPath = append(b.BootChainKernelPath, kernelPath) 503 return b.BootChainList, b.BootChainErr 504 } 505 506 // MockRecoveryAwareTrustedAssetsBootloader implements the 507 // bootloader.RecoveryAwareBootloader and bootloader.TrustedAssetsBootloader 508 // interfaces. 509 type MockRecoveryAwareTrustedAssetsBootloader struct { 510 *MockBootloader 511 512 MockRecoveryAwareMixin 513 MockTrustedAssetsMixin 514 } 515 516 func (b *MockBootloader) WithRecoveryAwareTrustedAssets() *MockRecoveryAwareTrustedAssetsBootloader { 517 return &MockRecoveryAwareTrustedAssetsBootloader{ 518 MockBootloader: b, 519 } 520 }