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