OvmfPkg/VirtioFsDxe: add a shared wrapper for FUSE_READ / FUSE_READDIRPLUS

Add the VirtioFsFuseReadFileOrDir() function, for sending the FUSE_READ or
FUSE_READDIRPLUS command to the Virtio Filesystem device.

Parsing the structured FUSE_READDIRPLUS output is complex, and cannot be
integrated into the wrapper function. Given that fact, FUSE_READ and
FUSE_READDIRPLUS turn out to need identical low-level handling, except for
the opcode. Hence the shared wrapper function.

(It's prudent to verify whether the FUSE server supports FUSE_READDIRPLUS,
so update the session init code accordingly.)

This is the first FUSE request wrapper function that deals with a variable
size tail buffer.

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-33-lersek@redhat.com>
Acked-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
This commit is contained in:
Laszlo Ersek 2020-12-16 22:11:09 +01:00 committed by mergify[bot]
parent c4edb49b4f
commit d98d7e3005
5 changed files with 276 additions and 4 deletions

View File

@ -108,6 +108,39 @@ typedef struct {
#define VIRTIO_FS_FUSE_OPEN_REQ_F_RDONLY 0
#define VIRTIO_FS_FUSE_OPEN_REQ_F_RDWR 2
//
// Flags for VirtioFsFuseOpInit.
//
#define VIRTIO_FS_FUSE_INIT_REQ_F_DO_READDIRPLUS BIT13
/**
Macro for calculating the size of a directory stream entry.
The macro may evaluate Namelen multiple times.
The macro evaluates to a UINTN value that is safe to cast to UINT32.
@param[in] Namelen The size of the filename byte array that follows
VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE in the directory
stream, as reported by
VIRTIO_FS_FUSE_STATFS_RESPONSE.Namelen or
VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE.Namelen. The filename
byte array is not NUL-terminated.
@retval 0 Namelen was zero or greater than SIZE_4KB.
@return The number of bytes in the directory entry, including the
VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE header.
**/
#define VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE(Namelen) \
((Namelen) == 0 || (Namelen) > SIZE_4KB ? \
(UINTN)0 : \
ALIGN_VALUE ( \
sizeof (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE) + (UINTN)(Namelen), \
sizeof (UINT64) \
) \
)
//
// FUSE operation codes.
//
@ -119,6 +152,7 @@ typedef enum {
VirtioFsFuseOpUnlink = 10,
VirtioFsFuseOpRmDir = 11,
VirtioFsFuseOpOpen = 14,
VirtioFsFuseOpRead = 15,
VirtioFsFuseOpStatFs = 17,
VirtioFsFuseOpRelease = 18,
VirtioFsFuseOpFsync = 20,
@ -128,6 +162,7 @@ typedef enum {
VirtioFsFuseOpReleaseDir = 29,
VirtioFsFuseOpFsyncDir = 30,
VirtioFsFuseOpCreate = 35,
VirtioFsFuseOpReadDirPlus = 44,
} VIRTIO_FS_FUSE_OPCODE;
#pragma pack (1)
@ -234,6 +269,19 @@ typedef struct {
UINT32 Padding;
} VIRTIO_FS_FUSE_OPEN_RESPONSE;
//
// Header for VirtioFsFuseOpRead and VirtioFsFuseOpReadDirPlus.
//
typedef struct {
UINT64 FileHandle;
UINT64 Offset;
UINT32 Size;
UINT32 ReadFlags;
UINT64 LockOwner;
UINT32 Flags;
UINT32 Padding;
} VIRTIO_FS_FUSE_READ_REQUEST;
//
// Header for VirtioFsFuseOpStatFs.
//
@ -312,6 +360,25 @@ typedef struct {
UINT32 Umask;
UINT32 Padding;
} VIRTIO_FS_FUSE_CREATE_REQUEST;
//
// Header for VirtioFsFuseOpReadDirPlus.
//
// Diverging from the rest of the headers, this structure embeds other
// structures. The reason is that a scatter list cannot be used to receive
// NodeResp and AttrResp separately; the record below is followed by a variable
// size filename byte array, and then such pairs are repeated a number of
// times. Thus, later header start offsets depend on earlier filename array
// sizes.
//
typedef struct {
VIRTIO_FS_FUSE_NODE_RESPONSE NodeResp;
VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE AttrResp;
UINT64 NodeId;
UINT64 CookieForNextEntry;
UINT32 Namelen;
UINT32 Type;
} VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE;
#pragma pack ()
#endif // VIRTIO_FS_H_

View File

@ -26,7 +26,8 @@
@retval EFI_SUCCESS The FUSE session has been started.
@retval EFI_UNSUPPORTED FUSE interface version negotiation failed.
@retval EFI_UNSUPPORTED FUSE interface version or feature negotiation
failed.
@return The "errno" value mapped to an EFI_STATUS code, if
the Virtio Filesystem device explicitly reported an
@ -97,7 +98,7 @@ VirtioFsFuseInitSession (
InitReq.Major = VIRTIO_FS_FUSE_MAJOR;
InitReq.Minor = VIRTIO_FS_FUSE_MINOR;
InitReq.MaxReadahead = 0;
InitReq.Flags = 0;
InitReq.Flags = VIRTIO_FS_FUSE_INIT_REQ_F_DO_READDIRPLUS;
//
// Submit the request.
@ -121,10 +122,11 @@ VirtioFsFuseInitSession (
}
//
// Check FUSE interface version compatibility.
// Check FUSE interface version / feature compatibility.
//
if (InitResp.Major < InitReq.Major ||
(InitResp.Major == InitReq.Major && InitResp.Minor < InitReq.Minor)) {
(InitResp.Major == InitReq.Major && InitResp.Minor < InitReq.Minor) ||
(InitResp.Flags & VIRTIO_FS_FUSE_INIT_REQ_F_DO_READDIRPLUS) == 0) {
return EFI_UNSUPPORTED;
}

View File

@ -0,0 +1,191 @@
/** @file
FUSE_READ / FUSE_READDIRPLUS wrapper for the Virtio Filesystem device.
Copyright (C) 2020, Red Hat, Inc.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "VirtioFsDxe.h"
/**
Read a chunk from a regular file or a directory stream, by sending the
FUSE_READ / FUSE_READDIRPLUS request to the Virtio Filesystem device.
The function may only be called after VirtioFsFuseInitSession() returns
successfully and before VirtioFsUninit() is called.
@param[in,out] VirtioFs The Virtio Filesystem device to send the FUSE_READ
or FUSE_READDIRPLUS request to. On output, the FUSE
request counter "VirtioFs->RequestId" will have been
incremented.
@param[in] NodeId The inode number of the regular file or directory
stream to read from.
@param[in] FuseHandle The open handle to the regular file or directory
stream to read from.
@param[in] IsDir TRUE if NodeId and FuseHandle refer to a directory,
FALSE if NodeId and FuseHandle refer to a regular
file.
@param[in] Offset If IsDir is FALSE: the absolute file position at
which to start reading.
If IsDir is TRUE: the directory stream cookie at
which to start or continue reading. The zero-valued
cookie identifies the start of the directory stream.
Further positions in the directory stream can be
passed in from the CookieForNextEntry field of
VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE.
@param[in,out] Size On input, the number of bytes to read. On successful
return, the number of bytes actually read, which may
be smaller than the value on input.
When reading a regular file (i.e., when IsDir is
FALSE), EOF can be detected by passing in a nonzero
Size, and finding a zero Size on output.
When reading a directory stream (i.e., when IsDir is
TRUE), Data consists of a sequence of variably-sized
records (directory entries). A read operation
returns the maximal sequence of records that fits in
Size, without having to truncate a record. In order
to guarantee progress, call
VirtioFsFuseStatFs (VirtioFs, NodeId,
&FilesysAttr)
first, to learn the maximum Namelen for the
directory stream. Then assign Size at least
VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (
FilesysAttr.Namelen)
on input. (Remember that
VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE() may return
0 if its Namelen argument is invalid.) EOF can be
detected if Size is set on input like described
above, and Size is zero on output.
@param[out] Data Buffer to read the bytes from the regular file or
the directory stream into. The caller is responsible
for providing room for (at least) as many bytes in
Data as Size is on input.
@retval EFI_SUCCESS Read successful. The caller is responsible for checking
Size to learn the actual byte count transferred.
@return The "errno" value mapped to an EFI_STATUS code, if the
Virtio Filesystem device explicitly reported an error.
@return Error codes propagated from VirtioFsSgListsValidate(),
VirtioFsFuseNewRequest(), VirtioFsSgListsSubmit(),
VirtioFsFuseCheckResponse().
**/
EFI_STATUS
VirtioFsFuseReadFileOrDir (
IN OUT VIRTIO_FS *VirtioFs,
IN UINT64 NodeId,
IN UINT64 FuseHandle,
IN BOOLEAN IsDir,
IN UINT64 Offset,
IN OUT UINT32 *Size,
OUT VOID *Data
)
{
VIRTIO_FS_FUSE_REQUEST CommonReq;
VIRTIO_FS_FUSE_READ_REQUEST ReadReq;
VIRTIO_FS_IO_VECTOR ReqIoVec[2];
VIRTIO_FS_SCATTER_GATHER_LIST ReqSgList;
VIRTIO_FS_FUSE_RESPONSE CommonResp;
VIRTIO_FS_IO_VECTOR RespIoVec[2];
VIRTIO_FS_SCATTER_GATHER_LIST RespSgList;
EFI_STATUS Status;
UINTN TailBufferFill;
//
// Set up the scatter-gather lists.
//
ReqIoVec[0].Buffer = &CommonReq;
ReqIoVec[0].Size = sizeof CommonReq;
ReqIoVec[1].Buffer = &ReadReq;
ReqIoVec[1].Size = sizeof ReadReq;
ReqSgList.IoVec = ReqIoVec;
ReqSgList.NumVec = ARRAY_SIZE (ReqIoVec);
RespIoVec[0].Buffer = &CommonResp;
RespIoVec[0].Size = sizeof CommonResp;
RespIoVec[1].Buffer = Data;
RespIoVec[1].Size = *Size;
RespSgList.IoVec = RespIoVec;
RespSgList.NumVec = ARRAY_SIZE (RespIoVec);
//
// Validate the scatter-gather lists; calculate the total transfer sizes.
//
Status = VirtioFsSgListsValidate (VirtioFs, &ReqSgList, &RespSgList);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Populate the common request header.
//
Status = VirtioFsFuseNewRequest (
VirtioFs,
&CommonReq,
ReqSgList.TotalSize,
IsDir ? VirtioFsFuseOpReadDirPlus : VirtioFsFuseOpRead,
NodeId
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Populate the FUSE_READ- / FUSE_READDIRPLUS-specific fields.
//
ReadReq.FileHandle = FuseHandle;
ReadReq.Offset = Offset;
ReadReq.Size = *Size;
ReadReq.ReadFlags = 0;
ReadReq.LockOwner = 0;
ReadReq.Flags = 0;
ReadReq.Padding = 0;
//
// Submit the request.
//
Status = VirtioFsSgListsSubmit (VirtioFs, &ReqSgList, &RespSgList);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Verify the response. Note that TailBufferFill is variable.
//
Status = VirtioFsFuseCheckResponse (&RespSgList, CommonReq.Unique,
&TailBufferFill);
if (EFI_ERROR (Status)) {
if (Status == EFI_DEVICE_ERROR) {
DEBUG ((DEBUG_ERROR, "%a: Label=\"%s\" NodeId=%Lu FuseHandle=%Lu "
"IsDir=%d Offset=0x%Lx Size=0x%x Data@%p Errno=%d\n", __FUNCTION__,
VirtioFs->Label, NodeId, FuseHandle, IsDir, Offset, *Size, Data,
CommonResp.Error));
Status = VirtioFsErrnoToEfiStatus (CommonResp.Error);
}
return Status;
}
//
// Report the actual transfer size.
//
// Integer overflow in the (UINT32) cast below is not possible; the
// VIRTIO_FS_SCATTER_GATHER_LIST functions would have caught that.
//
*Size = (UINT32)TailBufferFill;
return EFI_SUCCESS;
}

View File

@ -298,6 +298,17 @@ VirtioFsFuseOpen (
OUT UINT64 *FuseHandle
);
EFI_STATUS
VirtioFsFuseReadFileOrDir (
IN OUT VIRTIO_FS *VirtioFs,
IN UINT64 NodeId,
IN UINT64 FuseHandle,
IN BOOLEAN IsDir,
IN UINT64 Offset,
IN OUT UINT32 *Size,
OUT VOID *Data
);
EFI_STATUS
VirtioFsFuseStatFs (
IN OUT VIRTIO_FS *VirtioFs,

View File

@ -93,6 +93,7 @@
FuseOpen.c
FuseOpenDir.c
FuseOpenOrCreate.c
FuseRead.c
FuseRelease.c
FuseStatFs.c
FuseUnlink.c