/** @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
**/

#pragma once

#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
  );
