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 }