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

Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials are licensed and made available
under the terms and conditions of the BSD License which accompanies this
distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.



**/

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

  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) {
      //
      // Syncronize 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);
}
