/** @file
  Routines dealing with setting/getting file/volume info

Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent



**/

#include "Fat.h"

/**

  Get the volume's info into Buffer.

  @param  Volume                - FAT file system volume.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing volume info.

  @retval EFI_SUCCESS           - Get the volume info successfully.
  @retval EFI_BUFFER_TOO_SMALL  - The buffer is too small.

**/
EFI_STATUS
FatGetVolumeInfo (
  IN FAT_VOLUME  *Volume,
  IN OUT UINTN   *BufferSize,
  OUT VOID       *Buffer
  );

/**

  Set the volume's info.

  @param  Volume                - FAT file system volume.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing the new volume info.

  @retval EFI_SUCCESS           - Set the volume info successfully.
  @retval EFI_BAD_BUFFER_SIZE   - The buffer size is error.
  @retval EFI_WRITE_PROTECTED   - The volume is read only.
  @return other                 - An error occurred when operation the disk.

**/
EFI_STATUS
FatSetVolumeInfo (
  IN FAT_VOLUME  *Volume,
  IN UINTN       BufferSize,
  IN VOID        *Buffer
  );

/**

  Set or Get the some types info of the file into Buffer.

  @param  IsSet      - TRUE:The access is set, else is get
  @param  FHand      - The handle of file
  @param  Type       - The type of the info
  @param  BufferSize - Size of Buffer
  @param  Buffer     - Buffer containing volume info

  @retval EFI_SUCCESS       - Get the info successfully
  @retval EFI_DEVICE_ERROR  - Can not find the OFile for the file

**/
EFI_STATUS
FatSetOrGetInfo (
  IN BOOLEAN            IsSet,
  IN EFI_FILE_PROTOCOL  *FHand,
  IN EFI_GUID           *Type,
  IN OUT UINTN          *BufferSize,
  IN OUT VOID           *Buffer
  );

/**

  Get the open file's info into Buffer.

  @param  OFile                 - The open file.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing file info.

  @retval EFI_SUCCESS           - Get the file info successfully.
  @retval EFI_BUFFER_TOO_SMALL  - The buffer is too small.

**/
EFI_STATUS
FatGetFileInfo (
  IN FAT_OFILE  *OFile,
  IN OUT UINTN  *BufferSize,
  OUT VOID      *Buffer
  )
{
  return FatGetDirEntInfo (OFile->Volume, OFile->DirEnt, BufferSize, Buffer);
}

/**

  Get the volume's info into Buffer.

  @param  Volume                - FAT file system volume.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing volume info.

  @retval EFI_SUCCESS           - Get the volume info successfully.
  @retval EFI_BUFFER_TOO_SMALL  - The buffer is too small.

**/
EFI_STATUS
FatGetVolumeInfo (
  IN     FAT_VOLUME  *Volume,
  IN OUT UINTN       *BufferSize,
  OUT VOID           *Buffer
  )
{
  UINTN                 Size;
  UINTN                 NameSize;
  UINTN                 ResultSize;
  CHAR16                Name[FAT_NAME_LEN + 1];
  EFI_STATUS            Status;
  EFI_FILE_SYSTEM_INFO  *Info;
  UINT8                 ClusterAlignment;

  Size             = SIZE_OF_EFI_FILE_SYSTEM_INFO;
  Status           = FatGetVolumeEntry (Volume, Name);
  NameSize         = StrSize (Name);
  ResultSize       = Size + NameSize;
  ClusterAlignment = Volume->ClusterAlignment;

  //
  // If we don't have valid info, compute it now
  //
  FatComputeFreeInfo (Volume);

  Status = EFI_BUFFER_TOO_SMALL;
  if (*BufferSize >= ResultSize) {
    Status = EFI_SUCCESS;

    Info = Buffer;
    ZeroMem (Info, SIZE_OF_EFI_FILE_SYSTEM_INFO);

    Info->Size       = ResultSize;
    Info->ReadOnly   = Volume->ReadOnly;
    Info->BlockSize  = (UINT32)Volume->ClusterSize;
    Info->VolumeSize = LShiftU64 (Volume->MaxCluster, ClusterAlignment);
    Info->FreeSpace  = LShiftU64 (
                         Volume->FatInfoSector.FreeInfo.ClusterCount,
                         ClusterAlignment
                         );
    CopyMem ((CHAR8 *)Buffer + Size, Name, NameSize);
  }

  *BufferSize = ResultSize;
  return Status;
}

