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