]> git.proxmox.com Git - mirror_edk2.git/blob - UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c
UnitTestFrameworkPkg: Modify APIs in UnitTestPersistenceLib
[mirror_edk2.git] / UnitTestFrameworkPkg / Library / UnitTestPersistenceLibSimpleFileSystem / UnitTestPersistenceLibSimpleFileSystem.c
1 /** @file
2 This is an instance of the Unit Test Persistence Lib that will utilize
3 the filesystem that a test application is running from to save a serialized
4 version of the internal test state in case the test needs to quit and restore.
5
6 Copyright (c) Microsoft Corporation.<BR>
7 Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
8 SPDX-License-Identifier: BSD-2-Clause-Patent
9 **/
10
11 #include <PiDxe.h>
12 #include <Library/UnitTestPersistenceLib.h>
13 #include <Library/BaseLib.h>
14 #include <Library/DebugLib.h>
15 #include <Library/MemoryAllocationLib.h>
16 #include <Library/UefiBootServicesTableLib.h>
17 #include <Library/DevicePathLib.h>
18 #include <Library/ShellLib.h>
19 #include <Protocol/LoadedImage.h>
20 #include <UnitTestFrameworkTypes.h>
21
22 #define CACHE_FILE_SUFFIX L"_Cache.dat"
23
24 /**
25 Generate the device path to the cache file.
26
27 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
28
29 @retval !NULL A pointer to the EFI_FILE protocol instance for the filesystem.
30 @retval NULL Filesystem could not be found or an error occurred.
31
32 **/
33 STATIC
34 EFI_DEVICE_PATH_PROTOCOL *
35 GetCacheFileDevicePath (
36 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle
37 )
38 {
39 EFI_STATUS Status;
40 UNIT_TEST_FRAMEWORK *Framework;
41 EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
42 CHAR16 *AppPath;
43 CHAR16 *CacheFilePath;
44 CHAR16 *TestName;
45 UINTN DirectorySlashOffset;
46 UINTN CacheFilePathLength;
47 EFI_DEVICE_PATH_PROTOCOL *CacheFileDevicePath;
48
49 Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;
50 AppPath = NULL;
51 CacheFilePath = NULL;
52 TestName = NULL;
53 CacheFileDevicePath = NULL;
54
55 //
56 // First, we need to get some information from the loaded image.
57 //
58 Status = gBS->HandleProtocol (
59 gImageHandle,
60 &gEfiLoadedImageProtocolGuid,
61 (VOID **)&LoadedImage
62 );
63 if (EFI_ERROR (Status)) {
64 DEBUG ((DEBUG_WARN, "%a - Failed to locate DevicePath for loaded image. %r\n", __FUNCTION__, Status));
65 return NULL;
66 }
67
68 //
69 // Before we can start, change test name from ASCII to Unicode.
70 //
71 CacheFilePathLength = AsciiStrLen (Framework->ShortTitle) + 1;
72 TestName = AllocatePool (CacheFilePathLength * sizeof (CHAR16));
73 if (!TestName) {
74 goto Exit;
75 }
76
77 AsciiStrToUnicodeStrS (Framework->ShortTitle, TestName, CacheFilePathLength);
78
79 //
80 // Now we should have the device path of the root device and a file path for the rest.
81 // In order to target the directory for the test application, we must process
82 // the file path a little.
83 //
84 // NOTE: This may not be necessary... Path processing functions exist...
85 // PathCleanUpDirectories (FileNameCopy);
86 // if (PathRemoveLastItem (FileNameCopy)) {
87 //
88 AppPath = ConvertDevicePathToText (LoadedImage->FilePath, TRUE, TRUE); // NOTE: This must be freed.
89 DirectorySlashOffset = StrLen (AppPath);
90 //
91 // Make sure we didn't get any weird data.
92 //
93 if (DirectorySlashOffset == 0) {
94 DEBUG ((DEBUG_ERROR, "%a - Weird 0-length string when processing app path.\n", __FUNCTION__));
95 goto Exit;
96 }
97
98 //
99 // Now that we know we have a decent string, let's take a deeper look.
100 //
101 do {
102 if (AppPath[DirectorySlashOffset] == L'\\') {
103 break;
104 }
105
106 DirectorySlashOffset--;
107 } while (DirectorySlashOffset > 0);
108
109 //
110 // After that little maneuver, DirectorySlashOffset should be pointing at the last '\' in AppString.
111 // That would be the path to the parent directory that the test app is executing from.
112 // Let's check and make sure that's right.
113 //
114 if (AppPath[DirectorySlashOffset] != L'\\') {
115 DEBUG ((DEBUG_ERROR, "%a - Could not find a single directory separator in app path.\n", __FUNCTION__));
116 goto Exit;
117 }
118
119 //
120 // Now we know some things, we're ready to produce our output string, I think.
121 //
122 CacheFilePathLength = DirectorySlashOffset + 1;
123 CacheFilePathLength += StrLen (TestName);
124 CacheFilePathLength += StrLen (CACHE_FILE_SUFFIX);
125 CacheFilePathLength += 1; // Don't forget the NULL terminator.
126 CacheFilePath = AllocateZeroPool (CacheFilePathLength * sizeof (CHAR16));
127 if (!CacheFilePath) {
128 goto Exit;
129 }
130
131 //
132 // Let's produce our final path string, shall we?
133 //
134 StrnCpyS (CacheFilePath, CacheFilePathLength, AppPath, DirectorySlashOffset + 1); // Copy the path for the parent directory.
135 StrCatS (CacheFilePath, CacheFilePathLength, TestName); // Copy the base name for the test cache.
136 StrCatS (CacheFilePath, CacheFilePathLength, CACHE_FILE_SUFFIX); // Copy the file suffix.
137
138 //
139 // Finally, try to create the device path for the thing thing.
140 //
141 CacheFileDevicePath = FileDevicePath (LoadedImage->DeviceHandle, CacheFilePath);
142
143 Exit:
144 //
145 // Free allocated buffers.
146 //
147 if (AppPath != NULL) {
148 FreePool (AppPath);
149 }
150
151 if (CacheFilePath != NULL) {
152 FreePool (CacheFilePath);
153 }
154
155 if (TestName != NULL) {
156 FreePool (TestName);
157 }
158
159 return CacheFileDevicePath;
160 }
161
162 /**
163 Determines whether a persistence cache already exists for
164 the given framework.
165
166 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
167
168 @retval TRUE
169 @retval FALSE Cache doesn't exist or an error occurred.
170
171 **/
172 BOOLEAN
173 EFIAPI
174 DoesCacheExist (
175 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle
176 )
177 {
178 EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
179 EFI_STATUS Status;
180 SHELL_FILE_HANDLE FileHandle;
181
182 //
183 // NOTE: This devpath is allocated and must be freed.
184 //
185 FileDevicePath = GetCacheFileDevicePath (FrameworkHandle);
186
187 //
188 // Check to see whether the file exists. If the file can be opened for
189 // reading, it exists. Otherwise, probably not.
190 //
191 Status = ShellOpenFileByDevicePath (
192 &FileDevicePath,
193 &FileHandle,
194 EFI_FILE_MODE_READ,
195 0
196 );
197 if (!EFI_ERROR (Status)) {
198 ShellCloseFile (&FileHandle);
199 }
200
201 if (FileDevicePath != NULL) {
202 FreePool (FileDevicePath);
203 }
204
205 DEBUG ((DEBUG_VERBOSE, "%a - Returning %d\n", __FUNCTION__, !EFI_ERROR (Status)));
206
207 return (!EFI_ERROR (Status));
208 }
209
210 /**
211 Will save the data associated with an internal Unit Test Framework
212 state in a manner that can persist a Unit Test Application quit or
213 even a system reboot.
214
215 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
216 @param[in] SaveData A pointer to the buffer containing the serialized
217 framework internal state.
218 @param[in] SaveStateSize The size of SaveData in bytes.
219
220 @retval EFI_SUCCESS Data is persisted and the test can be safely quit.
221 @retval Others Data is not persisted and test cannot be resumed upon exit.
222
223 **/
224 EFI_STATUS
225 EFIAPI
226 SaveUnitTestCache (
227 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle,
228 IN VOID *SaveData,
229 IN UINTN SaveStateSize
230 )
231 {
232 EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
233 EFI_STATUS Status;
234 SHELL_FILE_HANDLE FileHandle;
235 UINTN WriteCount;
236
237 //
238 // Check the inputs for sanity.
239 //
240 if ((FrameworkHandle == NULL) || (SaveData == NULL)) {
241 return EFI_INVALID_PARAMETER;
242 }
243
244 //
245 // Determine the path for the cache file.
246 // NOTE: This devpath is allocated and must be freed.
247 //
248 FileDevicePath = GetCacheFileDevicePath (FrameworkHandle);
249
250 //
251 // First lets open the file if it exists so we can delete it...This is the work around for truncation
252 //
253 Status = ShellOpenFileByDevicePath (
254 &FileDevicePath,
255 &FileHandle,
256 (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE),
257 0
258 );
259
260 if (!EFI_ERROR (Status)) {
261 //
262 // If file handle above was opened it will be closed by the delete.
263 //
264 Status = ShellDeleteFile (&FileHandle);
265 if (EFI_ERROR (Status)) {
266 DEBUG ((DEBUG_ERROR, "%a failed to delete file %r\n", __FUNCTION__, Status));
267 }
268 }
269
270 //
271 // Now that we know the path to the file... let's open it for writing.
272 //
273 Status = ShellOpenFileByDevicePath (
274 &FileDevicePath,
275 &FileHandle,
276 (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE),
277 0
278 );
279 if (EFI_ERROR (Status)) {
280 DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, Status));
281 goto Exit;
282 }
283
284 //
285 // Write the data to the file.
286 //
287 WriteCount = SaveStateSize;
288 DEBUG ((DEBUG_INFO, "%a - Writing %d bytes to file...\n", __FUNCTION__, WriteCount));
289 Status = ShellWriteFile (
290 FileHandle,
291 &WriteCount,
292 SaveData
293 );
294
295 if (EFI_ERROR (Status) || (WriteCount != SaveStateSize)) {
296 DEBUG ((DEBUG_ERROR, "%a - Writing to file failed! %r\n", __FUNCTION__, Status));
297 } else {
298 DEBUG ((DEBUG_INFO, "%a - SUCCESS!\n", __FUNCTION__));
299 }
300
301 //
302 // No matter what, we should probably close the file.
303 //
304 ShellCloseFile (&FileHandle);
305
306 Exit:
307 if (FileDevicePath != NULL) {
308 FreePool (FileDevicePath);
309 }
310
311 return Status;
312 }
313
314 /**
315 Will retrieve any cached state associated with the given framework.
316 Will allocate a buffer to hold the loaded data.
317
318 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
319 @param[out] SaveData A pointer pointer that will be updated with the address
320 of the loaded data buffer.
321 @param[out] SaveStateSize Return the size of SaveData in bytes.
322
323 @retval EFI_SUCCESS Data has been loaded successfully and SaveData is updated
324 with a pointer to the buffer.
325 @retval Others An error has occurred and no data has been loaded. SaveData
326 is set to NULL.
327
328 **/
329 EFI_STATUS
330 EFIAPI
331 LoadUnitTestCache (
332 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle,
333 OUT VOID **SaveData,
334 OUT UINTN *SaveStateSize
335 )
336 {
337 EFI_STATUS Status;
338 EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
339 SHELL_FILE_HANDLE FileHandle;
340 BOOLEAN IsFileOpened;
341 UINT64 LargeFileSize;
342 UINTN FileSize;
343 VOID *Buffer;
344
345 IsFileOpened = FALSE;
346 Buffer = NULL;
347
348 //
349 // Check the inputs for sanity.
350 //
351 if ((FrameworkHandle == NULL) || (SaveData == NULL)) {
352 return EFI_INVALID_PARAMETER;
353 }
354
355 //
356 // Determine the path for the cache file.
357 // NOTE: This devpath is allocated and must be freed.
358 //
359 FileDevicePath = GetCacheFileDevicePath (FrameworkHandle);
360
361 //
362 // Now that we know the path to the file... let's open it for writing.
363 //
364 Status = ShellOpenFileByDevicePath (
365 &FileDevicePath,
366 &FileHandle,
367 EFI_FILE_MODE_READ,
368 0
369 );
370 if (EFI_ERROR (Status)) {
371 DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, Status));
372 goto Exit;
373 } else {
374 IsFileOpened = TRUE;
375 }
376
377 //
378 // Now that the file is opened, we need to determine how large a buffer we need.
379 //
380 Status = ShellGetFileSize (FileHandle, &LargeFileSize);
381 if (EFI_ERROR (Status)) {
382 DEBUG ((DEBUG_ERROR, "%a - Failed to determine file size! %r\n", __FUNCTION__, Status));
383 goto Exit;
384 }
385
386 //
387 // Now that we know the size, let's allocated a buffer to hold the contents.
388 //
389 FileSize = (UINTN)LargeFileSize; // You know what... if it's too large, this lib don't care.
390 *SaveStateSize = FileSize;
391 Buffer = AllocatePool (FileSize);
392 if (Buffer == NULL) {
393 DEBUG ((DEBUG_ERROR, "%a - Failed to allocate a pool to hold the file contents! %r\n", __FUNCTION__, Status));
394 Status = EFI_OUT_OF_RESOURCES;
395 goto Exit;
396 }
397
398 //
399 // Finally, let's read the data.
400 //
401 Status = ShellReadFile (FileHandle, &FileSize, Buffer);
402 if (EFI_ERROR (Status)) {
403 DEBUG ((DEBUG_ERROR, "%a - Failed to read the file contents! %r\n", __FUNCTION__, Status));
404 }
405
406 Exit:
407 //
408 // Free allocated buffers
409 //
410 if (FileDevicePath != NULL) {
411 FreePool (FileDevicePath);
412 }
413
414 if (IsFileOpened) {
415 ShellCloseFile (&FileHandle);
416 }
417
418 //
419 // If we're returning an error, make sure
420 // the state is sane.
421 if (EFI_ERROR (Status) && (Buffer != NULL)) {
422 FreePool (Buffer);
423 Buffer = NULL;
424 }
425
426 *SaveData = Buffer;
427 return Status;
428 }