| /** @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; | |
| } |