]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/VirtioFsDxe/Helpers.c
OvmfPkg/VirtioFsDxe: call IsTimeValid() before EfiTimeToEpoch()
[mirror_edk2.git] / OvmfPkg / VirtioFsDxe / Helpers.c
index 7b4906c541845dde7c260285336db59067c5df34..b81c04e0a4e81df5d72f99d164c8f49d6784e15d 100644 (file)
@@ -6,6 +6,10 @@
   SPDX-License-Identifier: BSD-2-Clause-Patent\r
 **/\r
 \r
+#include <Library/BaseLib.h>             // StrLen()\r
+#include <Library/BaseMemoryLib.h>       // CopyMem()\r
+#include <Library/MemoryAllocationLib.h> // AllocatePool()\r
+#include <Library/TimeBaseLib.h>         // EpochToEfiTime()\r
 #include <Library/VirtioLib.h>           // Virtio10WriteFeatures()\r
 \r
 #include "VirtioFsDxe.h"\r
@@ -297,3 +301,2126 @@ VirtioFsExitBoot (
     VirtioFsAsVoid, VirtioFs->Label));\r
   VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, 0);\r
 }\r
+\r
+/**\r
+  Validate two VIRTIO_FS_SCATTER_GATHER_LIST objects -- list of request\r
+  buffers, list of response buffers -- together.\r
+\r
+  On input, the caller is required to populate the following fields:\r
+  - VIRTIO_FS_IO_VECTOR.Buffer,\r
+  - VIRTIO_FS_IO_VECTOR.Size,\r
+  - VIRTIO_FS_SCATTER_GATHER_LIST.IoVec,\r
+  - VIRTIO_FS_SCATTER_GATHER_LIST.NumVec.\r
+\r
+  On output (on successful return), the following fields will be\r
+  zero-initialized:\r
+  - VIRTIO_FS_IO_VECTOR.Mapped,\r
+  - VIRTIO_FS_IO_VECTOR.MappedAddress,\r
+  - VIRTIO_FS_IO_VECTOR.Mapping,\r
+  - VIRTIO_FS_IO_VECTOR.Transferred.\r
+\r
+  On output (on successful return), the following fields will be calculated:\r
+  - VIRTIO_FS_SCATTER_GATHER_LIST.TotalSize.\r
+\r
+  The function may only be called after VirtioFsInit() returns successfully and\r
+  before VirtioFsUninit() is called.\r
+\r
+  @param[in] VirtioFs            The Virtio Filesystem device that the\r
+                                 request-response exchange, expressed via\r
+                                 RequestSgList and ResponseSgList, will be\r
+                                 submitted to.\r
+\r
+  @param[in,out] RequestSgList   The scatter-gather list that describes the\r
+                                 request part of the exchange -- the buffers\r
+                                 that should be sent to the Virtio Filesystem\r
+                                 device in the virtio transfer.\r
+\r
+  @param[in,out] ResponseSgList  The scatter-gather list that describes the\r
+                                 response part of the exchange -- the buffers\r
+                                 that the Virtio Filesystem device should\r
+                                 populate in the virtio transfer. May be NULL\r
+                                 if the exchange with the Virtio Filesystem\r
+                                 device consists of a request only, with the\r
+                                 response part omitted altogether.\r
+\r
+  @retval EFI_SUCCESS            RequestSgList and ResponseSgList have been\r
+                                 validated, output fields have been set.\r
+\r
+  @retval EFI_INVALID_PARAMETER  RequestSgList is NULL.\r
+\r
+  @retval EFI_INVALID_PARAMETER  On input, a\r
+                                 VIRTIO_FS_SCATTER_GATHER_LIST.IoVec field is\r
+                                 NULL, or a\r
+                                 VIRTIO_FS_SCATTER_GATHER_LIST.NumVec field is\r
+                                 zero.\r
+\r
+  @retval EFI_INVALID_PARAMETER  On input, a VIRTIO_FS_IO_VECTOR.Buffer field\r
+                                 is NULL, or a VIRTIO_FS_IO_VECTOR.Size field\r
+                                 is zero.\r
+\r
+  @retval EFI_UNSUPPORTED        (RequestSgList->NumVec +\r
+                                 ResponseSgList->NumVec) exceeds\r
+                                 VirtioFs->QueueSize, meaning that the total\r
+                                 list of buffers cannot be placed on the virtio\r
+                                 queue in a single descriptor chain (with one\r
+                                 descriptor per buffer).\r
+\r
+  @retval EFI_UNSUPPORTED        One of the\r
+                                 VIRTIO_FS_SCATTER_GATHER_LIST.TotalSize fields\r
+                                 would exceed MAX_UINT32.\r
+**/\r
+EFI_STATUS\r
+VirtioFsSgListsValidate (\r
+  IN     VIRTIO_FS                     *VirtioFs,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL\r
+  )\r
+{\r
+  VIRTIO_FS_SCATTER_GATHER_LIST *SgListParam[2];\r
+  UINT16                        DescriptorsNeeded;\r
+  UINTN                         ListId;\r
+\r
+  if (RequestSgList == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  SgListParam[0] = RequestSgList;\r
+  SgListParam[1] = ResponseSgList;\r
+\r
+  DescriptorsNeeded = 0;\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    VIRTIO_FS_SCATTER_GATHER_LIST *SgList;\r
+    UINT32                        SgListTotalSize;\r
+    UINTN                         IoVecIdx;\r
+\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    //\r
+    // Sanity-check SgList -- it must provide at least one IO Vector.\r
+    //\r
+    if (SgList->IoVec == NULL || SgList->NumVec == 0) {\r
+      return EFI_INVALID_PARAMETER;\r
+    }\r
+    //\r
+    // Make sure that, for each IO Vector in this SgList, a virtio descriptor\r
+    // can be added to the virtio queue, after the other descriptors added\r
+    // previously.\r
+    //\r
+    if (SgList->NumVec > (UINTN)(MAX_UINT16 - DescriptorsNeeded) ||\r
+        DescriptorsNeeded + SgList->NumVec > VirtioFs->QueueSize) {\r
+      return EFI_UNSUPPORTED;\r
+    }\r
+    DescriptorsNeeded += (UINT16)SgList->NumVec;\r
+\r
+    SgListTotalSize = 0;\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      VIRTIO_FS_IO_VECTOR *IoVec;\r
+\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Sanity-check this IoVec -- it must describe a non-empty buffer.\r
+      //\r
+      if (IoVec->Buffer == NULL || IoVec->Size == 0) {\r
+        return EFI_INVALID_PARAMETER;\r
+      }\r
+      //\r
+      // Make sure the cumulative size of all IO Vectors in this SgList remains\r
+      // expressible as a UINT32.\r
+      //\r
+      if (IoVec->Size > MAX_UINT32 - SgListTotalSize) {\r
+        return EFI_UNSUPPORTED;\r
+      }\r
+      SgListTotalSize += (UINT32)IoVec->Size;\r
+\r
+      //\r
+      // Initialize those fields in this IO Vector that will be updated in\r
+      // relation to mapping / transfer.\r
+      //\r
+      IoVec->Mapped        = FALSE;\r
+      IoVec->MappedAddress = 0;\r
+      IoVec->Mapping       = NULL;\r
+      IoVec->Transferred   = 0;\r
+    }\r
+\r
+    //\r
+    // Store the cumulative size of all IO Vectors that we have calculated in\r
+    // this SgList.\r
+    //\r
+    SgList->TotalSize = SgListTotalSize;\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Submit a validated pair of (request buffer list, response buffer list) to the\r
+  Virtio Filesystem device.\r
+\r
+  On input, the pair of VIRTIO_FS_SCATTER_GATHER_LIST objects must have been\r
+  validated together, using the VirtioFsSgListsValidate() function.\r
+\r
+  On output (on successful return), the following fields will be re-initialized\r
+  to zero (after temporarily setting them to different values):\r
+  - VIRTIO_FS_IO_VECTOR.Mapped,\r
+  - VIRTIO_FS_IO_VECTOR.MappedAddress,\r
+  - VIRTIO_FS_IO_VECTOR.Mapping.\r
+\r
+  On output (on successful return), the following fields will be calculated:\r
+  - VIRTIO_FS_IO_VECTOR.Transferred.\r
+\r
+  The function may only be called after VirtioFsInit() returns successfully and\r
+  before VirtioFsUninit() is called.\r
+\r
+  @param[in,out] VirtioFs        The Virtio Filesystem device that the\r
+                                 request-response exchange, expressed via\r
+                                 RequestSgList and ResponseSgList, should now\r
+                                 be submitted to.\r
+\r
+  @param[in,out] RequestSgList   The scatter-gather list that describes the\r
+                                 request part of the exchange -- the buffers\r
+                                 that should be sent to the Virtio Filesystem\r
+                                 device in the virtio transfer.\r
+\r
+  @param[in,out] ResponseSgList  The scatter-gather list that describes the\r
+                                 response part of the exchange -- the buffers\r
+                                 that the Virtio Filesystem device should\r
+                                 populate in the virtio transfer. May be NULL\r
+                                 if and only if NULL was passed to\r
+                                 VirtioFsSgListsValidate() as ResponseSgList.\r
+\r
+  @retval EFI_SUCCESS       Transfer complete. The caller should investigate\r
+                            the VIRTIO_FS_IO_VECTOR.Transferred fields in\r
+                            ResponseSgList, to ensure coverage of the relevant\r
+                            response buffers. Subsequently, the caller should\r
+                            investigate the contents of those buffers.\r
+\r
+  @retval EFI_DEVICE_ERROR  The Virtio Filesystem device reported populating\r
+                            more response bytes than ResponseSgList->TotalSize.\r
+\r
+  @return                   Error codes propagated from\r
+                            VirtioMapAllBytesInSharedBuffer(), VirtioFlush(),\r
+                            or VirtioFs->Virtio->UnmapSharedBuffer().\r
+**/\r
+EFI_STATUS\r
+VirtioFsSgListsSubmit (\r
+  IN OUT VIRTIO_FS                     *VirtioFs,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL\r
+  )\r
+{\r
+  VIRTIO_FS_SCATTER_GATHER_LIST *SgListParam[2];\r
+  VIRTIO_MAP_OPERATION          SgListVirtioMapOp[ARRAY_SIZE (SgListParam)];\r
+  UINT16                        SgListDescriptorFlag[ARRAY_SIZE (SgListParam)];\r
+  UINTN                         ListId;\r
+  VIRTIO_FS_SCATTER_GATHER_LIST *SgList;\r
+  UINTN                         IoVecIdx;\r
+  VIRTIO_FS_IO_VECTOR           *IoVec;\r
+  EFI_STATUS                    Status;\r
+  DESC_INDICES                  Indices;\r
+  UINT32                        TotalBytesWrittenByDevice;\r
+  UINT32                        BytesPermittedForWrite;\r
+\r
+  SgListParam[0]          = RequestSgList;\r
+  SgListVirtioMapOp[0]    = VirtioOperationBusMasterRead;\r
+  SgListDescriptorFlag[0] = 0;\r
+\r
+  SgListParam[1]          = ResponseSgList;\r
+  SgListVirtioMapOp[1]    = VirtioOperationBusMasterWrite;\r
+  SgListDescriptorFlag[1] = VRING_DESC_F_WRITE;\r
+\r
+  //\r
+  // Map all IO Vectors.\r
+  //\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Map this IO Vector.\r
+      //\r
+      Status = VirtioMapAllBytesInSharedBuffer (\r
+                 VirtioFs->Virtio,\r
+                 SgListVirtioMapOp[ListId],\r
+                 IoVec->Buffer,\r
+                 IoVec->Size,\r
+                 &IoVec->MappedAddress,\r
+                 &IoVec->Mapping\r
+                 );\r
+      if (EFI_ERROR (Status)) {\r
+        goto Unmap;\r
+      }\r
+      IoVec->Mapped = TRUE;\r
+    }\r
+  }\r
+\r
+  //\r
+  // Compose the descriptor chain.\r
+  //\r
+  VirtioPrepare (&VirtioFs->Ring, &Indices);\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      UINT16 NextFlag;\r
+\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Set VRING_DESC_F_NEXT on all except the very last descriptor.\r
+      //\r
+      NextFlag = VRING_DESC_F_NEXT;\r
+      if (ListId == ARRAY_SIZE (SgListParam) - 1 &&\r
+          IoVecIdx == SgList->NumVec - 1) {\r
+        NextFlag = 0;\r
+      }\r
+      VirtioAppendDesc (\r
+        &VirtioFs->Ring,\r
+        IoVec->MappedAddress,\r
+        (UINT32)IoVec->Size,\r
+        SgListDescriptorFlag[ListId] | NextFlag,\r
+        &Indices\r
+        );\r
+    }\r
+  }\r
+\r
+  //\r
+  // Submit the descriptor chain.\r
+  //\r
+  Status = VirtioFlush (VirtioFs->Virtio, VIRTIO_FS_REQUEST_QUEUE,\r
+             &VirtioFs->Ring, &Indices, &TotalBytesWrittenByDevice);\r
+  if (EFI_ERROR (Status)) {\r
+    goto Unmap;\r
+  }\r
+\r
+  //\r
+  // Sanity-check: the Virtio Filesystem device should not have written more\r
+  // bytes than what we offered buffers for.\r
+  //\r
+  if (ResponseSgList == NULL) {\r
+    BytesPermittedForWrite = 0;\r
+  } else {\r
+    BytesPermittedForWrite = ResponseSgList->TotalSize;\r
+  }\r
+  if (TotalBytesWrittenByDevice > BytesPermittedForWrite) {\r
+    Status = EFI_DEVICE_ERROR;\r
+    goto Unmap;\r
+  }\r
+\r
+  //\r
+  // Update the transfer sizes in the IO Vectors.\r
+  //\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      if (SgListVirtioMapOp[ListId] == VirtioOperationBusMasterRead) {\r
+        //\r
+        // We report that the Virtio Filesystem device has read all buffers in\r
+        // the request.\r
+        //\r
+        IoVec->Transferred = IoVec->Size;\r
+      } else {\r
+        //\r
+        // Regarding the response, calculate how much of the current IO Vector\r
+        // has been populated by the Virtio Filesystem device. In\r
+        // "TotalBytesWrittenByDevice", VirtioFlush() reported the total count\r
+        // across all device-writeable descriptors, in the order they were\r
+        // chained on the ring.\r
+        //\r
+        IoVec->Transferred = MIN ((UINTN)TotalBytesWrittenByDevice,\r
+                               IoVec->Size);\r
+        TotalBytesWrittenByDevice -= (UINT32)IoVec->Transferred;\r
+      }\r
+    }\r
+  }\r
+\r
+  //\r
+  // By now, "TotalBytesWrittenByDevice" has been exhausted.\r
+  //\r
+  ASSERT (TotalBytesWrittenByDevice == 0);\r
+\r
+  //\r
+  // We've succeeded; fall through.\r
+  //\r
+Unmap:\r
+  //\r
+  // Unmap all mapped IO Vectors on both the success and the error paths. The\r
+  // unmapping occurs in reverse order of mapping, in an attempt to avoid\r
+  // memory fragmentation.\r
+  //\r
+  ListId = ARRAY_SIZE (SgListParam);\r
+  while (ListId > 0) {\r
+    --ListId;\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    IoVecIdx = SgList->NumVec;\r
+    while (IoVecIdx > 0) {\r
+      EFI_STATUS UnmapStatus;\r
+\r
+      --IoVecIdx;\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Unmap this IO Vector, if it has been mapped.\r
+      //\r
+      if (!IoVec->Mapped) {\r
+        continue;\r
+      }\r
+      UnmapStatus = VirtioFs->Virtio->UnmapSharedBuffer (VirtioFs->Virtio,\r
+                                        IoVec->Mapping);\r
+      //\r
+      // Re-set the following fields to the values they initially got from\r
+      // VirtioFsSgListsValidate() -- the above unmapping attempt is considered\r
+      // final, even if it fails.\r
+      //\r
+      IoVec->Mapped        = FALSE;\r
+      IoVec->MappedAddress = 0;\r
+      IoVec->Mapping       = NULL;\r
+\r
+      //\r
+      // If we are on the success path, but the unmapping failed, we need to\r
+      // transparently flip to the failure path -- the caller must learn they\r
+      // should not consult the response buffers.\r
+      //\r
+      // The branch below can be taken at most once.\r
+      //\r
+      if (!EFI_ERROR (Status) && EFI_ERROR (UnmapStatus)) {\r
+        Status = UnmapStatus;\r
+      }\r
+    }\r
+  }\r
+\r
+  return Status;\r
+}\r
+\r
+/**\r
+  Set up the fields of a new VIRTIO_FS_FUSE_REQUEST object.\r
+\r
+  The function may only be called after VirtioFsInit() returns successfully and\r
+  before VirtioFsUninit() is called.\r
+\r
+  @param[in,out] VirtioFs The Virtio Filesystem device that the request is\r
+                          being prepared for. The "VirtioFs->RequestId" field\r
+                          will be copied into "Request->Unique". On output (on\r
+                          successful return), "VirtioFs->RequestId" will be\r
+                          incremented.\r
+\r
+  @param[out] Request     The VIRTIO_FS_FUSE_REQUEST object whose fields are to\r
+                          be set.\r
+\r
+  @param[in] RequestSize  The total size of the request, including\r
+                          sizeof(VIRTIO_FS_FUSE_REQUEST).\r
+\r
+  @param[in] Opcode       The VIRTIO_FS_FUSE_OPCODE that identifies the command\r
+                          to send.\r
+\r
+  @param[in] NodeId       The inode number of the file that the request refers\r
+                          to. When Opcode is VirtioFsFuseOpInit, NodeId is\r
+                          ignored by the Virtio Filesystem device.\r
+\r
+  @retval EFI_INVALID_PARAMETER  RequestSize is smaller than\r
+                                 sizeof(VIRTIO_FS_FUSE_REQUEST).\r
+\r
+  @retval EFI_OUT_OF_RESOURCES   "VirtioFs->RequestId" is MAX_UINT64, and can\r
+                                 be incremented no more.\r
+\r
+  @retval EFI_SUCCESS            Request has been populated,\r
+                                 "VirtioFs->RequestId" has been incremented.\r
+**/\r
+EFI_STATUS\r
+VirtioFsFuseNewRequest (\r
+  IN OUT VIRTIO_FS              *VirtioFs,\r
+     OUT VIRTIO_FS_FUSE_REQUEST *Request,\r
+  IN     UINT32                 RequestSize,\r
+  IN     VIRTIO_FS_FUSE_OPCODE  Opcode,\r
+  IN     UINT64                 NodeId\r
+  )\r
+{\r
+  if (RequestSize < sizeof *Request) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (VirtioFs->RequestId == MAX_UINT64) {\r
+    return EFI_OUT_OF_RESOURCES;\r
+  }\r
+\r
+  Request->Len     = RequestSize;\r
+  Request->Opcode  = Opcode;\r
+  Request->Unique  = VirtioFs->RequestId++;\r
+  Request->NodeId  = NodeId;\r
+  Request->Uid     = 0;\r
+  Request->Gid     = 0;\r
+  Request->Pid     = 1;\r
+  Request->Padding = 0;\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Check the common FUSE response format.\r
+\r
+  The first buffer in the response scatter-gather list is assumed a\r
+  VIRTIO_FS_FUSE_RESPONSE structure. Subsequent response buffers, if any, up to\r
+  and excluding the last one, are assumed fixed size. The last response buffer\r
+  may or may not be fixed size, as specified by the caller.\r
+\r
+  This function may only be called after VirtioFsSgListsSubmit() returns\r
+  successfully.\r
+\r
+  @param[in] ResponseSgList   The scatter-gather list that describes the\r
+                              response part of the exchange -- the buffers that\r
+                              the Virtio Filesystem device filled in during the\r
+                              virtio transfer.\r
+\r
+  @param[in] RequestId        The request identifier to which the response is\r
+                              expected to belong.\r
+\r
+  @param[out] TailBufferFill  If NULL, then the last buffer in ResponseSgList\r
+                              is considered fixed size. Otherwise, the last\r
+                              buffer is considered variable size, and on\r
+                              successful return, TailBufferFill reports the\r
+                              number of bytes in the last buffer.\r
+\r
+  @retval EFI_INVALID_PARAMETER  TailBufferFill is not NULL (i.e., the last\r
+                                 buffer is considered variable size), and\r
+                                 ResponseSgList->NumVec is 1.\r
+\r
+  @retval EFI_INVALID_PARAMETER  The allocated size of the first buffer does\r
+                                 not match sizeof(VIRTIO_FS_FUSE_RESPONSE).\r
+\r
+  @retval EFI_PROTOCOL_ERROR     The VIRTIO_FS_FUSE_RESPONSE structure in the\r
+                                 first buffer has not been fully populated.\r
+\r
+  @retval EFI_PROTOCOL_ERROR     "VIRTIO_FS_FUSE_RESPONSE.Len" in the first\r
+                                 buffer does not equal the sum of the\r
+                                 individual buffer sizes (as populated).\r
+\r
+  @retval EFI_PROTOCOL_ERROR     "VIRTIO_FS_FUSE_RESPONSE.Unique" in the first\r
+                                 buffer does not equal RequestId.\r
+\r
+  @retval EFI_PROTOCOL_ERROR     "VIRTIO_FS_FUSE_RESPONSE.Error" in the first\r
+                                 buffer is zero, but a subsequent fixed size\r
+                                 buffer has not been fully populated.\r
+\r
+  @retval EFI_DEVICE_ERROR       "VIRTIO_FS_FUSE_RESPONSE.Error" in the first\r
+                                 buffer is nonzero. The caller may investigate\r
+                                 "VIRTIO_FS_FUSE_RESPONSE.Error". Note that the\r
+                                 completeness of the subsequent fixed size\r
+                                 buffers is not verified in this case.\r
+\r
+  @retval EFI_SUCCESS            Verification successful.\r
+**/\r
+EFI_STATUS\r
+VirtioFsFuseCheckResponse (\r
+  IN  VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList,\r
+  IN  UINT64                        RequestId,\r
+  OUT UINTN                         *TailBufferFill\r
+  )\r
+{\r
+  UINTN                   NumFixedSizeVec;\r
+  VIRTIO_FS_FUSE_RESPONSE *CommonResp;\r
+  UINT32                  TotalTransferred;\r
+  UINTN                   Idx;\r
+\r
+  //\r
+  // Ensured by VirtioFsSgListsValidate().\r
+  //\r
+  ASSERT (ResponseSgList->NumVec > 0);\r
+\r
+  if (TailBufferFill == NULL) {\r
+    //\r
+    // All buffers are considered fixed size.\r
+    //\r
+    NumFixedSizeVec = ResponseSgList->NumVec;\r
+  } else {\r
+    //\r
+    // If the last buffer is variable size, then we need that buffer to be\r
+    // different from the first buffer, which is considered a\r
+    // VIRTIO_FS_FUSE_RESPONSE (fixed size) structure.\r
+    //\r
+    if (ResponseSgList->NumVec == 1) {\r
+      return EFI_INVALID_PARAMETER;\r
+    }\r
+    NumFixedSizeVec = ResponseSgList->NumVec - 1;\r
+  }\r
+\r
+  //\r
+  // The first buffer is supposed to carry a (fully populated)\r
+  // VIRTIO_FS_FUSE_RESPONSE structure.\r
+  //\r
+  if (ResponseSgList->IoVec[0].Size != sizeof *CommonResp) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+  if (ResponseSgList->IoVec[0].Transferred != ResponseSgList->IoVec[0].Size) {\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
+\r
+  //\r
+  // FUSE must report the same number of bytes, written by the Virtio\r
+  // Filesystem device, as the virtio transport does.\r
+  //\r
+  CommonResp = ResponseSgList->IoVec[0].Buffer;\r
+  TotalTransferred = 0;\r
+  for (Idx = 0; Idx < ResponseSgList->NumVec; Idx++) {\r
+    //\r
+    // Integer overflow and truncation are not possible, based on\r
+    // VirtioFsSgListsValidate() and VirtioFsSgListsSubmit().\r
+    //\r
+    TotalTransferred += (UINT32)ResponseSgList->IoVec[Idx].Transferred;\r
+  }\r
+  if (CommonResp->Len != TotalTransferred) {\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
+\r
+  //\r
+  // Enforce that FUSE match our request ID in the response.\r
+  //\r
+  if (CommonResp->Unique != RequestId) {\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
+\r
+  //\r
+  // If there is an explicit error report, skip checking the transfer\r
+  // counts for the rest of the fixed size buffers.\r
+  //\r
+  if (CommonResp->Error != 0) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  //\r
+  // There was no error reported, so we require that the Virtio Filesystem\r
+  // device populate all fixed size buffers. We checked this for the very first\r
+  // buffer above; let's check the rest (if any).\r
+  //\r
+  ASSERT (NumFixedSizeVec >= 1);\r
+  for (Idx = 1; Idx < NumFixedSizeVec; Idx++) {\r
+    if (ResponseSgList->IoVec[Idx].Transferred !=\r
+        ResponseSgList->IoVec[Idx].Size) {\r
+      return EFI_PROTOCOL_ERROR;\r
+    }\r
+  }\r
+\r
+  //\r
+  // If the last buffer is considered variable size, report its filled size.\r
+  //\r
+  if (TailBufferFill != NULL) {\r
+    *TailBufferFill = ResponseSgList->IoVec[NumFixedSizeVec].Transferred;\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  An ad-hoc function for mapping FUSE (well, Linux) "errno" values to\r
+  EFI_STATUS.\r
+\r
+  @param[in] Errno  The "VIRTIO_FS_FUSE_RESPONSE.Error" value, returned by the\r
+                    Virtio Filesystem device. The value is expected to be\r
+                    negative.\r
+\r
+  @return                   An EFI_STATUS error code that's deemed a passable\r
+                            mapping for the Errno value.\r
+\r
+  @retval EFI_DEVICE_ERROR  Fallback EFI_STATUS code for unrecognized Errno\r
+                            values.\r
+**/\r
+EFI_STATUS\r
+VirtioFsErrnoToEfiStatus (\r
+  IN INT32 Errno\r
+  )\r
+{\r
+  switch (Errno) {\r
+  case   -1: // EPERM               Operation not permitted\r
+    return EFI_SECURITY_VIOLATION;\r
+\r
+  case   -2: // ENOENT              No such file or directory\r
+  case   -3: // ESRCH               No such process\r
+  case   -6: // ENXIO               No such device or address\r
+  case  -10: // ECHILD              No child processes\r
+  case  -19: // ENODEV              No such device\r
+  case  -49: // EUNATCH             Protocol driver not attached\r
+  case  -65: // ENOPKG              Package not installed\r
+  case  -79: // ELIBACC             Can not access a needed shared library\r
+  case -126: // ENOKEY              Required key not available\r
+    return EFI_NOT_FOUND;\r
+\r
+  case   -4: // EINTR               Interrupted system call\r
+  case  -11: // EAGAIN, EWOULDBLOCK Resource temporarily unavailable\r
+  case  -16: // EBUSY               Device or resource busy\r
+  case  -26: // ETXTBSY             Text file busy\r
+  case  -35: // EDEADLK, EDEADLOCK  Resource deadlock avoided\r
+  case  -39: // ENOTEMPTY           Directory not empty\r
+  case  -42: // ENOMSG              No message of desired type\r
+  case  -61: // ENODATA             No data available\r
+  case  -85: // ERESTART            Interrupted system call should be restarted\r
+    return EFI_NOT_READY;\r
+\r
+  case   -5: // EIO                 Input/output error\r
+  case  -45: // EL2NSYNC            Level 2 not synchronized\r
+  case  -46: // EL3HLT              Level 3 halted\r
+  case  -47: // EL3RST              Level 3 reset\r
+  case  -51: // EL2HLT              Level 2 halted\r
+  case -121: // EREMOTEIO           Remote I/O error\r
+  case -133: // EHWPOISON           Memory page has hardware error\r
+    return EFI_DEVICE_ERROR;\r
+\r
+  case   -7: // E2BIG               Argument list too long\r
+  case  -36: // ENAMETOOLONG        File name too long\r
+  case  -90: // EMSGSIZE            Message too long\r
+    return EFI_BAD_BUFFER_SIZE;\r
+\r
+  case   -8: // ENOEXEC             Exec format error\r
+  case  -15: // ENOTBLK             Block device required\r
+  case  -18: // EXDEV               Invalid cross-device link\r
+  case  -20: // ENOTDIR             Not a directory\r
+  case  -21: // EISDIR              Is a directory\r
+  case  -25: // ENOTTY              Inappropriate ioctl for device\r
+  case  -27: // EFBIG               File too large\r
+  case  -29: // ESPIPE              Illegal seek\r
+  case  -38: // ENOSYS              Function not implemented\r
+  case  -59: // EBFONT              Bad font file format\r
+  case  -60: // ENOSTR              Device not a stream\r
+  case  -83: // ELIBEXEC            Cannot exec a shared library directly\r
+  case  -88: // ENOTSOCK            Socket operation on non-socket\r
+  case  -91: // EPROTOTYPE          Protocol wrong type for socket\r
+  case  -92: // ENOPROTOOPT         Protocol not available\r
+  case  -93: // EPROTONOSUPPORT     Protocol not supported\r
+  case  -94: // ESOCKTNOSUPPORT     Socket type not supported\r
+  case  -95: // ENOTSUP, EOPNOTSUPP Operation not supported\r
+  case  -96: // EPFNOSUPPORT        Protocol family not supported\r
+  case  -97: // EAFNOSUPPORT        Address family not supported by protocol\r
+  case  -99: // EADDRNOTAVAIL       Cannot assign requested address\r
+  case -118: // ENOTNAM             Not a XENIX named type file\r
+  case -120: // EISNAM              Is a named type file\r
+  case -124: // EMEDIUMTYPE         Wrong medium type\r
+    return EFI_UNSUPPORTED;\r
+\r
+  case   -9: // EBADF               Bad file descriptor\r
+  case  -14: // EFAULT              Bad address\r
+  case  -44: // ECHRNG              Channel number out of range\r
+  case  -48: // ELNRNG              Link number out of range\r
+  case  -53: // EBADR               Invalid request descriptor\r
+  case  -56: // EBADRQC             Invalid request code\r
+  case  -57: // EBADSLT             Invalid slot\r
+  case  -76: // ENOTUNIQ            Name not unique on network\r
+  case  -84: // EILSEQ        Invalid or incomplete multibyte or wide character\r
+    return EFI_NO_MAPPING;\r
+\r
+  case  -12: // ENOMEM              Cannot allocate memory\r
+  case  -23: // ENFILE              Too many open files in system\r
+  case  -24: // EMFILE              Too many open files\r
+  case  -31: // EMLINK              Too many links\r
+  case  -37: // ENOLCK              No locks available\r
+  case  -40: // ELOOP               Too many levels of symbolic links\r
+  case  -50: // ENOCSI              No CSI structure available\r
+  case  -55: // ENOANO              No anode\r
+  case  -63: // ENOSR               Out of streams resources\r
+  case  -82: // ELIBMAX         Attempting to link in too many shared libraries\r
+  case  -87: // EUSERS              Too many users\r
+  case -105: // ENOBUFS             No buffer space available\r
+  case -109: // ETOOMANYREFS        Too many references: cannot splice\r
+  case -119: // ENAVAIL             No XENIX semaphores available\r
+  case -122: // EDQUOT              Disk quota exceeded\r
+    return EFI_OUT_OF_RESOURCES;\r
+\r
+  case  -13: // EACCES              Permission denied\r
+    return EFI_ACCESS_DENIED;\r
+\r
+  case  -17: // EEXIST              File exists\r
+  case  -98: // EADDRINUSE          Address already in use\r
+  case -106: // EISCONN             Transport endpoint is already connected\r
+  case -114: // EALREADY            Operation already in progress\r
+  case -115: // EINPROGRESS         Operation now in progress\r
+    return EFI_ALREADY_STARTED;\r
+\r
+  case  -22: // EINVAL              Invalid argument\r
+  case  -33: // EDOM                Numerical argument out of domain\r
+    return EFI_INVALID_PARAMETER;\r
+\r
+  case  -28: // ENOSPC              No space left on device\r
+  case  -54: // EXFULL              Exchange full\r
+    return EFI_VOLUME_FULL;\r
+\r
+  case  -30: // EROFS               Read-only file system\r
+    return EFI_WRITE_PROTECTED;\r
+\r
+  case  -32: // EPIPE               Broken pipe\r
+  case  -43: // EIDRM               Identifier removed\r
+  case  -67: // ENOLINK             Link has been severed\r
+  case  -68: // EADV                Advertise error\r
+  case  -69: // ESRMNT              Srmount error\r
+  case  -70: // ECOMM               Communication error on send\r
+  case  -73: // EDOTDOT             RFS specific error\r
+  case  -78: // EREMCHG             Remote address changed\r
+  case  -86: // ESTRPIPE            Streams pipe error\r
+  case -102: // ENETRESET           Network dropped connection on reset\r
+  case -103: // ECONNABORTED        Software caused connection abort\r
+  case -104: // ECONNRESET          Connection reset by peer\r
+  case -116: // ESTALE              Stale file handle\r
+  case -125: // ECANCELED           Operation canceled\r
+  case -128: // EKEYREVOKED         Key has been revoked\r
+  case -129: // EKEYREJECTED        Key was rejected by service\r
+  case -130: // EOWNERDEAD          Owner died\r
+  case -131: // ENOTRECOVERABLE     State not recoverable\r
+    return EFI_ABORTED;\r
+\r
+  case  -34: // ERANGE              Numerical result out of range\r
+  case  -75: // EOVERFLOW           Value too large for defined data type\r
+    return EFI_BUFFER_TOO_SMALL;\r
+\r
+  case  -52: // EBADE               Invalid exchange\r
+  case -108: // ESHUTDOWN         Cannot send after transport endpoint shutdown\r
+  case -111: // ECONNREFUSED        Connection refused\r
+    return EFI_END_OF_FILE;\r
+\r
+  case  -62: // ETIME               Timer expired\r
+  case -110: // ETIMEDOUT           Connection timed out\r
+  case -127: // EKEYEXPIRED         Key has expired\r
+    return EFI_TIMEOUT;\r
+\r
+  case  -64: // ENONET              Machine is not on the network\r
+  case  -66: // EREMOTE             Object is remote\r
+  case  -72: // EMULTIHOP           Multihop attempted\r
+  case -100: // ENETDOWN            Network is down\r
+  case -101: // ENETUNREACH         Network is unreachable\r
+  case -112: // EHOSTDOWN           Host is down\r
+  case -113: // EHOSTUNREACH        No route to host\r
+  case -123: // ENOMEDIUM           No medium found\r
+  case -132: // ERFKILL             Operation not possible due to RF-kill\r
+    return EFI_NO_MEDIA;\r
+\r
+  case  -71: // EPROTO              Protocol error\r
+    return EFI_PROTOCOL_ERROR;\r
+\r
+  case  -74: // EBADMSG             Bad message\r
+  case  -77: // EBADFD              File descriptor in bad state\r
+  case  -80: // ELIBBAD             Accessing a corrupted shared library\r
+  case  -81: // ELIBSCN             .lib section in a.out corrupted\r
+  case -117: // EUCLEAN             Structure needs cleaning\r
+    return EFI_VOLUME_CORRUPTED;\r
+\r
+  case  -89: // EDESTADDRREQ        Destination address required\r
+  case -107: // ENOTCONN            Transport endpoint is not connected\r
+    return EFI_NOT_STARTED;\r
+\r
+  default:\r
+    break;\r
+  }\r
+\r
+  return EFI_DEVICE_ERROR;\r
+}\r
+\r
+//\r
+// Parser states for canonicalizing a POSIX pathname.\r
+//\r
+typedef enum {\r
+  ParserInit,   // just starting\r
+  ParserEnd,    // finished\r
+  ParserSlash,  // slash(es) seen\r
+  ParserDot,    // one dot seen since last slash\r
+  ParserDotDot, // two dots seen since last slash\r
+  ParserNormal, // a different sequence seen\r
+} PARSER_STATE;\r
+\r
+/**\r
+  Strip the trailing slash from the parser's output buffer, unless the trailing\r
+  slash stands for the root directory.\r
+\r
+  @param[in] Buffer        The parser's output buffer. Only used for\r
+                           sanity-checking.\r
+\r
+  @param[in,out] Position  On entry, points at the next character to produce\r
+                           (i.e., right past the end of the output written by\r
+                           the parser thus far). The last character in the\r
+                           parser's output buffer is a slash. On return, the\r
+                           slash is stripped, by decrementing Position by one.\r
+                           If this action would remove the slash character\r
+                           standing for the root directory, then the function\r
+                           has no effect.\r
+**/\r
+STATIC\r
+VOID\r
+ParserStripSlash (\r
+  IN     CHAR8 *Buffer,\r
+  IN OUT UINTN *Position\r
+  )\r
+{\r
+  ASSERT (*Position >= 1);\r
+  ASSERT (Buffer[*Position - 1] == '/');\r
+  if (*Position == 1) {\r
+    return;\r
+  }\r
+  (*Position)--;\r
+}\r
+\r
+/**\r
+  Produce one character in the parser's output buffer.\r
+\r
+  @param[out] Buffer       The parser's output buffer. On return, Char8 will\r
+                           have been written.\r
+\r
+  @param[in,out] Position  On entry, points at the next character to produce\r
+                           (i.e., right past the end of the output written by\r
+                           the parser thus far). On return, Position is\r
+                           incremented by one.\r
+\r
+  @param[in] Size          Total allocated size of the parser's output buffer.\r
+                           Used for sanity-checking.\r
+\r
+  @param[in] Char8         The character to place in the output buffer.\r
+**/\r
+STATIC\r
+VOID\r
+ParserCopy (\r
+     OUT CHAR8 *Buffer,\r
+  IN OUT UINTN *Position,\r
+  IN     UINTN Size,\r
+  IN     CHAR8 Char8\r
+  )\r
+{\r
+  ASSERT (*Position < Size);\r
+  Buffer[(*Position)++] = Char8;\r
+}\r
+\r
+/**\r
+  Rewind the last single-dot in the parser's output buffer.\r
+\r
+  @param[in] Buffer        The parser's output buffer. Only used for\r
+                           sanity-checking.\r
+\r
+  @param[in,out] Position  On entry, points at the next character to produce\r
+                           (i.e., right past the end of the output written by\r
+                           the parser thus far); the parser's output buffer\r
+                           ends with the characters ('/', '.'). On return, the\r
+                           dot is rewound by decrementing Position by one; a\r
+                           slash character will reside at the new end of the\r
+                           parser's output buffer.\r
+**/\r
+STATIC\r
+VOID\r
+ParserRewindDot (\r
+  IN     CHAR8 *Buffer,\r
+  IN OUT UINTN *Position\r
+  )\r
+{\r
+  ASSERT (*Position >= 2);\r
+  ASSERT (Buffer[*Position - 1] == '.');\r
+  ASSERT (Buffer[*Position - 2] == '/');\r
+  (*Position)--;\r
+}\r
+\r
+/**\r
+  Rewind the last dot-dot in the parser's output buffer.\r
+\r
+  @param[in] Buffer        The parser's output buffer. Only used for\r
+                           sanity-checking.\r
+\r
+  @param[in,out] Position  On entry, points at the next character to produce\r
+                           (i.e., right past the end of the output written by\r
+                           the parser thus far); the parser's output buffer\r
+                           ends with the characters ('/', '.', '.'). On return,\r
+                           the ('.', '.') pair is rewound unconditionally, by\r
+                           decrementing Position by two; a slash character\r
+                           resides at the new end of the parser's output\r
+                           buffer.\r
+\r
+                           If this slash character stands for the root\r
+                           directory, then RootEscape is set to TRUE.\r
+\r
+                           Otherwise (i.e., if this slash character is not the\r
+                           one standing for the root directory), then the slash\r
+                           character, and the pathname component preceding it,\r
+                           are removed by decrementing Position further. In\r
+                           this case, the slash character preceding the removed\r
+                           pathname component will reside at the new end of the\r
+                           parser's output buffer.\r
+\r
+  @param[out] RootEscape   Set to TRUE on output if the dot-dot component tries\r
+                           to escape the root directory, as described above.\r
+                           Otherwise, RootEscape is not modified.\r
+**/\r
+STATIC\r
+VOID\r
+ParserRewindDotDot (\r
+  IN     CHAR8   *Buffer,\r
+  IN OUT UINTN   *Position,\r
+     OUT BOOLEAN *RootEscape\r
+\r
+  )\r
+{\r
+  ASSERT (*Position >= 3);\r
+  ASSERT (Buffer[*Position - 1] == '.');\r
+  ASSERT (Buffer[*Position - 2] == '.');\r
+  ASSERT (Buffer[*Position - 3] == '/');\r
+  (*Position) -= 2;\r
+\r
+  if (*Position == 1) {\r
+    //\r
+    // Root directory slash reached; don't try to climb higher.\r
+    //\r
+    *RootEscape = TRUE;\r
+    return;\r
+  }\r
+\r
+  //\r
+  // Skip slash.\r
+  //\r
+  (*Position)--;\r
+  //\r
+  // Scan until next slash to the left.\r
+  //\r
+  do {\r
+    ASSERT (*Position > 0);\r
+    (*Position)--;\r
+  } while (Buffer[*Position] != '/');\r
+  (*Position)++;\r
+}\r
+\r
+/**\r
+  Append the UEFI-style RhsPath16 to the POSIX-style, canonical format\r
+  LhsPath8. Output the POSIX-style, canonical format result in ResultPath, as a\r
+  dynamically allocated string.\r
+\r
+  Canonicalization (aka sanitization) establishes the following properties:\r
+  - ResultPath is absolute (starts with "/"),\r
+  - dot (.) and dot-dot (..) components are resolved/eliminated in ResultPath,\r
+    with the usual semantics,\r
+  - ResultPath uses forward slashes,\r
+  - sequences of slashes are squashed in ResultPath,\r
+  - the printable ASCII character set covers ResultPath,\r
+  - CHAR8 encoding is used in ResultPath,\r
+  - no trailing slash present in ResultPath except for the standalone root\r
+    directory,\r
+  - the length of ResultPath is at most VIRTIO_FS_MAX_PATHNAME_LENGTH.\r
+\r
+  Any dot-dot in RhsPath16 that would remove the root directory is dropped, and\r
+  reported through RootEscape, without failing the function call.\r
+\r
+  @param[in] LhsPath8      Identifies the base directory. The caller is\r
+                           responsible for ensuring that LhsPath8 conform to\r
+                           the above canonical pathname format on entry.\r
+\r
+  @param[in] RhsPath16     Identifies the desired file with a UEFI-style CHAR16\r
+                           pathname. If RhsPath16 starts with a backslash, then\r
+                           RhsPath16 is considered absolute, and LhsPath8 is\r
+                           ignored; RhsPath16 is sanitized in isolation, for\r
+                           producing ResultPath8. Otherwise (i.e., if RhsPath16\r
+                           is relative), RhsPath16 is transliterated to CHAR8,\r
+                           and naively appended to LhsPath8. The resultant\r
+                           fused pathname is then sanitized, to produce\r
+                           ResultPath8.\r
+\r
+  @param[out] ResultPath8  The POSIX-style, canonical format pathname that\r
+                           leads to the file desired by the caller. After use,\r
+                           the caller is responsible for freeing ResultPath8.\r
+\r
+  @param[out] RootEscape   Set to TRUE if at least one dot-dot component in\r
+                           RhsPath16 attempted to escape the root directory;\r
+                           set to FALSE otherwise.\r
+\r
+  @retval EFI_SUCCESS            ResultPath8 has been produced. RootEscape has\r
+                                 been output.\r
+\r
+  @retval EFI_INVALID_PARAMETER  RhsPath16 is zero-length.\r
+\r
+  @retval EFI_INVALID_PARAMETER  RhsPath16 failed the\r
+                                 VIRTIO_FS_MAX_PATHNAME_LENGTH check.\r
+\r
+  @retval EFI_OUT_OF_RESOURCES   Memory allocation failed.\r
+\r
+  @retval EFI_OUT_OF_RESOURCES   ResultPath8 would have failed the\r
+                                 VIRTIO_FS_MAX_PATHNAME_LENGTH check.\r
+\r
+  @retval EFI_UNSUPPORTED        RhsPath16 contains a character that either\r
+                                 falls outside of the printable ASCII set, or\r
+                                 is a forward slash.\r
+**/\r
+EFI_STATUS\r
+VirtioFsAppendPath (\r
+  IN     CHAR8   *LhsPath8,\r
+  IN     CHAR16  *RhsPath16,\r
+     OUT CHAR8   **ResultPath8,\r
+     OUT BOOLEAN *RootEscape\r
+  )\r
+{\r
+  UINTN        RhsLen;\r
+  CHAR8        *RhsPath8;\r
+  UINTN        Idx;\r
+  EFI_STATUS   Status;\r
+  UINTN        SizeToSanitize;\r
+  CHAR8        *BufferToSanitize;\r
+  CHAR8        *SanitizedBuffer;\r
+  PARSER_STATE State;\r
+  UINTN        SanitizedPosition;\r
+\r
+  //\r
+  // Appending an empty pathname is not allowed.\r
+  //\r
+  RhsLen = StrLen (RhsPath16);\r
+  if (RhsLen == 0) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+  //\r
+  // Enforce length restriction on RhsPath16.\r
+  //\r
+  if (RhsLen > VIRTIO_FS_MAX_PATHNAME_LENGTH) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  //\r
+  // Transliterate RhsPath16 to RhsPath8 by:\r
+  // - rejecting RhsPath16 if a character outside of printable ASCII is seen,\r
+  // - rejecting RhsPath16 if a forward slash is seen,\r
+  // - replacing backslashes with forward slashes,\r
+  // - casting the characters from CHAR16 to CHAR8.\r
+  //\r
+  RhsPath8 = AllocatePool (RhsLen + 1);\r
+  if (RhsPath8 == NULL) {\r
+    return EFI_OUT_OF_RESOURCES;\r
+  }\r
+  for (Idx = 0; RhsPath16[Idx] != L'\0'; Idx++) {\r
+    if (RhsPath16[Idx] < 0x20 || RhsPath16[Idx] > 0x7E ||\r
+        RhsPath16[Idx] == L'/') {\r
+      Status = EFI_UNSUPPORTED;\r
+      goto FreeRhsPath8;\r
+    }\r
+    RhsPath8[Idx] = (CHAR8)((RhsPath16[Idx] == L'\\') ? L'/' : RhsPath16[Idx]);\r
+  }\r
+  RhsPath8[Idx++] = '\0';\r
+\r
+  //\r
+  // Now prepare the input for the canonicalization (squashing of sequences of\r
+  // forward slashes, and eliminating . (dot) and .. (dot-dot) pathname\r
+  // components).\r
+  //\r
+  // The sanitized path can never be longer than the naive concatenation of the\r
+  // left hand side and right hand side paths, so we'll use the catenated size\r
+  // for allocating the sanitized output too.\r
+  //\r
+  if (RhsPath8[0] == '/') {\r
+    //\r
+    // If the right hand side path is absolute, then it is not appended to the\r
+    // left hand side path -- it *replaces* the left hand side path.\r
+    //\r
+    SizeToSanitize = RhsLen + 1;\r
+    BufferToSanitize = RhsPath8;\r
+  } else {\r
+    //\r
+    // If the right hand side path is relative, then it is appended (naively)\r
+    // to the left hand side.\r
+    //\r
+    UINTN LhsLen;\r
+\r
+    LhsLen = AsciiStrLen (LhsPath8);\r
+    SizeToSanitize = LhsLen + 1 + RhsLen + 1;\r
+    BufferToSanitize = AllocatePool (SizeToSanitize);\r
+    if (BufferToSanitize == NULL) {\r
+      Status = EFI_OUT_OF_RESOURCES;\r
+      goto FreeRhsPath8;\r
+    }\r
+    CopyMem (BufferToSanitize, LhsPath8, LhsLen);\r
+    BufferToSanitize[LhsLen] = '/';\r
+    CopyMem (BufferToSanitize + LhsLen + 1, RhsPath8, RhsLen + 1);\r
+  }\r
+\r
+  //\r
+  // Allocate the output buffer.\r
+  //\r
+  SanitizedBuffer = AllocatePool (SizeToSanitize);\r
+  if (SanitizedBuffer == NULL) {\r
+    Status = EFI_OUT_OF_RESOURCES;\r
+    goto FreeBufferToSanitize;\r
+  }\r
+\r
+  //\r
+  // State machine for parsing the input and producing the canonical output\r
+  // follows.\r
+  //\r
+  *RootEscape       = FALSE;\r
+  Idx               = 0;\r
+  State             = ParserInit;\r
+  SanitizedPosition = 0;\r
+  do {\r
+    CHAR8 Chr8;\r
+\r
+    ASSERT (Idx < SizeToSanitize);\r
+    Chr8 = BufferToSanitize[Idx++];\r
+\r
+    switch (State) {\r
+    case ParserInit: // just starting\r
+      ASSERT (Chr8 == '/');\r
+      ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+      State = ParserSlash;\r
+      break;\r
+\r
+    case ParserSlash: // slash(es) seen\r
+      switch (Chr8) {\r
+      case '\0':\r
+        ParserStripSlash (SanitizedBuffer, &SanitizedPosition);\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserEnd;\r
+        break;\r
+      case '/':\r
+        //\r
+        // skip & stay in same state\r
+        //\r
+        break;\r
+      case '.':\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserDot;\r
+        break;\r
+      default:\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserNormal;\r
+        break;\r
+      }\r
+      break;\r
+\r
+    case ParserDot: // one dot seen since last slash\r
+      switch (Chr8) {\r
+      case '\0':\r
+        ParserRewindDot (SanitizedBuffer, &SanitizedPosition);\r
+        ParserStripSlash (SanitizedBuffer, &SanitizedPosition);\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserEnd;\r
+        break;\r
+      case '/':\r
+        ParserRewindDot (SanitizedBuffer, &SanitizedPosition);\r
+        State = ParserSlash;\r
+        break;\r
+      case '.':\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserDotDot;\r
+        break;\r
+      default:\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserNormal;\r
+        break;\r
+      }\r
+      break;\r
+\r
+    case ParserDotDot: // two dots seen since last slash\r
+      switch (Chr8) {\r
+      case '\0':\r
+        ParserRewindDotDot (SanitizedBuffer, &SanitizedPosition, RootEscape);\r
+        ParserStripSlash (SanitizedBuffer, &SanitizedPosition);\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserEnd;\r
+        break;\r
+      case '/':\r
+        ParserRewindDotDot (SanitizedBuffer, &SanitizedPosition, RootEscape);\r
+        State = ParserSlash;\r
+        break;\r
+      case '.':\r
+        //\r
+        // fall through\r
+        //\r
+      default:\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserNormal;\r
+        break;\r
+      }\r
+      break;\r
+\r
+    case ParserNormal: // a different sequence seen\r
+      switch (Chr8) {\r
+      case '\0':\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserEnd;\r
+        break;\r
+      case '/':\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        State = ParserSlash;\r
+        break;\r
+      case '.':\r
+        //\r
+        // fall through\r
+        //\r
+      default:\r
+        //\r
+        // copy and stay in same state\r
+        //\r
+        ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8);\r
+        break;\r
+      }\r
+      break;\r
+\r
+    default:\r
+      ASSERT (FALSE);\r
+      break;\r
+    }\r
+  } while (State != ParserEnd);\r
+\r
+  //\r
+  // Ensure length invariant on ResultPath8.\r
+  //\r
+  ASSERT (SanitizedPosition >= 2);\r
+  if (SanitizedPosition - 1 > VIRTIO_FS_MAX_PATHNAME_LENGTH) {\r
+    Status = EFI_OUT_OF_RESOURCES;\r
+    goto FreeSanitizedBuffer;\r
+  }\r
+\r
+  *ResultPath8    = SanitizedBuffer;\r
+  SanitizedBuffer = NULL;\r
+  Status          = EFI_SUCCESS;\r
+  //\r
+  // Fall through.\r
+  //\r
+FreeSanitizedBuffer:\r
+  if (SanitizedBuffer != NULL) {\r
+    FreePool (SanitizedBuffer);\r
+  }\r
+\r
+FreeBufferToSanitize:\r
+  if (RhsPath8[0] != '/') {\r
+    FreePool (BufferToSanitize);\r
+  }\r
+\r
+FreeRhsPath8:\r
+  FreePool (RhsPath8);\r
+  return Status;\r
+}\r
+\r
+/**\r
+  For a given canonical pathname (as defined at VirtioFsAppendPath()), look up\r
+  the NodeId of the most specific parent directory, plus output a pointer to\r
+  the last pathname component (which is therefore a direct child of said parent\r
+  directory).\r
+\r
+  The function may only be called after VirtioFsFuseInitSession() returns\r
+  successfully and before VirtioFsUninit() is called.\r
+\r
+  @param[in,out] VirtioFs    The Virtio Filesystem device to send FUSE_LOOKUP\r
+                             and FUSE_FORGET requests to. On output, the FUSE\r
+                             request counter "VirtioFs->RequestId" will have\r
+                             been incremented several times.\r
+\r
+  @param[in,out] Path        The canonical pathname (as defined in the\r
+                             description of VirtioFsAppendPath()) to split.\r
+                             Path is modified in-place temporarily; however, on\r
+                             return (successful or otherwise), Path reassumes\r
+                             its original contents.\r
+\r
+  @param[out] DirNodeId      The NodeId of the most specific parent directory\r
+                             identified by Path. The caller is responsible for\r
+                             sending a FUSE_FORGET request to the Virtio\r
+                             Filesystem device for DirNodeId -- unless\r
+                             DirNodeId equals VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID\r
+                             --, when DirNodeId's use ends.\r
+\r
+  @param[out] LastComponent  A pointer into Path, pointing at the start of the\r
+                             last pathname component.\r
+\r
+  @retval EFI_SUCCESS            Splitting successful.\r
+\r
+  @retval EFI_INVALID_PARAMETER  Path is "/".\r
+\r
+  @retval EFI_ACCESS_DENIED      One of the components on Path before the last\r
+                                 is not a directory.\r
+\r
+  @return                        Error codes propagated from\r
+                                 VirtioFsFuseLookup() and\r
+                                 VirtioFsFuseAttrToEfiFileInfo().\r
+**/\r
+EFI_STATUS\r
+VirtioFsLookupMostSpecificParentDir (\r
+  IN OUT VIRTIO_FS *VirtioFs,\r
+  IN OUT CHAR8     *Path,\r
+     OUT UINT64    *DirNodeId,\r
+     OUT CHAR8     **LastComponent\r
+  )\r
+{\r
+  UINT64     ParentDirNodeId;\r
+  CHAR8      *Slash;\r
+  EFI_STATUS Status;\r
+  UINT64     NextDirNodeId;\r
+\r
+  if (AsciiStrCmp (Path, "/") == 0) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  ParentDirNodeId = VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID;\r
+  Slash           = Path;\r
+  for (;;) {\r
+    CHAR8                              *NextSlash;\r
+    VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;\r
+    EFI_FILE_INFO                      FileInfo;\r
+\r
+    //\r
+    // Find the slash (if any) that terminates the next pathname component.\r
+    //\r
+    NextSlash = AsciiStrStr (Slash + 1, "/");\r
+    if (NextSlash == NULL) {\r
+      break;\r
+    }\r
+\r
+    //\r
+    // Temporarily replace the found slash character with a NUL in-place, for\r
+    // easy construction of the single-component filename that we need to look\r
+    // up.\r
+    //\r
+    *NextSlash = '\0';\r
+    Status = VirtioFsFuseLookup (VirtioFs, ParentDirNodeId, Slash + 1,\r
+               &NextDirNodeId, &FuseAttr);\r
+    *NextSlash = '/';\r
+\r
+    //\r
+    // We're done with the directory inode that was the basis for the lookup.\r
+    //\r
+    if (ParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {\r
+      VirtioFsFuseForget (VirtioFs, ParentDirNodeId);\r
+    }\r
+\r
+    //\r
+    // If we couldn't look up the next *non-final* pathname component, bail.\r
+    //\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+\r
+    //\r
+    // Lookup successful; now check if the next (non-final) component is a\r
+    // directory. If not, bail.\r
+    //\r
+    Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo);\r
+    if (EFI_ERROR (Status)) {\r
+      goto ForgetNextDirNodeId;\r
+    }\r
+    if ((FileInfo.Attribute & EFI_FILE_DIRECTORY) == 0) {\r
+      Status = EFI_ACCESS_DENIED;\r
+      goto ForgetNextDirNodeId;\r
+    }\r
+\r
+    //\r
+    // Advance.\r
+    //\r
+    ParentDirNodeId = NextDirNodeId;\r
+    Slash           = NextSlash;\r
+  }\r
+\r
+  //\r
+  // ParentDirNodeId corresponds to the last containing directory. The\r
+  // remaining single-component filename represents a direct child under that\r
+  // directory. Said filename starts at (Slash + 1).\r
+  //\r
+  *DirNodeId     = ParentDirNodeId;\r
+  *LastComponent = Slash + 1;\r
+  return EFI_SUCCESS;\r
+\r
+ForgetNextDirNodeId:\r
+  VirtioFsFuseForget (VirtioFs, NextDirNodeId);\r
+  return Status;\r
+}\r
+\r
+/**\r
+  Format the last component of a canonical pathname into a caller-provided\r
+  CHAR16 array.\r
+\r
+  @param[in] Path              The canonical pathname (as defined in the\r
+                               description of VirtioFsAppendPath()) to format\r
+                               the last component of.\r
+\r
+  @param[out] Basename         If BasenameSize is zero on input, Basename may\r
+                               be NULL. Otherwise, Basename is allocated by the\r
+                               caller. On successful return, Basename contains\r
+                               the last component of Path, formatted as a\r
+                               NUL-terminated CHAR16 string. When Path is "/"\r
+                               on input, Basename is L"" on output.\r
+\r
+  @param[in,out] BasenameSize  On input, the number of bytes the caller\r
+                               provides in Basename. On output, regardless of\r
+                               return value, the number of bytes required for\r
+                               formatting Basename, including the terminating\r
+                               L'\0'.\r
+\r
+  @retval EFI_SUCCESS           Basename has been filled in.\r
+\r
+  @retval EFI_BUFFER_TOO_SMALL  BasenameSize was too small on input; Basename\r
+                                has not been modified.\r
+**/\r
+EFI_STATUS\r
+VirtioFsGetBasename (\r
+  IN     CHAR8  *Path,\r
+     OUT CHAR16 *Basename     OPTIONAL,\r
+  IN OUT UINTN  *BasenameSize\r
+  )\r
+{\r
+  UINTN AllocSize;\r
+  UINTN LastComponent;\r
+  UINTN Idx;\r
+  UINTN PathSize;\r
+\r
+  AllocSize = *BasenameSize;\r
+\r
+  LastComponent = MAX_UINTN;\r
+  for (Idx = 0; Path[Idx] != '\0'; Idx++) {\r
+    if (Path[Idx] == '/') {\r
+      LastComponent = Idx;\r
+    }\r
+  }\r
+  PathSize = Idx + 1;\r
+  ASSERT (LastComponent < MAX_UINTN);\r
+  LastComponent++;\r
+  *BasenameSize = (PathSize - LastComponent) * sizeof Basename[0];\r
+\r
+  if (*BasenameSize > AllocSize) {\r
+    return EFI_BUFFER_TOO_SMALL;\r
+  }\r
+\r
+  for (Idx = LastComponent; Idx < PathSize; Idx++) {\r
+    Basename[Idx - LastComponent] = Path[Idx];\r
+  }\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Format the destination of a rename/move operation as a dynamically allocated\r
+  canonical pathname.\r
+\r
+  Any dot-dot in RhsPath16 that would remove the root directory is dropped, and\r
+  reported through RootEscape, without failing the function call.\r
+\r
+  @param[in] LhsPath8     The source pathname operand of the rename/move\r
+                          operation, expressed as a canonical pathname (as\r
+                          defined in the description of VirtioFsAppendPath()).\r
+                          The root directory "/" cannot be renamed/moved, and\r
+                          will be rejected.\r
+\r
+  @param[in] RhsPath16    The destination pathname operand expressed as a\r
+                          UEFI-style CHAR16 pathname.\r
+\r
+                          If RhsPath16 starts with a backslash, then RhsPath16\r
+                          is considered absolute. Otherwise, RhsPath16 is\r
+                          interpreted relative to the most specific parent\r
+                          directory found in LhsPath8.\r
+\r
+                          Independently, if RhsPath16 ends with a backslash\r
+                          (i.e., RhsPath16 is given in the "move into\r
+                          directory" convenience form), then RhsPath16 is\r
+                          interpreted with the basename of LhsPath8 appended.\r
+                          Otherwise, the last pathname component of RhsPath16\r
+                          is taken as the last pathname component of the\r
+                          rename/move destination.\r
+\r
+                          An empty RhsPath16 is rejected.\r
+\r
+  @param[out] ResultPath8  The POSIX-style, canonical format pathname that\r
+                           leads to the renamed/moved file. After use, the\r
+                           caller is responsible for freeing ResultPath8.\r
+\r
+  @param[out] RootEscape   Set to TRUE if at least one dot-dot component in\r
+                           RhsPath16 attempted to escape the root directory;\r
+                           set to FALSE otherwise.\r
+\r
+  @retval EFI_SUCCESS            ResultPath8 has been produced. RootEscape has\r
+                                 been output.\r
+\r
+  @retval EFI_INVALID_PARAMETER  LhsPath8 is "/".\r
+\r
+  @retval EFI_INVALID_PARAMETER  RhsPath16 is zero-length.\r
+\r
+  @retval EFI_INVALID_PARAMETER  RhsPath16 failed the\r
+                                 VIRTIO_FS_MAX_PATHNAME_LENGTH check.\r
+\r
+  @retval EFI_OUT_OF_RESOURCES   Memory allocation failed.\r
+\r
+  @retval EFI_OUT_OF_RESOURCES   ResultPath8 would have failed the\r
+                                 VIRTIO_FS_MAX_PATHNAME_LENGTH check.\r
+\r
+  @retval EFI_UNSUPPORTED        RhsPath16 contains a character that either\r
+                                 falls outside of the printable ASCII set, or\r
+                                 is a forward slash.\r
+**/\r
+EFI_STATUS\r
+VirtioFsComposeRenameDestination (\r
+  IN     CHAR8   *LhsPath8,\r
+  IN     CHAR16  *RhsPath16,\r
+     OUT CHAR8   **ResultPath8,\r
+     OUT BOOLEAN *RootEscape\r
+  )\r
+{\r
+  //\r
+  // Lengths are expressed as numbers of characters (CHAR8 or CHAR16),\r
+  // excluding terminating NULs. Sizes are expressed as byte counts, including\r
+  // the bytes taken up by terminating NULs.\r
+  //\r
+  UINTN      RhsLen;\r
+  UINTN      LhsBasename16Size;\r
+  EFI_STATUS Status;\r
+  UINTN      LhsBasenameLen;\r
+  UINTN      DestSuffix16Size;\r
+  CHAR16     *DestSuffix16;\r
+  CHAR8      *DestPrefix8;\r
+\r
+  //\r
+  // An empty destination operand for the rename/move operation is not allowed.\r
+  //\r
+  RhsLen = StrLen (RhsPath16);\r
+  if (RhsLen == 0) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+  //\r
+  // Enforce length restriction on RhsPath16.\r
+  //\r
+  if (RhsLen > VIRTIO_FS_MAX_PATHNAME_LENGTH) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  //\r
+  // Determine the length of the basename of LhsPath8.\r
+  //\r
+  LhsBasename16Size = 0;\r
+  Status = VirtioFsGetBasename (LhsPath8, NULL, &LhsBasename16Size);\r
+  ASSERT (Status == EFI_BUFFER_TOO_SMALL);\r
+  ASSERT (LhsBasename16Size >= sizeof (CHAR16));\r
+  ASSERT (LhsBasename16Size % sizeof (CHAR16) == 0);\r
+  LhsBasenameLen = LhsBasename16Size / sizeof (CHAR16) - 1;\r
+  if (LhsBasenameLen == 0) {\r
+    //\r
+    // The root directory cannot be renamed/moved.\r
+    //\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  //\r
+  // Resolve the "move into directory" convenience form in RhsPath16.\r
+  //\r
+  if (RhsPath16[RhsLen - 1] == L'\\') {\r
+    //\r
+    // Append the basename of LhsPath8 as a CHAR16 string to RhsPath16.\r
+    //\r
+    DestSuffix16Size = RhsLen * sizeof (CHAR16) + LhsBasename16Size;\r
+    DestSuffix16 = AllocatePool (DestSuffix16Size);\r
+    if (DestSuffix16 == NULL) {\r
+      return EFI_OUT_OF_RESOURCES;\r
+    }\r
+    CopyMem (DestSuffix16, RhsPath16, RhsLen * sizeof (CHAR16));\r
+    Status = VirtioFsGetBasename (LhsPath8, DestSuffix16 + RhsLen,\r
+               &LhsBasename16Size);\r
+    ASSERT_EFI_ERROR (Status);\r
+  } else {\r
+    //\r
+    // Just create a copy of RhsPath16.\r
+    //\r
+    DestSuffix16Size = (RhsLen + 1) * sizeof (CHAR16);\r
+    DestSuffix16 = AllocateCopyPool (DestSuffix16Size, RhsPath16);\r
+    if (DestSuffix16 == NULL) {\r
+      return EFI_OUT_OF_RESOURCES;\r
+    }\r
+  }\r
+\r
+  //\r
+  // If the destination operand is absolute, it will be interpreted relative to\r
+  // the root directory.\r
+  //\r
+  // Otherwise (i.e., if the destination operand is relative), then create the\r
+  // canonical pathname that the destination operand is interpreted relatively\r
+  // to; that is, the canonical pathname of the most specific parent directory\r
+  // found in LhsPath8.\r
+  //\r
+  if (DestSuffix16[0] == L'\\') {\r
+    DestPrefix8 = AllocateCopyPool (sizeof "/", "/");\r
+    if (DestPrefix8 == NULL) {\r
+      Status = EFI_OUT_OF_RESOURCES;\r
+      goto FreeDestSuffix16;\r
+    }\r
+  } else {\r
+    UINTN LhsLen;\r
+    UINTN DestPrefixLen;\r
+\r
+    //\r
+    // Strip the basename of LhsPath8.\r
+    //\r
+    LhsLen = AsciiStrLen (LhsPath8);\r
+    ASSERT (LhsBasenameLen < LhsLen);\r
+    DestPrefixLen = LhsLen - LhsBasenameLen;\r
+    ASSERT (LhsPath8[DestPrefixLen - 1] == '/');\r
+    //\r
+    // If we're not at the root directory, strip the slash too.\r
+    //\r
+    if (DestPrefixLen > 1) {\r
+      DestPrefixLen--;\r
+    }\r
+    DestPrefix8 = AllocatePool (DestPrefixLen + 1);\r
+    if (DestPrefix8 == NULL) {\r
+      Status = EFI_OUT_OF_RESOURCES;\r
+      goto FreeDestSuffix16;\r
+    }\r
+    CopyMem (DestPrefix8, LhsPath8, DestPrefixLen);\r
+    DestPrefix8[DestPrefixLen] = '\0';\r
+  }\r
+\r
+  //\r
+  // Now combine DestPrefix8 and DestSuffix16 into the final canonical\r
+  // pathname.\r
+  //\r
+  Status = VirtioFsAppendPath (DestPrefix8, DestSuffix16, ResultPath8,\r
+             RootEscape);\r
+\r
+  FreePool (DestPrefix8);\r
+  //\r
+  // Fall through.\r
+  //\r
+FreeDestSuffix16:\r
+  FreePool (DestSuffix16);\r
+\r
+  return Status;\r
+}\r
+\r
+/**\r
+  Convert select fields of a VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE object to\r
+  corresponding fields in EFI_FILE_INFO.\r
+\r
+  @param[in] FuseAttr   The VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE object to\r
+                        convert the relevant fields from.\r
+\r
+  @param[out] FileInfo  The EFI_FILE_INFO structure to modify. Importantly, the\r
+                        FileInfo->Size and FileInfo->FileName fields are not\r
+                        overwritten.\r
+\r
+  @retval EFI_SUCCESS      Conversion successful.\r
+\r
+  @retval EFI_UNSUPPORTED  The allocated size of the file is inexpressible in\r
+                           EFI_FILE_INFO.\r
+\r
+  @retval EFI_UNSUPPORTED  One of the file access times is inexpressible in\r
+                           EFI_FILE_INFO.\r
+\r
+  @retval EFI_UNSUPPORTED  The file type is inexpressible in EFI_FILE_INFO.\r
+\r
+  @retval EFI_UNSUPPORTED  The file is a regular file that has multiple names\r
+                           on the host side (i.e., its hard link count is\r
+                           greater than one).\r
+**/\r
+EFI_STATUS\r
+VirtioFsFuseAttrToEfiFileInfo (\r
+  IN     VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE *FuseAttr,\r
+     OUT EFI_FILE_INFO                      *FileInfo\r
+  )\r
+{\r
+  UINT64   EpochTime[3];\r
+  EFI_TIME *ConvertedTime[ARRAY_SIZE (EpochTime)];\r
+  UINTN    Idx;\r
+\r
+  FileInfo->FileSize = FuseAttr->Size;\r
+\r
+  //\r
+  // The unit for FuseAttr->Blocks is 512B.\r
+  //\r
+  if (FuseAttr->Blocks >= BIT55) {\r
+    return EFI_UNSUPPORTED;\r
+  }\r
+  FileInfo->PhysicalSize = LShiftU64 (FuseAttr->Blocks, 9);\r
+\r
+  //\r
+  // Convert the timestamps. File creation time is not tracked by the Virtio\r
+  // Filesystem device, so set FileInfo->CreateTime from FuseAttr->Mtime as\r
+  // well.\r
+  //\r
+  EpochTime[0]     = FuseAttr->Mtime;\r
+  EpochTime[1]     = FuseAttr->Atime;\r
+  EpochTime[2]     = FuseAttr->Mtime;\r
+  ConvertedTime[0] = &FileInfo->CreateTime;\r
+  ConvertedTime[1] = &FileInfo->LastAccessTime;\r
+  ConvertedTime[2] = &FileInfo->ModificationTime;\r
+\r
+  for (Idx = 0; Idx < ARRAY_SIZE (EpochTime); Idx++) {\r
+    //\r
+    // EpochToEfiTime() takes a UINTN for seconds.\r
+    //\r
+    if (EpochTime[Idx] > MAX_UINTN) {\r
+      return EFI_UNSUPPORTED;\r
+    }\r
+    //\r
+    // Set the following fields in the converted time: Year, Month, Day, Hour,\r
+    // Minute, Second, Nanosecond.\r
+    //\r
+    EpochToEfiTime ((UINTN)EpochTime[Idx], ConvertedTime[Idx]);\r
+    //\r
+    // The times are all expressed in UTC. Consequently, they are not affected\r
+    // by daylight saving.\r
+    //\r
+    ConvertedTime[Idx]->TimeZone = 0;\r
+    ConvertedTime[Idx]->Daylight = 0;\r
+    //\r
+    // Clear the padding fields.\r
+    //\r
+    ConvertedTime[Idx]->Pad1 = 0;\r
+    ConvertedTime[Idx]->Pad2 = 0;\r
+  }\r
+\r
+  //\r
+  // Set the attributes.\r
+  //\r
+  switch (FuseAttr->Mode & VIRTIO_FS_FUSE_MODE_TYPE_MASK) {\r
+  case VIRTIO_FS_FUSE_MODE_TYPE_DIR:\r
+    FileInfo->Attribute = EFI_FILE_DIRECTORY;\r
+    break;\r
+  case VIRTIO_FS_FUSE_MODE_TYPE_REG:\r
+    FileInfo->Attribute = 0;\r
+    break;\r
+  default:\r
+    //\r
+    // Other file types are not supported.\r
+    //\r
+    return EFI_UNSUPPORTED;\r
+  }\r
+  //\r
+  // Report the regular file or directory as read-only if all classes lack\r
+  // write permission.\r
+  //\r
+  if ((FuseAttr->Mode & (VIRTIO_FS_FUSE_MODE_PERM_WUSR |\r
+                         VIRTIO_FS_FUSE_MODE_PERM_WGRP |\r
+                         VIRTIO_FS_FUSE_MODE_PERM_WOTH)) == 0) {\r
+    FileInfo->Attribute |= EFI_FILE_READ_ONLY;\r
+  }\r
+\r
+  //\r
+  // A hard link count greater than 1 is not supported for regular files.\r
+  //\r
+  if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) == 0 && FuseAttr->Nlink > 1) {\r
+    return EFI_UNSUPPORTED;\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Convert a VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE filename to an EFI_FILE_INFO\r
+  filename.\r
+\r
+  @param[in] FuseDirent    The VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE object to\r
+                           convert the filename byte array from. The caller is\r
+                           responsible for ensuring that FuseDirent->Namelen\r
+                           describe valid storage.\r
+\r
+  @param[in,out] FileInfo  The EFI_FILE_INFO structure to modify. On input, the\r
+                           caller is responsible for setting FileInfo->Size\r
+                           according to the allocated size. On successful\r
+                           return, FileInfo->Size is reduced to reflect the\r
+                           filename converted into FileInfo->FileName.\r
+                           FileInfo->FileName is set from the filename byte\r
+                           array that directly follows the FuseDirent header\r
+                           object. Fields other than FileInfo->Size and\r
+                           FileInfo->FileName are not modified.\r
+\r
+  @retval EFI_SUCCESS            Conversion successful.\r
+\r
+  @retval EFI_INVALID_PARAMETER  VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE()\r
+                                 returns zero for FuseDirent->Namelen.\r
+\r
+  @retval EFI_BUFFER_TOO_SMALL   On input, FileInfo->Size does not provide\r
+                                 enough room for converting the filename byte\r
+                                 array from FuseDirent.\r
+\r
+  @retval EFI_UNSUPPORTED        The FuseDirent filename byte array contains a\r
+                                 byte that falls outside of the printable ASCII\r
+                                 range, or is a forward slash or a backslash.\r
+**/\r
+EFI_STATUS\r
+VirtioFsFuseDirentPlusToEfiFileInfo (\r
+  IN     VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *FuseDirent,\r
+  IN OUT EFI_FILE_INFO                      *FileInfo\r
+  )\r
+{\r
+  UINTN  DirentSize;\r
+  UINTN  FileInfoSize;\r
+  UINT8  *DirentName;\r
+  UINT32 Idx;\r
+\r
+  DirentSize = VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (FuseDirent->Namelen);\r
+  if (DirentSize == 0) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+  //\r
+  // We're now safe from overflow in the calculation below.\r
+  //\r
+  FileInfoSize = (OFFSET_OF (EFI_FILE_INFO, FileName) +\r
+                  ((UINTN)FuseDirent->Namelen + 1) * sizeof (CHAR16));\r
+  if (FileInfoSize > FileInfo->Size) {\r
+    return EFI_BUFFER_TOO_SMALL;\r
+  }\r
+\r
+  //\r
+  // Convert the name.\r
+  //\r
+  DirentName = (UINT8 *)(FuseDirent + 1);\r
+  for (Idx = 0; Idx < FuseDirent->Namelen; Idx++) {\r
+    UINT8 NameByte;\r
+\r
+    NameByte = DirentName[Idx];\r
+    if (NameByte < 0x20 || NameByte > 0x7E ||\r
+        NameByte == '/' || NameByte == '\\') {\r
+      return EFI_UNSUPPORTED;\r
+    }\r
+    FileInfo->FileName[Idx] = (CHAR16)NameByte;\r
+  }\r
+  FileInfo->FileName[Idx++] = L'\0';\r
+  //\r
+  // Set the (possibly reduced) size.\r
+  //\r
+  FileInfo->Size = FileInfoSize;\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Given an EFI_FILE_INFO object received in an EFI_FILE_PROTOCOL.SetInfo()\r
+  call, determine whether updating the size of the file is necessary, relative\r
+  to an EFI_FILE_INFO object describing the current state of the file.\r
+\r
+  @param[in] Info     The EFI_FILE_INFO describing the current state of the\r
+                      file. The caller is responsible for populating Info on\r
+                      input with VirtioFsFuseAttrToEfiFileInfo(), from the\r
+                      current FUSE attributes of the file. The Info->Size and\r
+                      Info->FileName members are ignored.\r
+\r
+  @param[in] NewInfo  The EFI_FILE_INFO object received in the\r
+                      EFI_FILE_PROTOCOL.SetInfo() call.\r
+\r
+  @param[out] Update  Set to TRUE on output if the file size needs to be\r
+                      updated. Set to FALSE otherwise.\r
+\r
+  @param[out] Size    If Update is set to TRUE, then Size provides the new file\r
+                      size to set. Otherwise, Size is not written to.\r
+**/\r
+VOID\r
+VirtioFsGetFuseSizeUpdate (\r
+  IN     EFI_FILE_INFO *Info,\r
+  IN     EFI_FILE_INFO *NewInfo,\r
+     OUT BOOLEAN       *Update,\r
+     OUT UINT64        *Size\r
+  )\r
+{\r
+  BOOLEAN IsDirectory;\r
+\r
+  IsDirectory = (BOOLEAN)((Info->Attribute & EFI_FILE_DIRECTORY) != 0);\r
+\r
+  if (IsDirectory || Info->FileSize == NewInfo->FileSize) {\r
+    *Update = FALSE;\r
+    return;\r
+  }\r
+  *Update = TRUE;\r
+  *Size = NewInfo->FileSize;\r
+}\r
+\r
+/**\r
+  Given an EFI_FILE_INFO object received in an EFI_FILE_PROTOCOL.SetInfo()\r
+  call, determine whether updating the last access time and/or the last\r
+  modification time of the file is necessary, relative to an EFI_FILE_INFO\r
+  object describing the current state of the file.\r
+\r
+  @param[in] Info          The EFI_FILE_INFO describing the current state of\r
+                           the file. The caller is responsible for populating\r
+                           Info on input with VirtioFsFuseAttrToEfiFileInfo(),\r
+                           from the current FUSE attributes of the file. The\r
+                           Info->Size and Info->FileName members are ignored.\r
+\r
+  @param[in] NewInfo       The EFI_FILE_INFO object received in the\r
+                           EFI_FILE_PROTOCOL.SetInfo() call.\r
+\r
+  @param[out] UpdateAtime  Set to TRUE on output if the last access time needs\r
+                           to be updated. Set to FALSE otherwise.\r
+\r
+  @param[out] UpdateMtime  Set to TRUE on output if the last modification time\r
+                           needs to be updated. Set to FALSE otherwise.\r
+\r
+  @param[out] Atime        If UpdateAtime is set to TRUE, then Atime provides\r
+                           the last access timestamp to set (as seconds since\r
+                           the Epoch). Otherwise, Atime is not written to.\r
+\r
+  @param[out] Mtime        If UpdateMtime is set to TRUE, then Mtime provides\r
+                           the last modification timestamp to set (as seconds\r
+                           since the Epoch). Otherwise, Mtime is not written\r
+                           to.\r
+\r
+  @retval EFI_SUCCESS            Output parameters have been set successfully.\r
+\r
+  @retval EFI_INVALID_PARAMETER  At least one of the CreateTime, LastAccessTime\r
+                                 and ModificationTime fields in NewInfo\r
+                                 represents an actual update relative to the\r
+                                 current state of the file (expressed in Info),\r
+                                 but does not satisfy the UEFI spec\r
+                                 requirements on EFI_TIME.\r
+\r
+  @retval EFI_ACCESS_DENIED      NewInfo requests changing both CreateTime and\r
+                                 ModificationTime, but to values that differ\r
+                                 from each other. The Virtio Filesystem device\r
+                                 does not support this.\r
+**/\r
+EFI_STATUS\r
+VirtioFsGetFuseTimeUpdates (\r
+  IN     EFI_FILE_INFO *Info,\r
+  IN     EFI_FILE_INFO *NewInfo,\r
+     OUT BOOLEAN       *UpdateAtime,\r
+     OUT BOOLEAN       *UpdateMtime,\r
+     OUT UINT64        *Atime,\r
+     OUT UINT64        *Mtime\r
+  )\r
+{\r
+  EFI_TIME              *Time[3];\r
+  EFI_TIME              *NewTime[ARRAY_SIZE (Time)];\r
+  UINTN                 Idx;\r
+  STATIC CONST EFI_TIME ZeroTime = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\r
+  BOOLEAN               Change[ARRAY_SIZE (Time)];\r
+  UINT64                Seconds[ARRAY_SIZE (Time)];\r
+\r
+  Time[0]    = &Info->CreateTime;\r
+  Time[1]    = &Info->LastAccessTime;\r
+  Time[2]    = &Info->ModificationTime;\r
+  NewTime[0] = &NewInfo->CreateTime;\r
+  NewTime[1] = &NewInfo->LastAccessTime;\r
+  NewTime[2] = &NewInfo->ModificationTime;\r
+\r
+  //\r
+  // Determine which timestamps differ from the current state. (A zero time\r
+  // means "don't update", per UEFI spec.) For each timestamp that's being\r
+  // changed, calculate the seconds since the Epoch.\r
+  //\r
+  for (Idx = 0; Idx < ARRAY_SIZE (Time); Idx++) {\r
+    if (CompareMem (NewTime[Idx], &ZeroTime, sizeof (EFI_TIME)) == 0 ||\r
+        CompareMem (NewTime[Idx], Time[Idx], sizeof (EFI_TIME)) == 0) {\r
+      Change[Idx] = FALSE;\r
+    } else {\r
+      if (!IsTimeValid (NewTime[Idx])) {\r
+        return EFI_INVALID_PARAMETER;\r
+      }\r
+      Change[Idx] = TRUE;\r
+      Seconds[Idx] = EfiTimeToEpoch (NewTime[Idx]);\r
+    }\r
+  }\r
+\r
+  //\r
+  // If a change is requested for exactly one of CreateTime and\r
+  // ModificationTime, we'll change the last modification time. If changes are\r
+  // requested for both, and to the same timestamp, we'll similarly update the\r
+  // last modification time. If changes are requested for both, but to\r
+  // different timestamps, we reject the request.\r
+  //\r
+  if (Change[0] && Change[2] && Seconds[0] != Seconds[2]) {\r
+    return EFI_ACCESS_DENIED;\r
+  }\r
+\r
+  *UpdateAtime = FALSE;\r
+  *UpdateMtime = FALSE;\r
+\r
+  if (Change[0]) {\r
+    *UpdateMtime = TRUE;\r
+    *Mtime = Seconds[0];\r
+  }\r
+  if (Change[1]) {\r
+    *UpdateAtime = TRUE;\r
+    *Atime = Seconds[1];\r
+  }\r
+  if (Change[2]) {\r
+    *UpdateMtime = TRUE;\r
+    *Mtime = Seconds[2];\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Given an EFI_FILE_INFO object received in an EFI_FILE_PROTOCOL.SetInfo()\r
+  call, determine whether updating the file mode bits of the file is necessary,\r
+  relative to an EFI_FILE_INFO object describing the current state of the file.\r
+\r
+  @param[in] Info     The EFI_FILE_INFO describing the current state of the\r
+                      file. The caller is responsible for populating Info on\r
+                      input with VirtioFsFuseAttrToEfiFileInfo(), from the\r
+                      current FUSE attributes of the file. The Info->Size and\r
+                      Info->FileName members are ignored.\r
+\r
+  @param[in] NewInfo  The EFI_FILE_INFO object received in the\r
+                      EFI_FILE_PROTOCOL.SetInfo() call.\r
+\r
+  @param[out] Update  Set to TRUE on output if the file mode bits need to be\r
+                      updated. Set to FALSE otherwise.\r
+\r
+  @param[out] Mode    If Update is set to TRUE, then Mode provides the file\r
+                      mode bits to set. Otherwise, Mode is not written to.\r
+\r
+  @retval EFI_SUCCESS        Output parameters have been set successfully.\r
+\r
+  @retval EFI_ACCESS_DENIED  NewInfo requests toggling an unknown bit in the\r
+                             Attribute bitmask.\r
+\r
+  @retval EFI_ACCESS_DENIED  NewInfo requests toggling EFI_FILE_DIRECTORY in\r
+                             the Attribute bitmask.\r
+**/\r
+EFI_STATUS\r
+VirtioFsGetFuseModeUpdate (\r
+  IN     EFI_FILE_INFO *Info,\r
+  IN     EFI_FILE_INFO *NewInfo,\r
+     OUT BOOLEAN       *Update,\r
+     OUT UINT32        *Mode\r
+     )\r
+{\r
+  UINT64  Toggle;\r
+  BOOLEAN IsDirectory;\r
+  BOOLEAN IsWriteable;\r
+  BOOLEAN WillBeWriteable;\r
+\r
+  Toggle = Info->Attribute ^ NewInfo->Attribute;\r
+  if ((Toggle & ~EFI_FILE_VALID_ATTR) != 0) {\r
+    //\r
+    // Unknown attribute requested.\r
+    //\r
+    return EFI_ACCESS_DENIED;\r
+  }\r
+  if ((Toggle & EFI_FILE_DIRECTORY) != 0) {\r
+    //\r
+    // EFI_FILE_DIRECTORY cannot be toggled.\r
+    //\r
+    return EFI_ACCESS_DENIED;\r
+  }\r
+\r
+  IsDirectory     = (BOOLEAN)((Info->Attribute    & EFI_FILE_DIRECTORY) != 0);\r
+  IsWriteable     = (BOOLEAN)((Info->Attribute    & EFI_FILE_READ_ONLY) == 0);\r
+  WillBeWriteable = (BOOLEAN)((NewInfo->Attribute & EFI_FILE_READ_ONLY) == 0);\r
+\r
+  if (IsWriteable == WillBeWriteable) {\r
+    *Update = FALSE;\r
+    return EFI_SUCCESS;\r
+  }\r
+\r
+  if (IsDirectory) {\r
+    if (WillBeWriteable) {\r
+      *Mode = (VIRTIO_FS_FUSE_MODE_PERM_RWXU |\r
+               VIRTIO_FS_FUSE_MODE_PERM_RWXG |\r
+               VIRTIO_FS_FUSE_MODE_PERM_RWXO);\r
+    } else {\r
+      *Mode = (VIRTIO_FS_FUSE_MODE_PERM_RUSR |\r
+               VIRTIO_FS_FUSE_MODE_PERM_XUSR |\r
+               VIRTIO_FS_FUSE_MODE_PERM_RGRP |\r
+               VIRTIO_FS_FUSE_MODE_PERM_XGRP |\r
+               VIRTIO_FS_FUSE_MODE_PERM_ROTH |\r
+               VIRTIO_FS_FUSE_MODE_PERM_XOTH);\r
+    }\r
+  } else {\r
+    if (WillBeWriteable) {\r
+      *Mode = (VIRTIO_FS_FUSE_MODE_PERM_RUSR |\r
+               VIRTIO_FS_FUSE_MODE_PERM_WUSR |\r
+               VIRTIO_FS_FUSE_MODE_PERM_RGRP |\r
+               VIRTIO_FS_FUSE_MODE_PERM_WGRP |\r
+               VIRTIO_FS_FUSE_MODE_PERM_ROTH |\r
+               VIRTIO_FS_FUSE_MODE_PERM_WOTH);\r
+    } else {\r
+      *Mode = (VIRTIO_FS_FUSE_MODE_PERM_RUSR |\r
+               VIRTIO_FS_FUSE_MODE_PERM_RGRP |\r
+               VIRTIO_FS_FUSE_MODE_PERM_ROTH);\r
+    }\r
+  }\r
+  *Update = TRUE;\r
+  return EFI_SUCCESS;\r
+}\r