diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c b/src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c new file mode 100644 index 00000000..7f7c54b0 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c @@ -0,0 +1,416 @@ +/** @file + This is an instance of the Unit Test Persistence Lib that will utilize + the filesystem that a test application is running from to save a serialized + version of the internal test state in case the test needs to quit and restore. + + Copyright (c) Microsoft Corporation.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include <PiDxe.h> +#include <Library/UnitTestPersistenceLib.h> +#include <Library/BaseLib.h> +#include <Library/DebugLib.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/UefiBootServicesTableLib.h> +#include <Library/DevicePathLib.h> +#include <Library/ShellLib.h> +#include <Protocol/LoadedImage.h> + +#define CACHE_FILE_SUFFIX L"_Cache.dat" + +/** + Generate the device path to the cache file. + + @param[in] FrameworkHandle A pointer to the framework that is being persisted. + + @retval !NULL A pointer to the EFI_FILE protocol instance for the filesystem. + @retval NULL Filesystem could not be found or an error occurred. + +**/ +STATIC +EFI_DEVICE_PATH_PROTOCOL* +GetCacheFileDevicePath ( + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK *Framework; + EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; + CHAR16 *AppPath; + CHAR16 *CacheFilePath; + CHAR16 *TestName; + UINTN DirectorySlashOffset; + UINTN CacheFilePathLength; + EFI_DEVICE_PATH_PROTOCOL *CacheFileDevicePath; + + Framework = (UNIT_TEST_FRAMEWORK*)FrameworkHandle; + AppPath = NULL; + CacheFilePath = NULL; + TestName = NULL; + CacheFileDevicePath = NULL; + + // + // First, we need to get some information from the loaded image. + // + Status = gBS->HandleProtocol ( + gImageHandle, + &gEfiLoadedImageProtocolGuid, + (VOID**)&LoadedImage + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "%a - Failed to locate DevicePath for loaded image. %r\n", __FUNCTION__, Status)); + return NULL; + } + + // + // Before we can start, change test name from ASCII to Unicode. + // + CacheFilePathLength = AsciiStrLen (Framework->ShortTitle) + 1; + TestName = AllocatePool (CacheFilePathLength * sizeof(CHAR16)); + if (!TestName) { + goto Exit; + } + AsciiStrToUnicodeStrS (Framework->ShortTitle, TestName, CacheFilePathLength); + + // + // Now we should have the device path of the root device and a file path for the rest. + // In order to target the directory for the test application, we must process + // the file path a little. + // + // NOTE: This may not be necessary... Path processing functions exist... + // PathCleanUpDirectories (FileNameCopy); + // if (PathRemoveLastItem (FileNameCopy)) { + // + AppPath = ConvertDevicePathToText (LoadedImage->FilePath, TRUE, TRUE); // NOTE: This must be freed. + DirectorySlashOffset = StrLen (AppPath); + // + // Make sure we didn't get any weird data. + // + if (DirectorySlashOffset == 0) { + DEBUG ((DEBUG_ERROR, "%a - Weird 0-length string when processing app path.\n", __FUNCTION__)); + goto Exit; + } + + // + // Now that we know we have a decent string, let's take a deeper look. + // + do { + if (AppPath[DirectorySlashOffset] == L'\\') { + break; + } + DirectorySlashOffset--; + } while (DirectorySlashOffset > 0); + + // + // After that little maneuver, DirectorySlashOffset should be pointing at the last '\' in AppString. + // That would be the path to the parent directory that the test app is executing from. + // Let's check and make sure that's right. + // + if (AppPath[DirectorySlashOffset] != L'\\') { + DEBUG ((DEBUG_ERROR, "%a - Could not find a single directory separator in app path.\n", __FUNCTION__)); + goto Exit; + } + + // + // Now we know some things, we're ready to produce our output string, I think. + // + CacheFilePathLength = DirectorySlashOffset + 1; + CacheFilePathLength += StrLen (TestName); + CacheFilePathLength += StrLen (CACHE_FILE_SUFFIX); + CacheFilePathLength += 1; // Don't forget the NULL terminator. + CacheFilePath = AllocateZeroPool (CacheFilePathLength * sizeof (CHAR16)); + if (!CacheFilePath) { + goto Exit; + } + + // + // Let's produce our final path string, shall we? + // + StrnCpyS (CacheFilePath, CacheFilePathLength, AppPath, DirectorySlashOffset + 1); // Copy the path for the parent directory. + StrCatS (CacheFilePath, CacheFilePathLength, TestName); // Copy the base name for the test cache. + StrCatS (CacheFilePath, CacheFilePathLength, CACHE_FILE_SUFFIX); // Copy the file suffix. + + // + // Finally, try to create the device path for the thing thing. + // + CacheFileDevicePath = FileDevicePath (LoadedImage->DeviceHandle, CacheFilePath); + +Exit: + // + // Free allocated buffers. + // + if (AppPath != NULL) { + FreePool (AppPath); + } + if (CacheFilePath != NULL) { + FreePool (CacheFilePath); + } + if (TestName != NULL) { + FreePool (TestName); + } + + return CacheFileDevicePath; +} + +/** + Determines whether a persistence cache already exists for + the given framework. + + @param[in] FrameworkHandle A pointer to the framework that is being persisted. + + @retval TRUE + @retval FALSE Cache doesn't exist or an error occurred. + +**/ +BOOLEAN +EFIAPI +DoesCacheExist ( + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle + ) +{ + EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; + EFI_STATUS Status; + SHELL_FILE_HANDLE FileHandle; + + // + // NOTE: This devpath is allocated and must be freed. + // + FileDevicePath = GetCacheFileDevicePath (FrameworkHandle); + + // + // Check to see whether the file exists. If the file can be opened for + // reading, it exists. Otherwise, probably not. + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &FileHandle, + EFI_FILE_MODE_READ, + 0 + ); + if (!EFI_ERROR (Status)) { + ShellCloseFile (&FileHandle); + } + + if (FileDevicePath != NULL) { + FreePool (FileDevicePath); + } + + DEBUG ((DEBUG_VERBOSE, "%a - Returning %d\n", __FUNCTION__, !EFI_ERROR (Status))); + + return (!EFI_ERROR (Status)); +} + +/** + Will save the data associated with an internal Unit Test Framework + state in a manner that can persist a Unit Test Application quit or + even a system reboot. + + @param[in] FrameworkHandle A pointer to the framework that is being persisted. + @param[in] SaveData A pointer to the buffer containing the serialized + framework internal state. + + @retval EFI_SUCCESS Data is persisted and the test can be safely quit. + @retval Others Data is not persisted and test cannot be resumed upon exit. + +**/ +EFI_STATUS +EFIAPI +SaveUnitTestCache ( + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle, + IN UNIT_TEST_SAVE_HEADER *SaveData + ) +{ + EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; + EFI_STATUS Status; + SHELL_FILE_HANDLE FileHandle; + UINTN WriteCount; + + // + // Check the inputs for sanity. + // + if (FrameworkHandle == NULL || SaveData == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Determine the path for the cache file. + // NOTE: This devpath is allocated and must be freed. + // + FileDevicePath = GetCacheFileDevicePath (FrameworkHandle); + + // + //First lets open the file if it exists so we can delete it...This is the work around for truncation + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &FileHandle, + (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE), + 0 + ); + + if (!EFI_ERROR (Status)) { + // + // If file handle above was opened it will be closed by the delete. + // + Status = ShellDeleteFile (&FileHandle); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a failed to delete file %r\n", __FUNCTION__, Status)); + } + } + + // + // Now that we know the path to the file... let's open it for writing. + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &FileHandle, + (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE), + 0 + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, Status)); + goto Exit; + } + + // + // Write the data to the file. + // + WriteCount = SaveData->SaveStateSize; + DEBUG ((DEBUG_INFO, "%a - Writing %d bytes to file...\n", __FUNCTION__, WriteCount)); + Status = ShellWriteFile ( + FileHandle, + &WriteCount, + SaveData + ); + + if (EFI_ERROR (Status) || WriteCount != SaveData->SaveStateSize) { + DEBUG ((DEBUG_ERROR, "%a - Writing to file failed! %r\n", __FUNCTION__, Status)); + } else { + DEBUG ((DEBUG_INFO, "%a - SUCCESS!\n", __FUNCTION__)); + } + + // + // No matter what, we should probably close the file. + // + ShellCloseFile (&FileHandle); + +Exit: + if (FileDevicePath != NULL) { + FreePool (FileDevicePath); + } + + return Status; +} + +/** + Will retrieve any cached state associated with the given framework. + Will allocate a buffer to hold the loaded data. + + @param[in] FrameworkHandle A pointer to the framework that is being persisted. + @param[in] SaveData A pointer pointer that will be updated with the address + of the loaded data buffer. + + @retval EFI_SUCCESS Data has been loaded successfully and SaveData is updated + with a pointer to the buffer. + @retval Others An error has occurred and no data has been loaded. SaveData + is set to NULL. + +**/ +EFI_STATUS +EFIAPI +LoadUnitTestCache ( + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle, + OUT UNIT_TEST_SAVE_HEADER **SaveData + ) +{ + EFI_STATUS Status; + EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; + SHELL_FILE_HANDLE FileHandle; + BOOLEAN IsFileOpened; + UINT64 LargeFileSize; + UINTN FileSize; + UNIT_TEST_SAVE_HEADER *Buffer; + + IsFileOpened = FALSE; + Buffer = NULL; + + // + // Check the inputs for sanity. + // + if (FrameworkHandle == NULL || SaveData == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Determine the path for the cache file. + // NOTE: This devpath is allocated and must be freed. + // + FileDevicePath = GetCacheFileDevicePath (FrameworkHandle); + + // + // Now that we know the path to the file... let's open it for writing. + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &FileHandle, + EFI_FILE_MODE_READ, + 0 + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, Status)); + goto Exit; + } else { + IsFileOpened = TRUE; + } + + // + // Now that the file is opened, we need to determine how large a buffer we need. + // + Status = ShellGetFileSize (FileHandle, &LargeFileSize); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a - Failed to determine file size! %r\n", __FUNCTION__, Status)); + goto Exit; + } + + // + // Now that we know the size, let's allocated a buffer to hold the contents. + // + FileSize = (UINTN)LargeFileSize; // You know what... if it's too large, this lib don't care. + Buffer = AllocatePool (FileSize); + if (Buffer == NULL) { + DEBUG ((DEBUG_ERROR, "%a - Failed to allocate a pool to hold the file contents! %r\n", __FUNCTION__, Status)); + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + + // + // Finally, let's read the data. + // + Status = ShellReadFile (FileHandle, &FileSize, Buffer); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a - Failed to read the file contents! %r\n", __FUNCTION__, Status)); + } + +Exit: + // + // Free allocated buffers + // + if (FileDevicePath != NULL) { + FreePool (FileDevicePath); + } + if (IsFileOpened) { + ShellCloseFile (&FileHandle); + } + + // + // If we're returning an error, make sure + // the state is sane. + if (EFI_ERROR (Status) && Buffer != NULL) { + FreePool (Buffer); + Buffer = NULL; + } + + *SaveData = Buffer; + return Status; +} |