]> git.proxmox.com Git - mirror_edk2.git/commitdiff
OvmfPkg/VirtioFsDxe: implement EFI_FILE_PROTOCOL.Read() for directories
authorLaszlo Ersek <lersek@redhat.com>
Wed, 16 Dec 2020 21:11:13 +0000 (22:11 +0100)
committermergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Mon, 21 Dec 2020 17:16:23 +0000 (17:16 +0000)
Using the functions introduced previously, we can now implement
VirtioFsSimpleFileRead() for directories as well.

This patch completes the read-only support for virtio-fs. Commands like
"TYPE" and "DIR" work in the UEFI shell.

Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=3097
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20201216211125.19496-37-lersek@redhat.com>
Acked-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
OvmfPkg/VirtioFsDxe/SimpleFsRead.c

index c4ad07c9aec4ceaa8653f0357bccb46746859712..d6368342a139d7f5f55ba1297fb215fa4538380f 100644 (file)
@@ -6,8 +6,349 @@
   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
@@ -85,7 +426,7 @@ VirtioFsSimpleFileRead (
   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