/** @file
  EFI_FILE_PROTOCOL.GetInfo() member function for the Virtio Filesystem driver.

  Copyright (C) 2020, Red Hat, Inc.

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

#include <Guid/FileSystemInfo.h>            // gEfiFileSystemInfoGuid
#include <Guid/FileSystemVolumeLabelInfo.h> // gEfiFileSystemVolumeLabelInfo...
#include <Library/BaseLib.h>                // StrSize()
#include <Library/BaseMemoryLib.h>          // CompareGuid()

#include "VirtioFsDxe.h"

/**
  Provide EFI_FILE_INFO about this particular file.
**/
STATIC
EFI_STATUS
GetFileInfo (
  IN     EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN              *BufferSize,
  OUT VOID                  *Buffer
  )
{
  VIRTIO_FS_FILE                      *VirtioFsFile;
  VIRTIO_FS                           *VirtioFs;
  UINTN                               AllocSize;
  UINTN                               BasenameSize;
  EFI_STATUS                          Status;
  EFI_FILE_INFO                       *FileInfo;
  VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE  FuseAttr;

  VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
  VirtioFs     = VirtioFsFile->OwnerFs;

  AllocSize = *BufferSize;

  //
  // Calculate the needed size.
  //
  BasenameSize = 0;
  Status       = VirtioFsGetBasename (
                   VirtioFsFile->CanonicalPathname,
                   NULL,
                   &BasenameSize
                   );
  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
  *BufferSize = OFFSET_OF (EFI_FILE_INFO, FileName) + BasenameSize;

  if (*BufferSize > AllocSize) {
    return EFI_BUFFER_TOO_SMALL;
  }

  //
  // Set the structure size, and store the basename.
  //
  FileInfo       = Buffer;
  FileInfo->Size = *BufferSize;
  Status         = VirtioFsGetBasename (
                     VirtioFsFile->CanonicalPathname,
                     FileInfo->FileName,
                     &BasenameSize
                     );
  ASSERT_EFI_ERROR (Status);

  //
  // Fetch the file attributes, and convert them into the caller's buffer.
  //
  Status = VirtioFsFuseGetAttr (VirtioFs, VirtioFsFile->NodeId, &FuseAttr);
  if (!EFI_ERROR (Status)) {
    Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, FileInfo);
  }

  return (Status == EFI_BUFFER_TOO_SMALL) ? EFI_DEVICE_ERROR : Status;
}

/**
  Provide EFI_FILE_SYSTEM_INFO about the filesystem this file lives on.
**/
STATIC
EFI_STATUS
GetFileSystemInfo (
  IN     EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN              *BufferSize,
  OUT VOID                  *Buffer
  )
{
  VIRTIO_FS_FILE                  *VirtioFsFile;
  VIRTIO_FS                       *VirtioFs;
  UINTN                           AllocSize;
  UINTN                           LabelSize;
  EFI_STATUS                      Status;
  VIRTIO_FS_FUSE_STATFS_RESPONSE  FilesysAttr;
  UINT64                          MaxBlocks;
  EFI_FILE_SYSTEM_INFO            *FilesysInfo;

  VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
  VirtioFs     = VirtioFsFile->OwnerFs;

  AllocSize = *BufferSize;

  //
  // Calculate the needed size.
  //
  LabelSize   = StrSize (VirtioFs->Label);
  *BufferSize = OFFSET_OF (EFI_FILE_SYSTEM_INFO, VolumeLabel) + LabelSize;

  if (*BufferSize > AllocSize) {
    return EFI_BUFFER_TOO_SMALL;
  }

  //
  // Fetch the filesystem attributes.
  //
  Status = VirtioFsFuseStatFs (VirtioFs, VirtioFsFile->NodeId, &FilesysAttr);
  if (EFI_ERROR (Status)) {
    return (Status == EFI_BUFFER_TOO_SMALL) ? EFI_DEVICE_ERROR : Status;
  }

  //
  // Sanity checks...
  //
  if (FilesysAttr.Frsize != FilesysAttr.Bsize) {
    return EFI_UNSUPPORTED;
  }

  if ((FilesysAttr.Frsize == 0) || (FilesysAttr.Blocks == 0) ||
      (FilesysAttr.Bavail > FilesysAttr.Blocks))
  {
    return EFI_DEVICE_ERROR;
  }

  MaxBlocks = DivU64x32 (MAX_UINT64, FilesysAttr.Frsize);
  if ((FilesysAttr.Blocks > MaxBlocks) || (FilesysAttr.Bavail > MaxBlocks)) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Fill in EFI_FILE_SYSTEM_INFO.
  //
  FilesysInfo             = Buffer;
  FilesysInfo->Size       = *BufferSize;
  FilesysInfo->ReadOnly   = FALSE;
  FilesysInfo->VolumeSize = MultU64x32 (
                              FilesysAttr.Blocks,
                              FilesysAttr.Frsize
                              );
  FilesysInfo->FreeSpace = MultU64x32 (
                             FilesysAttr.Bavail,
                             FilesysAttr.Frsize
                             );
  FilesysInfo->BlockSize = FilesysAttr.Frsize;
  CopyMem (FilesysInfo->VolumeLabel, VirtioFs->Label, LabelSize);

  return EFI_SUCCESS;
}

/**
  Return the filesystem label as EFI_FILE_SYSTEM_VOLUME_LABEL.
**/
STATIC
EFI_STATUS
GetFileSystemVolumeLabelInfo (
  IN     EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN              *BufferSize,
  OUT VOID                  *Buffer
  )
{
  VIRTIO_FS_FILE                *VirtioFsFile;
  VIRTIO_FS                     *VirtioFs;
  UINTN                         AllocSize;
  UINTN                         LabelSize;
  EFI_FILE_SYSTEM_VOLUME_LABEL  *FilesysVolumeLabel;

  VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
  VirtioFs     = VirtioFsFile->OwnerFs;

  AllocSize = *BufferSize;

  //
  // Calculate the needed size.
  //
  LabelSize   = StrSize (VirtioFs->Label);
  *BufferSize = (OFFSET_OF (EFI_FILE_SYSTEM_VOLUME_LABEL, VolumeLabel) +
                 LabelSize);

  if (*BufferSize > AllocSize) {
    return EFI_BUFFER_TOO_SMALL;
  }

  //
  // Store the label.
  //
  FilesysVolumeLabel = Buffer;
  CopyMem (FilesysVolumeLabel->VolumeLabel, VirtioFs->Label, LabelSize);

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
VirtioFsSimpleFileGetInfo (
  IN     EFI_FILE_PROTOCOL  *This,
  IN     EFI_GUID           *InformationType,
  IN OUT UINTN              *BufferSize,
  OUT VOID                  *Buffer
  )
{
  if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
    return GetFileInfo (This, BufferSize, Buffer);
  }

  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
    return GetFileSystemInfo (This, BufferSize, Buffer);
  }

  if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
    return GetFileSystemVolumeLabelInfo (This, BufferSize, Buffer);
  }

  return EFI_UNSUPPORTED;
}