/**

  Get the volume's label info into Buffer.

  @param  Volume                - FAT file system volume.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing volume's label info.

  @retval EFI_SUCCESS           - Get the volume's label info successfully.
  @retval EFI_BUFFER_TOO_SMALL  - The buffer is too small.

**/
EFI_STATUS
FatGetVolumeLabelInfo (
  IN FAT_VOLUME  *Volume,
  IN OUT UINTN   *BufferSize,
  OUT VOID       *Buffer
  )
{
  UINTN       Size;
  UINTN       NameSize;
  UINTN       ResultSize;
  CHAR16      Name[FAT_NAME_LEN + 1];
  EFI_STATUS  Status;

  Size       = SIZE_OF_EFI_FILE_SYSTEM_VOLUME_LABEL;
  Status     = FatGetVolumeEntry (Volume, Name);
  NameSize   = StrSize (Name);
  ResultSize = Size + NameSize;

  Status = EFI_BUFFER_TOO_SMALL;
  if (*BufferSize >= ResultSize) {
    Status = EFI_SUCCESS;
    CopyMem ((CHAR8 *)Buffer + Size, Name, NameSize);
  }

  *BufferSize = ResultSize;
  return Status;
}

/**

  Set the volume's info.

  @param  Volume                - FAT file system volume.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing the new volume info.

  @retval EFI_SUCCESS           - Set the volume info successfully.
  @retval EFI_BAD_BUFFER_SIZE   - The buffer size is error.
  @retval EFI_WRITE_PROTECTED   - The volume is read only.
  @return other                 - An error occurred when operation the disk.

**/
EFI_STATUS
FatSetVolumeInfo (
  IN FAT_VOLUME  *Volume,
  IN UINTN       BufferSize,
  IN VOID        *Buffer
  )
{
  EFI_FILE_SYSTEM_INFO  *Info;

  Info = (EFI_FILE_SYSTEM_INFO *)Buffer;

  if ((BufferSize < SIZE_OF_EFI_FILE_SYSTEM_INFO + 2) || (Info->Size > BufferSize)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  return FatSetVolumeEntry (Volume, Info->VolumeLabel);
}

/**

  Set the volume's label info.

  @param  Volume                - FAT file system volume.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing the new volume label info.

  @retval EFI_SUCCESS           - Set the volume label info successfully.
  @retval EFI_WRITE_PROTECTED   - The disk is write protected.
  @retval EFI_BAD_BUFFER_SIZE   - The buffer size is error.
  @return other                 - An error occurred when operation the disk.

**/
EFI_STATUS
FatSetVolumeLabelInfo (
  IN FAT_VOLUME  *Volume,
  IN UINTN       BufferSize,
  IN VOID        *Buffer
  )
{
  EFI_FILE_SYSTEM_VOLUME_LABEL  *Info;

  Info = (EFI_FILE_SYSTEM_VOLUME_LABEL *)Buffer;

  if (BufferSize < SIZE_OF_EFI_FILE_SYSTEM_VOLUME_LABEL + 2) {
    return EFI_BAD_BUFFER_SIZE;
  }

  return FatSetVolumeEntry (Volume, Info->VolumeLabel);
}

/**

  Set the file info.

  @param  Volume                - FAT file system volume.
  @param  IFile                 - The instance of the open file.
  @param  OFile                 - The open file.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing the new file info.

  @retval EFI_SUCCESS           - Set the file info successfully.
  @retval EFI_ACCESS_DENIED     - It is the root directory
                          or the directory attribute bit can not change
                          or try to change a directory size
                          or something else.
  @retval EFI_UNSUPPORTED       - The new file size is larger than 4GB.
  @retval EFI_WRITE_PROTECTED   - The disk is write protected.
  @retval EFI_BAD_BUFFER_SIZE   - The buffer size is error.
  @retval EFI_INVALID_PARAMETER - The time info or attributes info is error.
  @retval EFI_OUT_OF_RESOURCES  - Can not allocate new memory.
  @retval EFI_VOLUME_CORRUPTED  - The volume is corrupted.
  @return other                 - An error occurred when operation the disk.

**/
EFI_STATUS
FatSetFileInfo (
  IN FAT_VOLUME  *Volume,
  IN FAT_IFILE   *IFile,
  IN FAT_OFILE   *OFile,
  IN UINTN       BufferSize,
  IN VOID        *Buffer
  )
{
  EFI_STATUS     Status;
  EFI_FILE_INFO  *NewInfo;
  FAT_OFILE      *DotOFile;
  FAT_OFILE      *Parent;
  CHAR16         NewFileName[EFI_PATH_STRING_LENGTH];
  EFI_TIME       ZeroTime;
  FAT_DIRENT     *DirEnt;
  FAT_DIRENT     *TempDirEnt;
  UINT8          NewAttribute;
  BOOLEAN        ReadOnly;

  TempDirEnt = NULL;

  ZeroMem (&ZeroTime, sizeof (EFI_TIME));
  Parent = OFile->Parent;
  DirEnt = OFile->DirEnt;
  //
  // If this is the root directory, we can't make any updates
  //
  if (Parent == NULL) {
    return EFI_ACCESS_DENIED;
  }

  //
  // Make sure there's a valid input buffer
  //
  NewInfo = Buffer;
  if ((BufferSize < SIZE_OF_EFI_FILE_INFO + 2) || (NewInfo->Size > BufferSize)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  ReadOnly = (BOOLEAN)(IFile->ReadOnly || (DirEnt->Entry.Attributes & EFI_FILE_READ_ONLY));
  //
  // if a zero time is specified, then the original time is preserved
  //
  if (CompareMem (&ZeroTime, &NewInfo->CreateTime, sizeof (EFI_TIME)) != 0) {
    if (!FatIsValidTime (&NewInfo->CreateTime)) {
      return EFI_INVALID_PARAMETER;
    }

    if (!ReadOnly) {
      FatEfiTimeToFatTime (&NewInfo->CreateTime, &DirEnt->Entry.FileCreateTime);
    }
  }

  if (CompareMem (&ZeroTime, &NewInfo->ModificationTime, sizeof (EFI_TIME)) != 0) {
    if (!FatIsValidTime (&NewInfo->ModificationTime)) {
      return EFI_INVALID_PARAMETER;
    }

    if (!ReadOnly) {
      FatEfiTimeToFatTime (&NewInfo->ModificationTime, &DirEnt->Entry.FileModificationTime);
    }

    OFile->PreserveLastModification = TRUE;
  }

  if (NewInfo->Attribute & (~EFI_FILE_VALID_ATTR)) {
    return EFI_INVALID_PARAMETER;
  }

  NewAttribute = (UINT8)NewInfo->Attribute;
  //
  // Can not change the directory attribute bit
  //
  if ((NewAttribute ^ DirEnt->Entry.Attributes) & EFI_FILE_DIRECTORY) {
    return EFI_ACCESS_DENIED;
  }

  //
  // Set the current attributes even if the IFile->ReadOnly is TRUE
  //
  DirEnt->Entry.Attributes = (UINT8)((DirEnt->Entry.Attributes &~EFI_FILE_VALID_ATTR) | NewAttribute);
  //
  // Open the filename and see if it refers to an existing file
  //
  Status = FatLocateOFile (&Parent, NewInfo->FileName, DirEnt->Entry.Attributes, NewFileName);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (*NewFileName != 0) {
    //
    // File was not found.  We do not allow rename of the current directory if
    // there are open files below the current directory
    //
    if (!IsListEmpty (&OFile->ChildHead) || (Parent == OFile)) {
      return EFI_ACCESS_DENIED;
    }

    if (ReadOnly) {
      return EFI_ACCESS_DENIED;
    }

    Status = FatRemoveDirEnt (OFile->Parent, DirEnt);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Create new dirent
    //
    Status = FatCreateDirEnt (Parent, NewFileName, DirEnt->Entry.Attributes, &TempDirEnt);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    FatCloneDirEnt (TempDirEnt, DirEnt);
    FatFreeDirEnt (DirEnt);
    DirEnt        = TempDirEnt;
    DirEnt->OFile = OFile;
    OFile->DirEnt = DirEnt;
    OFile->Parent = Parent;
    RemoveEntryList (&OFile->ChildLink);
    InsertHeadList (&Parent->ChildHead, &OFile->ChildLink);
    //
    // If this is a directory, synchronize its dot directory entry
    //
    if (OFile->ODir != NULL) {
      //
      // Synchronize its dot entry
      //
      FatResetODirCursor (OFile);
      ASSERT (OFile->Parent != NULL);
      for (DotOFile = OFile; DotOFile != OFile->Parent->Parent; DotOFile = DotOFile->Parent) {
        Status = FatGetNextDirEnt (OFile, &DirEnt);
        if (EFI_ERROR (Status) || (DirEnt == NULL) || !FatIsDotDirEnt (DirEnt)) {
          return EFI_VOLUME_CORRUPTED;
        }

        FatCloneDirEnt (DirEnt, DotOFile->DirEnt);
        Status = FatStoreDirEnt (OFile, DirEnt);
        if (EFI_ERROR (Status)) {
          return Status;
        }
      }
    }

    //
    // If the file is renamed, we should append the ARCHIVE attribute
    //
    OFile->Archive = TRUE;
  } else if (Parent != OFile) {
    //
    // filename is to a different filename that already exists
    //
    return EFI_ACCESS_DENIED;
  }

  //
  // If the file size has changed, apply it
  //
  if (NewInfo->FileSize != OFile->FileSize) {
    if ((OFile->ODir != NULL) || ReadOnly) {
      //
      // If this is a directory or the file is read only, we can't change the file size
      //
      return EFI_ACCESS_DENIED;
    }

    if (NewInfo->FileSize > OFile->FileSize) {
      Status = FatExpandOFile (OFile, NewInfo->FileSize);
    } else {
      Status = FatTruncateOFile (OFile, (UINTN)NewInfo->FileSize);
    }

    if (EFI_ERROR (Status)) {
      return Status;
    }

    FatUpdateDirEntClusterSizeInfo (OFile);
  }

  OFile->Dirty = TRUE;
  return FatOFileFlush (OFile);
}

/**

  Set or Get the some types info of the file into Buffer.

  @param  IsSet      - TRUE:The access is set, else is get
  @param  FHand      - The handle of file
  @param  Type       - The type of the info
  @param  BufferSize - Size of Buffer
  @param  Buffer     - Buffer containing volume info

  @retval EFI_SUCCESS       - Get the info successfully
  @retval EFI_DEVICE_ERROR  - Can not find the OFile for the file

**/
EFI_STATUS
FatSetOrGetInfo (
  IN     BOOLEAN            IsSet,
  IN     EFI_FILE_PROTOCOL  *FHand,
  IN     EFI_GUID           *Type,
  IN OUT UINTN              *BufferSize,
  IN OUT VOID               *Buffer
  )
{
  FAT_IFILE   *IFile;
  FAT_OFILE   *OFile;
  FAT_VOLUME  *Volume;
  EFI_STATUS  Status;

  IFile  = IFILE_FROM_FHAND (FHand);
  OFile  = IFile->OFile;
  Volume = OFile->Volume;

  Status = OFile->Error;
  if (Status == EFI_NOT_FOUND) {
    return EFI_DEVICE_ERROR;
  }

  FatWaitNonblockingTask (IFile);

  FatAcquireLock ();

  //
  // Verify the file handle isn't in an error state
  //
  if (!EFI_ERROR (Status)) {
    //
    // Get the proper information based on the request
    //
    Status = EFI_UNSUPPORTED;
    if (IsSet) {
      if (CompareGuid (Type, &gEfiFileInfoGuid)) {
        Status = Volume->ReadOnly ? EFI_WRITE_PROTECTED : FatSetFileInfo (Volume, IFile, OFile, *BufferSize, Buffer);
      }

      if (CompareGuid (Type, &gEfiFileSystemInfoGuid)) {
        Status = Volume->ReadOnly ? EFI_WRITE_PROTECTED : FatSetVolumeInfo (Volume, *BufferSize, Buffer);
      }

      if (CompareGuid (Type, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
        Status = Volume->ReadOnly ? EFI_WRITE_PROTECTED : FatSetVolumeLabelInfo (Volume, *BufferSize, Buffer);
      }
    } else {
      if (CompareGuid (Type, &gEfiFileInfoGuid)) {
        Status = FatGetFileInfo (OFile, BufferSize, Buffer);
      }

      if (CompareGuid (Type, &gEfiFileSystemInfoGuid)) {
        Status = FatGetVolumeInfo (Volume, BufferSize, Buffer);
      }

      if (CompareGuid (Type, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
        Status = FatGetVolumeLabelInfo (Volume, BufferSize, Buffer);
      }
    }
  }

  Status = FatCleanupVolume (Volume, NULL, Status, NULL);

  FatReleaseLock ();
  return Status;
}

/**

  Get the some types info of the file into Buffer.

  @param  FHand                 - The handle of file.
  @param  Type                  - The type of the info.
  @param  BufferSize            - Size of Buffer.
  @param  Buffer                - Buffer containing volume info.

  @retval EFI_SUCCESS           - Get the info successfully.
  @retval EFI_DEVICE_ERROR      - Can not find the OFile for the file.

**/
EFI_STATUS
EFIAPI
FatGetInfo (
  IN     EFI_FILE_PROTOCOL  *FHand,
  IN     EFI_GUID           *Type,
  IN OUT UINTN              *BufferSize,
  OUT VOID                  *Buffer
  )
{
  return FatSetOrGetInfo (FALSE, FHand, Type, BufferSize, Buffer);
}

/**

  Set the some types info of the file into Buffer.

  @param  FHand                 - The handle of file.
  @param  Type                  - The type of the info.
  @param  BufferSize            - Size of Buffer
  @param  Buffer                - Buffer containing volume info.

  @retval EFI_SUCCESS           - Set the info successfully.
  @retval EFI_DEVICE_ERROR      - Can not find the OFile for the file.

**/
EFI_STATUS
EFIAPI
FatSetInfo (
  IN EFI_FILE_PROTOCOL  *FHand,
  IN EFI_GUID           *Type,
  IN UINTN              BufferSize,
  IN VOID               *Buffer
  )
{
  return FatSetOrGetInfo (TRUE, FHand, Type, &BufferSize, Buffer);
}
