2 EFI_FILE_PROTOCOL.Read() member function for the Virtio Filesystem driver.
4 Copyright (C) 2020, Red Hat, Inc.
6 SPDX-License-Identifier: BSD-2-Clause-Patent
9 #include <Library/BaseMemoryLib.h> // CopyMem()
10 #include <Library/MemoryAllocationLib.h> // AllocatePool()
12 #include "VirtioFsDxe.h"
15 Populate a caller-allocated EFI_FILE_INFO object from
16 VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE.
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.
22 @param[in] SingleFileInfoSize The allocated size of FileInfo.
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
30 @retval EFI_SUCCESS FileInfo has been filled in.
32 @return Error codes propagated from
33 VirtioFsFuseDirentPlusToEfiFileInfo() and
34 VirtioFsFuseAttrToEfiFileInfo(). The contents of
35 FileInfo are indeterminate.
40 IN VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE
*Dirent
,
41 IN UINTN SingleFileInfoSize
,
42 OUT EFI_FILE_INFO
*FileInfo
48 // Convert the name, set the actual size.
50 FileInfo
->Size
= SingleFileInfoSize
;
51 Status
= VirtioFsFuseDirentPlusToEfiFileInfo (Dirent
, FileInfo
);
52 if (EFI_ERROR (Status
)) {
56 // Populate the scalar fields.
58 Status
= VirtioFsFuseAttrToEfiFileInfo (&Dirent
->AttrResp
, FileInfo
);
63 Refill the EFI_FILE_INFO cache from the directory stream.
68 IN OUT VIRTIO_FS_FILE
*VirtioFsFile
73 VIRTIO_FS_FUSE_STATFS_RESPONSE FilesysAttr
;
76 UINTN SingleFileInfoSize
;
78 UINT64 DirStreamCookie
;
79 UINT64 CacheEndsAtCookie
;
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()
89 VirtioFs
= VirtioFsFile
->OwnerFs
;
90 Status
= VirtioFsFuseStatFs (VirtioFs
, VirtioFsFile
->NodeId
, &FilesysAttr
);
91 if (EFI_ERROR (Status
)) {
94 DirentBufSize
= (UINT32
)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (
96 if (DirentBufSize
== 0) {
97 return EFI_UNSUPPORTED
;
99 DirentBufSize
*= VIRTIO_FS_FILE_MAX_FILE_INFO
;
100 DirentBuf
= AllocatePool (DirentBufSize
);
101 if (DirentBuf
== NULL
) {
102 return EFI_OUT_OF_RESOURCES
;
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.
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
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
119 if (FileInfoArray
== NULL
) {
120 Status
= EFI_OUT_OF_RESOURCES
;
125 // Pick up reading the directory stream where the previous cache ended.
127 DirStreamCookie
= VirtioFsFile
->FilePosition
;
128 CacheEndsAtCookie
= VirtioFsFile
->FilePosition
;
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).
140 Remaining
= DirentBufSize
;
141 Status
= VirtioFsFuseReadFileOrDir (
143 VirtioFsFile
->NodeId
,
144 VirtioFsFile
->FuseHandle
,
146 DirStreamCookie
, // Offset
150 if (EFI_ERROR (Status
)) {
151 goto FreeFileInfoArray
;
154 if (Remaining
== 0) {
156 // The directory stream ends.
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).
167 while (Remaining
>= sizeof (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE
)) {
168 VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE
*Dirent
;
171 Dirent
= (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE
*)(DirentBuf
+ Consumed
);
172 DirentSize
= (UINT32
)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (
174 if (DirentSize
== 0) {
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.
182 Status
= EFI_PROTOCOL_ERROR
;
183 goto FreeFileInfoArray
;
185 if (DirentSize
> Remaining
) {
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.
191 Status
= EFI_PROTOCOL_ERROR
;
192 goto FreeFileInfoArray
;
194 if (Dirent
->Namelen
> FilesysAttr
.Namelen
) {
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.
201 Status
= EFI_PROTOCOL_ERROR
;
202 goto FreeFileInfoArray
;
206 // If we haven't filled the EFI_FILE_INFO cache yet, attempt transforming
207 // Dirent to EFI_FILE_INFO.
209 if (NumFileInfo
< VIRTIO_FS_FILE_MAX_FILE_INFO
) {
210 EFI_FILE_INFO
*FileInfo
;
212 FileInfo
= (EFI_FILE_INFO
*)(FileInfoArray
+
213 (NumFileInfo
* SingleFileInfoSize
));
214 Status
= PopulateFileInfo (Dirent
, SingleFileInfoSize
, FileInfo
);
215 if (!EFI_ERROR (Status
)) {
217 // Dirent has been transformed and cached successfully.
221 // The next time we refill the cache, restart reading the directory
222 // stream right after the entry that we've just transformed and
225 CacheEndsAtCookie
= Dirent
->CookieForNextEntry
;
228 // If Dirent wasn't transformable to an EFI_FILE_INFO, we'll just skip
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.)
239 if (Dirent
->NodeResp
.NodeId
!= 0) {
240 VirtioFsFuseForget (VirtioFs
, Dirent
->NodeResp
.NodeId
);
244 // Advance to the next entry in DirentBuf.
246 DirStreamCookie
= Dirent
->CookieForNextEntry
;
247 Consumed
+= DirentSize
;
248 Remaining
-= DirentSize
;
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.
257 Status
= EFI_PROTOCOL_ERROR
;
258 goto FreeFileInfoArray
;
261 // Fetch another DirentBuf from the directory stream, unless we've filled
262 // the EFI_FILE_INFO cache.
264 } while (NumFileInfo
< VIRTIO_FS_FILE_MAX_FILE_INFO
);
267 // Commit the results. (Note that the result may be an empty cache.)
269 if (VirtioFsFile
->FileInfoArray
!= NULL
) {
270 FreePool (VirtioFsFile
->FileInfoArray
);
272 VirtioFsFile
->FileInfoArray
= FileInfoArray
;
273 VirtioFsFile
->SingleFileInfoSize
= SingleFileInfoSize
;
274 VirtioFsFile
->NumFileInfo
= NumFileInfo
;
275 VirtioFsFile
->NextFileInfo
= 0;
276 VirtioFsFile
->FilePosition
= CacheEndsAtCookie
;
278 FreePool (DirentBuf
);
282 FreePool (FileInfoArray
);
285 FreePool (DirentBuf
);
291 Read an entry from the EFI_FILE_INFO cache.
296 IN OUT VIRTIO_FS_FILE
*VirtioFsFile
,
297 IN OUT UINTN
*BufferSize
,
301 EFI_FILE_INFO
*FileInfo
;
302 UINTN CallerAllocated
;
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.
308 if (VirtioFsFile
->NextFileInfo
== VirtioFsFile
->NumFileInfo
) {
311 Status
= RefillFileInfoCache (VirtioFsFile
);
312 if (EFI_ERROR (Status
)) {
313 return (Status
== EFI_BUFFER_TOO_SMALL
) ? EFI_DEVICE_ERROR
: Status
;
315 if (VirtioFsFile
->NumFileInfo
== 0) {
320 FileInfo
= (EFI_FILE_INFO
*)(VirtioFsFile
->FileInfoArray
+
321 (VirtioFsFile
->NextFileInfo
*
322 VirtioFsFile
->SingleFileInfoSize
));
325 // Check if the caller is ready to accept FileInfo. If not, we'll just
326 // present the required size for now.
328 // (The (UINTN) cast below is safe because FileInfo->Size has been reduced
329 // from VirtioFsFile->SingleFileInfoSize, in
331 // RefillFileInfoCache()
332 // PopulateFileInfo()
333 // VirtioFsFuseDirentPlusToEfiFileInfo()
335 // and VirtioFsFile->SingleFileInfoSize was computed from
336 // FilesysAttr.Namelen, which had been accepted by
337 // VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE().)
339 CallerAllocated
= *BufferSize
;
340 *BufferSize
= (UINTN
)FileInfo
->Size
;
341 if (CallerAllocated
< *BufferSize
) {
342 return EFI_BUFFER_TOO_SMALL
;
345 // Output FileInfo, and remove it from the cache.
347 CopyMem (Buffer
, FileInfo
, *BufferSize
);
348 VirtioFsFile
->NextFileInfo
++;
353 Read from a regular file.
358 IN OUT VIRTIO_FS_FILE
*VirtioFsFile
,
359 IN OUT UINTN
*BufferSize
,
365 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr
;
369 VirtioFs
= VirtioFsFile
->OwnerFs
;
371 // The UEFI spec forbids reads that start beyond the end of the file.
373 Status
= VirtioFsFuseGetAttr (VirtioFs
, VirtioFsFile
->NodeId
, &FuseAttr
);
374 if (EFI_ERROR (Status
) || VirtioFsFile
->FilePosition
> FuseAttr
.Size
) {
375 return EFI_DEVICE_ERROR
;
378 Status
= EFI_SUCCESS
;
385 // FUSE_READ cannot express a >=4GB buffer size.
387 ReadSize
= (UINT32
)MIN ((UINTN
)MAX_UINT32
, Left
);
388 Status
= VirtioFsFuseReadFileOrDir (
390 VirtioFsFile
->NodeId
,
391 VirtioFsFile
->FuseHandle
,
393 VirtioFsFile
->FilePosition
+ Transferred
,
395 (UINT8
*)Buffer
+ Transferred
397 if (EFI_ERROR (Status
) || ReadSize
== 0) {
400 Transferred
+= ReadSize
;
404 *BufferSize
= Transferred
;
405 VirtioFsFile
->FilePosition
+= Transferred
;
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
412 return (Transferred
> 0) ? EFI_SUCCESS
: Status
;
417 VirtioFsSimpleFileRead (
418 IN EFI_FILE_PROTOCOL
*This
,
419 IN OUT UINTN
*BufferSize
,
423 VIRTIO_FS_FILE
*VirtioFsFile
;
426 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
428 if (VirtioFsFile
->IsDirectory
) {
429 Status
= ReadFileInfoCache (VirtioFsFile
, BufferSize
, Buffer
);
431 Status
= ReadRegularFile (VirtioFsFile
, BufferSize
, Buffer
);