/** @file
  Internal macro definitions, type definitions, and function declarations for
  the Virtio Filesystem device driver.

  Copyright (C) 2020, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#ifndef VIRTIO_FS_DXE_H_
#define VIRTIO_FS_DXE_H_

#include <Base.h>                      // SIGNATURE_64()
#include <Guid/FileInfo.h>             // EFI_FILE_INFO
#include <IndustryStandard/VirtioFs.h> // VIRTIO_FS_TAG_BYTES
#include <Library/DebugLib.h>          // CR()
#include <Protocol/SimpleFileSystem.h> // EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
#include <Protocol/VirtioDevice.h>     // VIRTIO_DEVICE_PROTOCOL
#include <Uefi/UefiBaseType.h>         // EFI_EVENT

#define VIRTIO_FS_SIG SIGNATURE_64 ('V', 'I', 'R', 'T', 'I', 'O', 'F', 'S')

#define VIRTIO_FS_FILE_SIG \
  SIGNATURE_64 ('V', 'I', 'O', 'F', 'S', 'F', 'I', 'L')

//
// The following limit applies to two kinds of pathnames.
//
// - The length of a POSIX-style, canonical pathname *at rest* never exceeds
//   VIRTIO_FS_MAX_PATHNAME_LENGTH. (Length is defined as the number of CHAR8
//   elements in the canonical pathname, excluding the terminating '\0'.) This
//   is an invariant that is ensured for canonical pathnames created, and that
//   is assumed about canonical pathname inputs (which all originate
//   internally).
//
// - If the length of a UEFI-style pathname *argument*, originating directly or
//   indirectly from the EFI_FILE_PROTOCOL caller, exceeds
//   VIRTIO_FS_MAX_PATHNAME_LENGTH, then the argument is rejected. (Length is
//   defined as the number of CHAR16 elements in the UEFI-style pathname,
//   excluding the terminating L'\0'.) This is a restriction that's checked on
//   external UEFI-style pathname inputs.
//
// The limit is not expected to be a practical limitation; it's only supposed
// to prevent attempts at overflowing size calculations. For both kinds of
// pathnames, separate limits could be used; a common limit is used purely for
// simplicity.
//
#define VIRTIO_FS_MAX_PATHNAME_LENGTH ((UINTN)65535)

//
// Maximum value for VIRTIO_FS_FILE.NumFileInfo.
//
#define VIRTIO_FS_FILE_MAX_FILE_INFO 256

//
// Filesystem label encoded in UCS-2, transformed from the UTF-8 representation
// in "VIRTIO_FS_CONFIG.Tag", and NUL-terminated. Only the printable ASCII code
// points (U+0020 through U+007E) are supported.
//
typedef CHAR16 VIRTIO_FS_LABEL[VIRTIO_FS_TAG_BYTES + 1];

//
// Main context structure, expressing an EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
// interface on top of the Virtio Filesystem device.
//
typedef struct {
  //
  // Parts of this structure are initialized / torn down in various functions
  // at various call depths. The table to the right should make it easier to
  // track them.
  //
  //                              field         init function       init depth
  //                              -----------   ------------------  ----------
  UINT64                          Signature; // DriverBindingStart  0
  VIRTIO_DEVICE_PROTOCOL          *Virtio;   // DriverBindingStart  0
  VIRTIO_FS_LABEL                 Label;     // VirtioFsInit        1
  UINT16                          QueueSize; // VirtioFsInit        1
  VRING                           Ring;      // VirtioRingInit      2
  VOID                            *RingMap;  // VirtioRingMap       2
  UINT64                          RequestId; // FuseInitSession     1
  UINT32                          MaxWrite;  // FuseInitSession     1
  EFI_EVENT                       ExitBoot;  // DriverBindingStart  0
  LIST_ENTRY                      OpenFiles; // DriverBindingStart  0
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL SimpleFs;  // DriverBindingStart  0
} VIRTIO_FS;

#define VIRTIO_FS_FROM_SIMPLE_FS(SimpleFsReference) \
  CR (SimpleFsReference, VIRTIO_FS, SimpleFs, VIRTIO_FS_SIG);

//
// Structure for describing a contiguous buffer, potentially mapped for Virtio
// transfer.
//
typedef struct {
  //
  // The following fields originate from the owner of the buffer.
  //
  VOID  *Buffer;
  UINTN Size;
  //
  // All of the fields below, until the end of the structure, are
  // zero-initialized when the structure is initially validated.
  //
  // Mapped, MappedAddress and Mapping are updated when the buffer is mapped
  // for VirtioOperationBusMasterRead or VirtioOperationBusMasterWrite. They
  // are again updated when the buffer is unmapped.
  //
  BOOLEAN              Mapped;
  EFI_PHYSICAL_ADDRESS MappedAddress;
  VOID                 *Mapping;
  //
  // Transferred is updated after VirtioFlush() returns successfully:
  // - for VirtioOperationBusMasterRead, Transferred is set to Size;
  // - for VirtioOperationBusMasterWrite, Transferred is calculated from the
  //   UsedLen output parameter of VirtioFlush().
  //
  UINTN Transferred;
} VIRTIO_FS_IO_VECTOR;

//
// Structure for describing a list of IO Vectors.
//
typedef struct {
  //
  // The following fields originate from the owner of the buffers.
  //
  VIRTIO_FS_IO_VECTOR *IoVec;
  UINTN               NumVec;
  //
  // TotalSize is calculated when the scatter-gather list is initially
  // validated.
  //
  UINT32 TotalSize;
} VIRTIO_FS_SCATTER_GATHER_LIST;

//
// Private context structure that exposes EFI_FILE_PROTOCOL on top of an open
// FUSE file reference.
//
typedef struct {
  UINT64            Signature;
  EFI_FILE_PROTOCOL SimpleFile;
  BOOLEAN           IsDirectory;
  BOOLEAN           IsOpenForWriting;
  VIRTIO_FS         *OwnerFs;
  LIST_ENTRY        OpenFilesEntry;
  CHAR8             *CanonicalPathname;
  UINT64            FilePosition;
  //
  // In the FUSE wire protocol, every request except FUSE_INIT refers to a
  // file, namely by the "VIRTIO_FS_FUSE_REQUEST.NodeId" field; that is, by the
  // inode number of the file. However, some of the FUSE requests that we need
  // for some of the EFI_FILE_PROTOCOL member functions require an open file
  // handle *in addition* to the inode number. For simplicity, whenever a
  // VIRTIO_FS_FILE object is created, primarily defined by its NodeId field,
  // we also *open* the referenced file at once, and save the returned file
  // handle in the FuseHandle field. This way, when an EFI_FILE_PROTOCOL member
  // function must send a FUSE request that needs the file handle *in addition*
  // to the inode number, FuseHandle will be at our disposal at once.
  //
  UINT64 NodeId;
  UINT64 FuseHandle;
  //
  // EFI_FILE_INFO objects cached for an in-flight directory read.
  //
  // For reading through a directory stream with tolerable performance, we have
  // to call FUSE_READDIRPLUS each time with such a buffer that can deliver a
  // good number of variable size records (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE
  // elements). Every time we do that, we turn the whole bunch into an array of
  // EFI_FILE_INFOs immediately. EFI_FILE_PROTOCOL.Read() invocations (on
  // directories) will be served from this EFI_FILE_INFO cache.
  //
  UINT8 *FileInfoArray;
  UINTN SingleFileInfoSize;
  UINTN NumFileInfo;
  UINTN NextFileInfo;
} VIRTIO_FS_FILE;

#define VIRTIO_FS_FILE_FROM_SIMPLE_FILE(SimpleFileReference) \
  CR (SimpleFileReference, VIRTIO_FS_FILE, SimpleFile, VIRTIO_FS_FILE_SIG);

#define VIRTIO_FS_FILE_FROM_OPEN_FILES_ENTRY(OpenFilesEntryReference) \
  CR (OpenFilesEntryReference, VIRTIO_FS_FILE, OpenFilesEntry, \
    VIRTIO_FS_FILE_SIG);

//
// Initialization and helper routines for the Virtio Filesystem device.
//

EFI_STATUS
VirtioFsInit (
  IN OUT VIRTIO_FS *VirtioFs
  );

VOID
VirtioFsUninit (
  IN OUT VIRTIO_FS *VirtioFs
  );

VOID
EFIAPI
VirtioFsExitBoot (
  IN EFI_EVENT ExitBootEvent,
  IN VOID      *VirtioFsAsVoid
  );

EFI_STATUS
VirtioFsSgListsValidate (
  IN     VIRTIO_FS                     *VirtioFs,
  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,
  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL
  );

EFI_STATUS
VirtioFsSgListsSubmit (
  IN OUT VIRTIO_FS                     *VirtioFs,
  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,
  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL
  );

EFI_STATUS
VirtioFsFuseNewRequest (
  IN OUT VIRTIO_FS              *VirtioFs,
     OUT VIRTIO_FS_FUSE_REQUEST *Request,
  IN     UINT32                 RequestSize,
  IN     VIRTIO_FS_FUSE_OPCODE  Opcode,
  IN     UINT64                 NodeId
  );

EFI_STATUS
VirtioFsFuseCheckResponse (
  IN  VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList,
  IN  UINT64                        RequestId,
  OUT UINTN                         *TailBufferFill
  );

EFI_STATUS
VirtioFsErrnoToEfiStatus (
  IN INT32 Errno
  );

EFI_STATUS
VirtioFsAppendPath (
  IN     CHAR8   *LhsPath8,
  IN     CHAR16  *RhsPath16,
     OUT CHAR8   **ResultPath8,
     OUT BOOLEAN *RootEscape
  );

EFI_STATUS
VirtioFsLookupMostSpecificParentDir (
  IN OUT VIRTIO_FS *VirtioFs,
  IN OUT CHAR8     *Path,
     OUT UINT64    *DirNodeId,
     OUT CHAR8     **LastComponent
  );

EFI_STATUS
VirtioFsGetBasename (
  IN     CHAR8  *Path,
     OUT CHAR16 *Basename     OPTIONAL,
  IN OUT UINTN  *BasenameSize
  );

EFI_STATUS
VirtioFsComposeRenameDestination (
  IN     CHAR8   *LhsPath8,
  IN     CHAR16  *RhsPath16,
     OUT CHAR8   **ResultPath8,
     OUT BOOLEAN *RootEscape
  );

EFI_STATUS
VirtioFsFuseAttrToEfiFileInfo (
  IN     VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE *FuseAttr,
     OUT EFI_FILE_INFO                      *FileInfo
  );

EFI_STATUS
VirtioFsFuseDirentPlusToEfiFileInfo (
  IN     VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *FuseDirent,
  IN OUT EFI_FILE_INFO                      *FileInfo
  );

VOID
VirtioFsGetFuseSizeUpdate (
  IN     EFI_FILE_INFO *Info,
  IN     EFI_FILE_INFO *NewInfo,
     OUT BOOLEAN       *Update,
     OUT UINT64        *Size
  );

EFI_STATUS
VirtioFsGetFuseTimeUpdates (
  IN     EFI_FILE_INFO *Info,
  IN     EFI_FILE_INFO *NewInfo,
     OUT BOOLEAN       *UpdateAtime,
     OUT BOOLEAN       *UpdateMtime,
     OUT UINT64        *Atime,
     OUT UINT64        *Mtime
  );

EFI_STATUS
VirtioFsGetFuseModeUpdate (
  IN     EFI_FILE_INFO *Info,
  IN     EFI_FILE_INFO *NewInfo,
     OUT BOOLEAN       *Update,
     OUT UINT32        *Mode
     );

//
// Wrapper functions for FUSE commands (primitives).
//

EFI_STATUS
VirtioFsFuseLookup (
  IN OUT VIRTIO_FS                          *VirtioFs,
  IN     UINT64                             DirNodeId,
  IN     CHAR8                              *Name,
     OUT UINT64                             *NodeId,
     OUT VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE *FuseAttr
  );

EFI_STATUS
VirtioFsFuseForget (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId
  );

EFI_STATUS
VirtioFsFuseGetAttr (
  IN OUT VIRTIO_FS                          *VirtioFs,
  IN     UINT64                             NodeId,
     OUT VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE *FuseAttr
  );

EFI_STATUS
VirtioFsFuseSetAttr (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
  IN     UINT64    *Size      OPTIONAL,
  IN     UINT64    *Atime     OPTIONAL,
  IN     UINT64    *Mtime     OPTIONAL,
  IN     UINT32    *Mode      OPTIONAL
  );

EFI_STATUS
VirtioFsFuseMkDir (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    ParentNodeId,
  IN     CHAR8     *Name,
     OUT UINT64    *NodeId
  );

EFI_STATUS
VirtioFsFuseRemoveFileOrDir (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    ParentNodeId,
  IN     CHAR8     *Name,
  IN     BOOLEAN   IsDir
  );

EFI_STATUS
VirtioFsFuseOpen (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
  IN     BOOLEAN   ReadWrite,
     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
VirtioFsFuseWrite (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
  IN     UINT64    FuseHandle,
  IN     UINT64    Offset,
  IN OUT UINT32    *Size,
  IN     VOID      *Data
  );

EFI_STATUS
VirtioFsFuseStatFs (
  IN OUT VIRTIO_FS                      *VirtioFs,
  IN     UINT64                         NodeId,
     OUT VIRTIO_FS_FUSE_STATFS_RESPONSE *FilesysAttr
     );

EFI_STATUS
VirtioFsFuseReleaseFileOrDir (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
  IN     UINT64    FuseHandle,
  IN     BOOLEAN   IsDir
  );

EFI_STATUS
VirtioFsFuseFsyncFileOrDir (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
  IN     UINT64    FuseHandle,
  IN     BOOLEAN   IsDir
  );

EFI_STATUS
VirtioFsFuseFlush (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
  IN     UINT64    FuseHandle
  );

EFI_STATUS
VirtioFsFuseInitSession (
  IN OUT VIRTIO_FS *VirtioFs
  );

EFI_STATUS
VirtioFsFuseOpenDir (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    NodeId,
     OUT UINT64    *FuseHandle
  );

EFI_STATUS
VirtioFsFuseOpenOrCreate (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    ParentNodeId,
  IN     CHAR8     *Name,
     OUT UINT64    *NodeId,
     OUT UINT64    *FuseHandle
  );

EFI_STATUS
VirtioFsFuseRename (
  IN OUT VIRTIO_FS *VirtioFs,
  IN     UINT64    OldParentNodeId,
  IN     CHAR8     *OldName,
  IN     UINT64    NewParentNodeId,
  IN     CHAR8     *NewName
  );

//
// EFI_SIMPLE_FILE_SYSTEM_PROTOCOL member functions for the Virtio Filesystem
// driver.
//

EFI_STATUS
EFIAPI
VirtioFsOpenVolume (
  IN  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
  OUT EFI_FILE_PROTOCOL               **Root
  );

//
// EFI_FILE_PROTOCOL member functions for the Virtio Filesystem driver.
//

EFI_STATUS
EFIAPI
VirtioFsSimpleFileClose (
  IN EFI_FILE_PROTOCOL *This
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileDelete (
  IN EFI_FILE_PROTOCOL *This
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileFlush (
  IN EFI_FILE_PROTOCOL *This
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileGetInfo (
  IN     EFI_FILE_PROTOCOL *This,
  IN     EFI_GUID          *InformationType,
  IN OUT UINTN             *BufferSize,
     OUT VOID              *Buffer
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileGetPosition (
  IN     EFI_FILE_PROTOCOL *This,
     OUT UINT64            *Position
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileOpen (
  IN     EFI_FILE_PROTOCOL *This,
     OUT EFI_FILE_PROTOCOL **NewHandle,
  IN     CHAR16            *FileName,
  IN     UINT64            OpenMode,
  IN     UINT64            Attributes
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileRead (
  IN     EFI_FILE_PROTOCOL *This,
  IN OUT UINTN             *BufferSize,
     OUT VOID              *Buffer
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileSetInfo (
  IN EFI_FILE_PROTOCOL *This,
  IN EFI_GUID          *InformationType,
  IN UINTN             BufferSize,
  IN VOID              *Buffer
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileSetPosition (
  IN EFI_FILE_PROTOCOL *This,
  IN UINT64            Position
  );

EFI_STATUS
EFIAPI
VirtioFsSimpleFileWrite (
  IN     EFI_FILE_PROTOCOL *This,
  IN OUT UINTN             *BufferSize,
  IN     VOID              *Buffer
  );

#endif // VIRTIO_FS_DXE_H_
