SPDX-License-Identifier: BSD-2-Clause-Patent\r
**/\r
\r
+#include <Library/BaseMemoryLib.h> // CopyMem()\r
+#include <Library/MemoryAllocationLib.h> // AllocatePool()\r
+\r
#include "VirtioFsDxe.h"\r
\r
+/**\r
+ Populate a caller-allocated EFI_FILE_INFO object from\r
+ VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE.\r
+\r
+ @param[in] Dirent The entry read from the directory stream. The\r
+ caller is responsible for ensuring that\r
+ Dirent->Namelen describe valid storage.\r
+\r
+ @param[in] SingleFileInfoSize The allocated size of FileInfo.\r
+\r
+ @param[out] FileInfo The EFI_FILE_INFO object to populate. On\r
+ success, all fields in FileInfo will be\r
+ updated, setting FileInfo->Size to the\r
+ actually used size (which will not exceed\r
+ SingleFileInfoSize).\r
+\r
+ @retval EFI_SUCCESS FileInfo has been filled in.\r
+\r
+ @return Error codes propagated from\r
+ VirtioFsFuseDirentPlusToEfiFileInfo() and\r
+ VirtioFsFuseAttrToEfiFileInfo(). The contents of\r
+ FileInfo are indeterminate.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+PopulateFileInfo (\r
+ IN VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *Dirent,\r
+ IN UINTN SingleFileInfoSize,\r
+ OUT EFI_FILE_INFO *FileInfo\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+\r
+ //\r
+ // Convert the name, set the actual size.\r
+ //\r
+ FileInfo->Size = SingleFileInfoSize;\r
+ Status = VirtioFsFuseDirentPlusToEfiFileInfo (Dirent, FileInfo);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+ //\r
+ // Populate the scalar fields.\r
+ //\r
+ Status = VirtioFsFuseAttrToEfiFileInfo (&Dirent->AttrResp, FileInfo);\r
+ return Status;\r
+}\r
+\r
+/**\r
+ Refill the EFI_FILE_INFO cache from the directory stream.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+RefillFileInfoCache (\r
+ IN OUT VIRTIO_FS_FILE *VirtioFsFile\r
+ )\r
+{\r
+ VIRTIO_FS *VirtioFs;\r
+ EFI_STATUS Status;\r
+ VIRTIO_FS_FUSE_STATFS_RESPONSE FilesysAttr;\r
+ UINT32 DirentBufSize;\r
+ UINT8 *DirentBuf;\r
+ UINTN SingleFileInfoSize;\r
+ UINT8 *FileInfoArray;\r
+ UINT64 DirStreamCookie;\r
+ UINT64 CacheEndsAtCookie;\r
+ UINTN NumFileInfo;\r
+\r
+ //\r
+ // Allocate a DirentBuf that can receive at least\r
+ // VIRTIO_FS_FILE_MAX_FILE_INFO directory entries, based on the maximum\r
+ // filename length supported by the filesystem. Note that the multiplication\r
+ // is safe from overflow due to the VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE()\r
+ // check.\r
+ //\r
+ VirtioFs = VirtioFsFile->OwnerFs;\r
+ Status = VirtioFsFuseStatFs (VirtioFs, VirtioFsFile->NodeId, &FilesysAttr);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+ DirentBufSize = (UINT32)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (\r
+ FilesysAttr.Namelen);\r
+ if (DirentBufSize == 0) {\r
+ return EFI_UNSUPPORTED;\r
+ }\r
+ DirentBufSize *= VIRTIO_FS_FILE_MAX_FILE_INFO;\r
+ DirentBuf = AllocatePool (DirentBufSize);\r
+ if (DirentBuf == NULL) {\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+\r
+ //\r
+ // Allocate the EFI_FILE_INFO cache. A single EFI_FILE_INFO element is sized\r
+ // accordingly to the maximum filename length supported by the filesystem.\r
+ //\r
+ // Note that the calculation below cannot overflow, due to the filename limit\r
+ // imposed by the VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE() check above. The\r
+ // calculation takes the L'\0' character that we'll need to append into\r
+ // account.\r
+ //\r
+ SingleFileInfoSize = (OFFSET_OF (EFI_FILE_INFO, FileName) +\r
+ ((UINTN)FilesysAttr.Namelen + 1) * sizeof (CHAR16));\r
+ FileInfoArray = AllocatePool (\r
+ VIRTIO_FS_FILE_MAX_FILE_INFO * SingleFileInfoSize\r
+ );\r
+ if (FileInfoArray == NULL) {\r
+ Status = EFI_OUT_OF_RESOURCES;\r
+ goto FreeDirentBuf;\r
+ }\r
+\r
+ //\r
+ // Pick up reading the directory stream where the previous cache ended.\r
+ //\r
+ DirStreamCookie = VirtioFsFile->FilePosition;\r
+ CacheEndsAtCookie = VirtioFsFile->FilePosition;\r
+ NumFileInfo = 0;\r
+ do {\r
+ UINT32 Remaining;\r
+ UINT32 Consumed;\r
+\r
+ //\r
+ // Fetch a chunk of the directory stream. The chunk may hold more entries\r
+ // than what we can fit in the cache. The chunk may also not entirely fill\r
+ // the cache, especially after filtering out entries that cannot be\r
+ // supported under UEFI (sockets, FIFOs, filenames with backslashes, etc).\r
+ //\r
+ Remaining = DirentBufSize;\r
+ Status = VirtioFsFuseReadFileOrDir (\r
+ VirtioFs,\r
+ VirtioFsFile->NodeId,\r
+ VirtioFsFile->FuseHandle,\r
+ TRUE, // IsDir\r
+ DirStreamCookie, // Offset\r
+ &Remaining, // Size\r
+ DirentBuf // Data\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ goto FreeFileInfoArray;\r
+ }\r
+\r
+ if (Remaining == 0) {\r
+ //\r
+ // The directory stream ends.\r
+ //\r
+ break;\r
+ }\r
+\r
+ //\r
+ // Iterate over all records in DirentBuf. Primarily, forget them all.\r
+ // Secondarily, if a record proves transformable to EFI_FILE_INFO, add it\r
+ // to the EFI_FILE_INFO cache (unless the cache is full).\r
+ //\r
+ Consumed = 0;\r
+ while (Remaining >= sizeof (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE)) {\r
+ VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *Dirent;\r
+ UINT32 DirentSize;\r
+\r
+ Dirent = (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *)(DirentBuf + Consumed);\r
+ DirentSize = (UINT32)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (\r
+ Dirent->Namelen);\r
+ if (DirentSize == 0) {\r
+ //\r
+ // This means one of two things: (a) Dirent->Namelen is zero, or (b)\r
+ // (b) Dirent->Namelen is unsupportably large. (a) is just invalid for\r
+ // the Virtio Filesystem device to send, while (b) shouldn't happen\r
+ // because "FilesysAttr.Namelen" -- the maximum filename length\r
+ // supported by the filesystem -- proved acceptable above.\r
+ //\r
+ Status = EFI_PROTOCOL_ERROR;\r
+ goto FreeFileInfoArray;\r
+ }\r
+ if (DirentSize > Remaining) {\r
+ //\r
+ // Dirent->Namelen suggests that the filename byte array (plus any\r
+ // padding) are truncated. This should never happen; the Virtio\r
+ // Filesystem device is supposed to send complete entries only.\r
+ //\r
+ Status = EFI_PROTOCOL_ERROR;\r
+ goto FreeFileInfoArray;\r
+ }\r
+ if (Dirent->Namelen > FilesysAttr.Namelen) {\r
+ //\r
+ // This is possible without tripping the truncation check above, due to\r
+ // how entries are padded. The condition means that Dirent->Namelen is\r
+ // reportedly larger than the filesystem limit, without spilling into\r
+ // the next alignment bucket. Should never happen.\r
+ //\r
+ Status = EFI_PROTOCOL_ERROR;\r
+ goto FreeFileInfoArray;\r
+ }\r
+\r
+ //\r
+ // If we haven't filled the EFI_FILE_INFO cache yet, attempt transforming\r
+ // Dirent to EFI_FILE_INFO.\r
+ //\r
+ if (NumFileInfo < VIRTIO_FS_FILE_MAX_FILE_INFO) {\r
+ EFI_FILE_INFO *FileInfo;\r
+\r
+ FileInfo = (EFI_FILE_INFO *)(FileInfoArray +\r
+ (NumFileInfo * SingleFileInfoSize));\r
+ Status = PopulateFileInfo (Dirent, SingleFileInfoSize, FileInfo);\r
+ if (!EFI_ERROR (Status)) {\r
+ //\r
+ // Dirent has been transformed and cached successfully.\r
+ //\r
+ NumFileInfo++;\r
+ //\r
+ // The next time we refill the cache, restart reading the directory\r
+ // stream right after the entry that we've just transformed and\r
+ // cached.\r
+ //\r
+ CacheEndsAtCookie = Dirent->CookieForNextEntry;\r
+ }\r
+ //\r
+ // If Dirent wasn't transformable to an EFI_FILE_INFO, we'll just skip\r
+ // it.\r
+ //\r
+ }\r
+\r
+ //\r
+ // Make the Virtio Filesystem device forget the NodeId in this directory\r
+ // entry, as we'll need it no more. (The "." and ".." entries need no\r
+ // FUSE_FORGET requests, when returned by FUSE_READDIRPLUS -- and so the\r
+ // Virtio Filesystem device reports their NodeId fields as zero.)\r
+ //\r
+ if (Dirent->NodeResp.NodeId != 0) {\r
+ VirtioFsFuseForget (VirtioFs, Dirent->NodeResp.NodeId);\r
+ }\r
+\r
+ //\r
+ // Advance to the next entry in DirentBuf.\r
+ //\r
+ DirStreamCookie = Dirent->CookieForNextEntry;\r
+ Consumed += DirentSize;\r
+ Remaining -= DirentSize;\r
+ }\r
+\r
+ if (Remaining > 0) {\r
+ //\r
+ // This suggests that a VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE header was\r
+ // truncated. This should never happen; the Virtio Filesystem device is\r
+ // supposed to send complete entries only.\r
+ //\r
+ Status = EFI_PROTOCOL_ERROR;\r
+ goto FreeFileInfoArray;\r
+ }\r
+ //\r
+ // Fetch another DirentBuf from the directory stream, unless we've filled\r
+ // the EFI_FILE_INFO cache.\r
+ //\r
+ } while (NumFileInfo < VIRTIO_FS_FILE_MAX_FILE_INFO);\r
+\r
+ //\r
+ // Commit the results. (Note that the result may be an empty cache.)\r
+ //\r
+ if (VirtioFsFile->FileInfoArray != NULL) {\r
+ FreePool (VirtioFsFile->FileInfoArray);\r
+ }\r
+ VirtioFsFile->FileInfoArray = FileInfoArray;\r
+ VirtioFsFile->SingleFileInfoSize = SingleFileInfoSize;\r
+ VirtioFsFile->NumFileInfo = NumFileInfo;\r
+ VirtioFsFile->NextFileInfo = 0;\r
+ VirtioFsFile->FilePosition = CacheEndsAtCookie;\r
+\r
+ FreePool (DirentBuf);\r
+ return EFI_SUCCESS;\r
+\r
+FreeFileInfoArray:\r
+ FreePool (FileInfoArray);\r
+\r
+FreeDirentBuf:\r
+ FreePool (DirentBuf);\r
+\r
+ return Status;\r
+}\r
+\r
+/**\r
+ Read an entry from the EFI_FILE_INFO cache.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+ReadFileInfoCache (\r
+ IN OUT VIRTIO_FS_FILE *VirtioFsFile,\r
+ IN OUT UINTN *BufferSize,\r
+ OUT VOID *Buffer\r
+ )\r
+{\r
+ EFI_FILE_INFO *FileInfo;\r
+ UINTN CallerAllocated;\r
+\r
+ //\r
+ // Refill the cache if needed. If the refill doesn't produce any new\r
+ // EFI_FILE_INFO, report End of Directory, by setting (*BufferSize) to 0.\r
+ //\r
+ if (VirtioFsFile->NextFileInfo == VirtioFsFile->NumFileInfo) {\r
+ EFI_STATUS Status;\r
+\r
+ Status = RefillFileInfoCache (VirtioFsFile);\r
+ if (EFI_ERROR (Status)) {\r
+ return (Status == EFI_BUFFER_TOO_SMALL) ? EFI_DEVICE_ERROR : Status;\r
+ }\r
+ if (VirtioFsFile->NumFileInfo == 0) {\r
+ *BufferSize = 0;\r
+ return EFI_SUCCESS;\r
+ }\r
+ }\r
+ FileInfo = (EFI_FILE_INFO *)(VirtioFsFile->FileInfoArray +\r
+ (VirtioFsFile->NextFileInfo *\r
+ VirtioFsFile->SingleFileInfoSize));\r
+\r
+ //\r
+ // Check if the caller is ready to accept FileInfo. If not, we'll just\r
+ // present the required size for now.\r
+ //\r
+ // (The (UINTN) cast below is safe because FileInfo->Size has been reduced\r
+ // from VirtioFsFile->SingleFileInfoSize, in\r
+ //\r
+ // RefillFileInfoCache()\r
+ // PopulateFileInfo()\r
+ // VirtioFsFuseDirentPlusToEfiFileInfo()\r
+ //\r
+ // and VirtioFsFile->SingleFileInfoSize was computed from\r
+ // FilesysAttr.Namelen, which had been accepted by\r
+ // VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE().)\r
+ //\r
+ CallerAllocated = *BufferSize;\r
+ *BufferSize = (UINTN)FileInfo->Size;\r
+ if (CallerAllocated < *BufferSize) {\r
+ return EFI_BUFFER_TOO_SMALL;\r
+ }\r
+ //\r
+ // Output FileInfo, and remove it from the cache.\r
+ //\r
+ CopyMem (Buffer, FileInfo, *BufferSize);\r
+ VirtioFsFile->NextFileInfo++;\r
+ return EFI_SUCCESS;\r
+}\r
+\r
/**\r
Read from a regular file.\r
**/\r
VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);\r
\r
if (VirtioFsFile->IsDirectory) {\r
- Status = EFI_NO_MEDIA;\r
+ Status = ReadFileInfoCache (VirtioFsFile, BufferSize, Buffer);\r
} else {\r
Status = ReadRegularFile (VirtioFsFile, BufferSize, Buffer);\r
}\r