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