]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/VirtioFsDxe/SimpleFsRead.c
OvmfPkg/VirtioFsDxe: implement EFI_FILE_PROTOCOL.Read() for directories
[mirror_edk2.git] / OvmfPkg / VirtioFsDxe / SimpleFsRead.c
CommitLineData
334c13e1
LE
1/** @file\r
2 EFI_FILE_PROTOCOL.Read() member function for the Virtio Filesystem driver.\r
3\r
4 Copyright (C) 2020, Red Hat, Inc.\r
5\r
6 SPDX-License-Identifier: BSD-2-Clause-Patent\r
7**/\r
8\r
eb0b7958
LE
9#include <Library/BaseMemoryLib.h> // CopyMem()\r
10#include <Library/MemoryAllocationLib.h> // AllocatePool()\r
11\r
334c13e1
LE
12#include "VirtioFsDxe.h"\r
13\r
eb0b7958
LE
14/**\r
15 Populate a caller-allocated EFI_FILE_INFO object from\r
16 VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE.\r
17\r
18 @param[in] Dirent The entry read from the directory stream. The\r
19 caller is responsible for ensuring that\r
20 Dirent->Namelen describe valid storage.\r
21\r
22 @param[in] SingleFileInfoSize The allocated size of FileInfo.\r
23\r
24 @param[out] FileInfo The EFI_FILE_INFO object to populate. On\r
25 success, all fields in FileInfo will be\r
26 updated, setting FileInfo->Size to the\r
27 actually used size (which will not exceed\r
28 SingleFileInfoSize).\r
29\r
30 @retval EFI_SUCCESS FileInfo has been filled in.\r
31\r
32 @return Error codes propagated from\r
33 VirtioFsFuseDirentPlusToEfiFileInfo() and\r
34 VirtioFsFuseAttrToEfiFileInfo(). The contents of\r
35 FileInfo are indeterminate.\r
36**/\r
37STATIC\r
38EFI_STATUS\r
39PopulateFileInfo (\r
40 IN VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *Dirent,\r
41 IN UINTN SingleFileInfoSize,\r
42 OUT EFI_FILE_INFO *FileInfo\r
43 )\r
44{\r
45 EFI_STATUS Status;\r
46\r
47 //\r
48 // Convert the name, set the actual size.\r
49 //\r
50 FileInfo->Size = SingleFileInfoSize;\r
51 Status = VirtioFsFuseDirentPlusToEfiFileInfo (Dirent, FileInfo);\r
52 if (EFI_ERROR (Status)) {\r
53 return Status;\r
54 }\r
55 //\r
56 // Populate the scalar fields.\r
57 //\r
58 Status = VirtioFsFuseAttrToEfiFileInfo (&Dirent->AttrResp, FileInfo);\r
59 return Status;\r
60}\r
61\r
62/**\r
63 Refill the EFI_FILE_INFO cache from the directory stream.\r
64**/\r
65STATIC\r
66EFI_STATUS\r
67RefillFileInfoCache (\r
68 IN OUT VIRTIO_FS_FILE *VirtioFsFile\r
69 )\r
70{\r
71 VIRTIO_FS *VirtioFs;\r
72 EFI_STATUS Status;\r
73 VIRTIO_FS_FUSE_STATFS_RESPONSE FilesysAttr;\r
74 UINT32 DirentBufSize;\r
75 UINT8 *DirentBuf;\r
76 UINTN SingleFileInfoSize;\r
77 UINT8 *FileInfoArray;\r
78 UINT64 DirStreamCookie;\r
79 UINT64 CacheEndsAtCookie;\r
80 UINTN NumFileInfo;\r
81\r
82 //\r
83 // Allocate a DirentBuf that can receive at least\r
84 // VIRTIO_FS_FILE_MAX_FILE_INFO directory entries, based on the maximum\r
85 // filename length supported by the filesystem. Note that the multiplication\r
86 // is safe from overflow due to the VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE()\r
87 // check.\r
88 //\r
89 VirtioFs = VirtioFsFile->OwnerFs;\r
90 Status = VirtioFsFuseStatFs (VirtioFs, VirtioFsFile->NodeId, &FilesysAttr);\r
91 if (EFI_ERROR (Status)) {\r
92 return Status;\r
93 }\r
94 DirentBufSize = (UINT32)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (\r
95 FilesysAttr.Namelen);\r
96 if (DirentBufSize == 0) {\r
97 return EFI_UNSUPPORTED;\r
98 }\r
99 DirentBufSize *= VIRTIO_FS_FILE_MAX_FILE_INFO;\r
100 DirentBuf = AllocatePool (DirentBufSize);\r
101 if (DirentBuf == NULL) {\r
102 return EFI_OUT_OF_RESOURCES;\r
103 }\r
104\r
105 //\r
106 // Allocate the EFI_FILE_INFO cache. A single EFI_FILE_INFO element is sized\r
107 // accordingly to the maximum filename length supported by the filesystem.\r
108 //\r
109 // Note that the calculation below cannot overflow, due to the filename limit\r
110 // imposed by the VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE() check above. The\r
111 // calculation takes the L'\0' character that we'll need to append into\r
112 // account.\r
113 //\r
114 SingleFileInfoSize = (OFFSET_OF (EFI_FILE_INFO, FileName) +\r
115 ((UINTN)FilesysAttr.Namelen + 1) * sizeof (CHAR16));\r
116 FileInfoArray = AllocatePool (\r
117 VIRTIO_FS_FILE_MAX_FILE_INFO * SingleFileInfoSize\r
118 );\r
119 if (FileInfoArray == NULL) {\r
120 Status = EFI_OUT_OF_RESOURCES;\r
121 goto FreeDirentBuf;\r
122 }\r
123\r
124 //\r
125 // Pick up reading the directory stream where the previous cache ended.\r
126 //\r
127 DirStreamCookie = VirtioFsFile->FilePosition;\r
128 CacheEndsAtCookie = VirtioFsFile->FilePosition;\r
129 NumFileInfo = 0;\r
130 do {\r
131 UINT32 Remaining;\r
132 UINT32 Consumed;\r
133\r
134 //\r
135 // Fetch a chunk of the directory stream. The chunk may hold more entries\r
136 // than what we can fit in the cache. The chunk may also not entirely fill\r
137 // the cache, especially after filtering out entries that cannot be\r
138 // supported under UEFI (sockets, FIFOs, filenames with backslashes, etc).\r
139 //\r
140 Remaining = DirentBufSize;\r
141 Status = VirtioFsFuseReadFileOrDir (\r
142 VirtioFs,\r
143 VirtioFsFile->NodeId,\r
144 VirtioFsFile->FuseHandle,\r
145 TRUE, // IsDir\r
146 DirStreamCookie, // Offset\r
147 &Remaining, // Size\r
148 DirentBuf // Data\r
149 );\r
150 if (EFI_ERROR (Status)) {\r
151 goto FreeFileInfoArray;\r
152 }\r
153\r
154 if (Remaining == 0) {\r
155 //\r
156 // The directory stream ends.\r
157 //\r
158 break;\r
159 }\r
160\r
161 //\r
162 // Iterate over all records in DirentBuf. Primarily, forget them all.\r
163 // Secondarily, if a record proves transformable to EFI_FILE_INFO, add it\r
164 // to the EFI_FILE_INFO cache (unless the cache is full).\r
165 //\r
166 Consumed = 0;\r
167 while (Remaining >= sizeof (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE)) {\r
168 VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *Dirent;\r
169 UINT32 DirentSize;\r
170\r
171 Dirent = (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *)(DirentBuf + Consumed);\r
172 DirentSize = (UINT32)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (\r
173 Dirent->Namelen);\r
174 if (DirentSize == 0) {\r
175 //\r
176 // This means one of two things: (a) Dirent->Namelen is zero, or (b)\r
177 // (b) Dirent->Namelen is unsupportably large. (a) is just invalid for\r
178 // the Virtio Filesystem device to send, while (b) shouldn't happen\r
179 // because "FilesysAttr.Namelen" -- the maximum filename length\r
180 // supported by the filesystem -- proved acceptable above.\r
181 //\r
182 Status = EFI_PROTOCOL_ERROR;\r
183 goto FreeFileInfoArray;\r
184 }\r
185 if (DirentSize > Remaining) {\r
186 //\r
187 // Dirent->Namelen suggests that the filename byte array (plus any\r
188 // padding) are truncated. This should never happen; the Virtio\r
189 // Filesystem device is supposed to send complete entries only.\r
190 //\r
191 Status = EFI_PROTOCOL_ERROR;\r
192 goto FreeFileInfoArray;\r
193 }\r
194 if (Dirent->Namelen > FilesysAttr.Namelen) {\r
195 //\r
196 // This is possible without tripping the truncation check above, due to\r
197 // how entries are padded. The condition means that Dirent->Namelen is\r
198 // reportedly larger than the filesystem limit, without spilling into\r
199 // the next alignment bucket. Should never happen.\r
200 //\r
201 Status = EFI_PROTOCOL_ERROR;\r
202 goto FreeFileInfoArray;\r
203 }\r
204\r
205 //\r
206 // If we haven't filled the EFI_FILE_INFO cache yet, attempt transforming\r
207 // Dirent to EFI_FILE_INFO.\r
208 //\r
209 if (NumFileInfo < VIRTIO_FS_FILE_MAX_FILE_INFO) {\r
210 EFI_FILE_INFO *FileInfo;\r
211\r
212 FileInfo = (EFI_FILE_INFO *)(FileInfoArray +\r
213 (NumFileInfo * SingleFileInfoSize));\r
214 Status = PopulateFileInfo (Dirent, SingleFileInfoSize, FileInfo);\r
215 if (!EFI_ERROR (Status)) {\r
216 //\r
217 // Dirent has been transformed and cached successfully.\r
218 //\r
219 NumFileInfo++;\r
220 //\r
221 // The next time we refill the cache, restart reading the directory\r
222 // stream right after the entry that we've just transformed and\r
223 // cached.\r
224 //\r
225 CacheEndsAtCookie = Dirent->CookieForNextEntry;\r
226 }\r
227 //\r
228 // If Dirent wasn't transformable to an EFI_FILE_INFO, we'll just skip\r
229 // it.\r
230 //\r
231 }\r
232\r
233 //\r
234 // Make the Virtio Filesystem device forget the NodeId in this directory\r
235 // entry, as we'll need it no more. (The "." and ".." entries need no\r
236 // FUSE_FORGET requests, when returned by FUSE_READDIRPLUS -- and so the\r
237 // Virtio Filesystem device reports their NodeId fields as zero.)\r
238 //\r
239 if (Dirent->NodeResp.NodeId != 0) {\r
240 VirtioFsFuseForget (VirtioFs, Dirent->NodeResp.NodeId);\r
241 }\r
242\r
243 //\r
244 // Advance to the next entry in DirentBuf.\r
245 //\r
246 DirStreamCookie = Dirent->CookieForNextEntry;\r
247 Consumed += DirentSize;\r
248 Remaining -= DirentSize;\r
249 }\r
250\r
251 if (Remaining > 0) {\r
252 //\r
253 // This suggests that a VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE header was\r
254 // truncated. This should never happen; the Virtio Filesystem device is\r
255 // supposed to send complete entries only.\r
256 //\r
257 Status = EFI_PROTOCOL_ERROR;\r
258 goto FreeFileInfoArray;\r
259 }\r
260 //\r
261 // Fetch another DirentBuf from the directory stream, unless we've filled\r
262 // the EFI_FILE_INFO cache.\r
263 //\r
264 } while (NumFileInfo < VIRTIO_FS_FILE_MAX_FILE_INFO);\r
265\r
266 //\r
267 // Commit the results. (Note that the result may be an empty cache.)\r
268 //\r
269 if (VirtioFsFile->FileInfoArray != NULL) {\r
270 FreePool (VirtioFsFile->FileInfoArray);\r
271 }\r
272 VirtioFsFile->FileInfoArray = FileInfoArray;\r
273 VirtioFsFile->SingleFileInfoSize = SingleFileInfoSize;\r
274 VirtioFsFile->NumFileInfo = NumFileInfo;\r
275 VirtioFsFile->NextFileInfo = 0;\r
276 VirtioFsFile->FilePosition = CacheEndsAtCookie;\r
277\r
278 FreePool (DirentBuf);\r
279 return EFI_SUCCESS;\r
280\r
281FreeFileInfoArray:\r
282 FreePool (FileInfoArray);\r
283\r
284FreeDirentBuf:\r
285 FreePool (DirentBuf);\r
286\r
287 return Status;\r
288}\r
289\r
290/**\r
291 Read an entry from the EFI_FILE_INFO cache.\r
292**/\r
293STATIC\r
294EFI_STATUS\r
295ReadFileInfoCache (\r
296 IN OUT VIRTIO_FS_FILE *VirtioFsFile,\r
297 IN OUT UINTN *BufferSize,\r
298 OUT VOID *Buffer\r
299 )\r
300{\r
301 EFI_FILE_INFO *FileInfo;\r
302 UINTN CallerAllocated;\r
303\r
304 //\r
305 // Refill the cache if needed. If the refill doesn't produce any new\r
306 // EFI_FILE_INFO, report End of Directory, by setting (*BufferSize) to 0.\r
307 //\r
308 if (VirtioFsFile->NextFileInfo == VirtioFsFile->NumFileInfo) {\r
309 EFI_STATUS Status;\r
310\r
311 Status = RefillFileInfoCache (VirtioFsFile);\r
312 if (EFI_ERROR (Status)) {\r
313 return (Status == EFI_BUFFER_TOO_SMALL) ? EFI_DEVICE_ERROR : Status;\r
314 }\r
315 if (VirtioFsFile->NumFileInfo == 0) {\r
316 *BufferSize = 0;\r
317 return EFI_SUCCESS;\r
318 }\r
319 }\r
320 FileInfo = (EFI_FILE_INFO *)(VirtioFsFile->FileInfoArray +\r
321 (VirtioFsFile->NextFileInfo *\r
322 VirtioFsFile->SingleFileInfoSize));\r
323\r
324 //\r
325 // Check if the caller is ready to accept FileInfo. If not, we'll just\r
326 // present the required size for now.\r
327 //\r
328 // (The (UINTN) cast below is safe because FileInfo->Size has been reduced\r
329 // from VirtioFsFile->SingleFileInfoSize, in\r
330 //\r
331 // RefillFileInfoCache()\r
332 // PopulateFileInfo()\r
333 // VirtioFsFuseDirentPlusToEfiFileInfo()\r
334 //\r
335 // and VirtioFsFile->SingleFileInfoSize was computed from\r
336 // FilesysAttr.Namelen, which had been accepted by\r
337 // VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE().)\r
338 //\r
339 CallerAllocated = *BufferSize;\r
340 *BufferSize = (UINTN)FileInfo->Size;\r
341 if (CallerAllocated < *BufferSize) {\r
342 return EFI_BUFFER_TOO_SMALL;\r
343 }\r
344 //\r
345 // Output FileInfo, and remove it from the cache.\r
346 //\r
347 CopyMem (Buffer, FileInfo, *BufferSize);\r
348 VirtioFsFile->NextFileInfo++;\r
349 return EFI_SUCCESS;\r
350}\r
351\r
1c05df69
LE
352/**\r
353 Read from a regular file.\r
354**/\r
355STATIC\r
356EFI_STATUS\r
357ReadRegularFile (\r
358 IN OUT VIRTIO_FS_FILE *VirtioFsFile,\r
359 IN OUT UINTN *BufferSize,\r
360 OUT VOID *Buffer\r
361 )\r
362{\r
363 VIRTIO_FS *VirtioFs;\r
364 EFI_STATUS Status;\r
365 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;\r
366 UINTN Transferred;\r
367 UINTN Left;\r
368\r
369 VirtioFs = VirtioFsFile->OwnerFs;\r
370 //\r
371 // The UEFI spec forbids reads that start beyond the end of the file.\r
372 //\r
373 Status = VirtioFsFuseGetAttr (VirtioFs, VirtioFsFile->NodeId, &FuseAttr);\r
374 if (EFI_ERROR (Status) || VirtioFsFile->FilePosition > FuseAttr.Size) {\r
375 return EFI_DEVICE_ERROR;\r
376 }\r
377\r
378 Status = EFI_SUCCESS;\r
379 Transferred = 0;\r
380 Left = *BufferSize;\r
381 while (Left > 0) {\r
382 UINT32 ReadSize;\r
383\r
384 //\r
385 // FUSE_READ cannot express a >=4GB buffer size.\r
386 //\r
387 ReadSize = (UINT32)MIN ((UINTN)MAX_UINT32, Left);\r
388 Status = VirtioFsFuseReadFileOrDir (\r
389 VirtioFs,\r
390 VirtioFsFile->NodeId,\r
391 VirtioFsFile->FuseHandle,\r
392 FALSE, // IsDir\r
393 VirtioFsFile->FilePosition + Transferred,\r
394 &ReadSize,\r
395 (UINT8 *)Buffer + Transferred\r
396 );\r
397 if (EFI_ERROR (Status) || ReadSize == 0) {\r
398 break;\r
399 }\r
400 Transferred += ReadSize;\r
401 Left -= ReadSize;\r
402 }\r
403\r
404 *BufferSize = Transferred;\r
405 VirtioFsFile->FilePosition += Transferred;\r
406 //\r
407 // If we managed to read some data, return success. If zero bytes were\r
408 // transferred due to zero-sized buffer on input or due to EOF on first read,\r
409 // return SUCCESS. Otherwise, return the error due to which zero bytes were\r
410 // transferred.\r
411 //\r
412 return (Transferred > 0) ? EFI_SUCCESS : Status;\r
413}\r
414\r
334c13e1
LE
415EFI_STATUS\r
416EFIAPI\r
417VirtioFsSimpleFileRead (\r
418 IN EFI_FILE_PROTOCOL *This,\r
419 IN OUT UINTN *BufferSize,\r
420 OUT VOID *Buffer\r
421 )\r
422{\r
1c05df69
LE
423 VIRTIO_FS_FILE *VirtioFsFile;\r
424 EFI_STATUS Status;\r
425\r
426 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);\r
427\r
428 if (VirtioFsFile->IsDirectory) {\r
eb0b7958 429 Status = ReadFileInfoCache (VirtioFsFile, BufferSize, Buffer);\r
1c05df69
LE
430 } else {\r
431 Status = ReadRegularFile (VirtioFsFile, BufferSize, Buffer);\r
432 }\r
433 return Status;\r
334c13e1 434}\r