/*++

Copyright (c) 2005 - 2013, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the Software
License Agreement which accompanies this distribution.


Module Name:

  flush.c

Abstract:

  Routines that check references and flush OFiles

Revision History

--*/

#include "Fat.h"

EFI_STATUS
EFIAPI
FatFlushEx (
  IN EFI_FILE_PROTOCOL  *FHand,
  IN EFI_FILE_IO_TOKEN  *Token
  )
/*++

Routine Description:

  Flushes all data associated with the file handle.

Arguments:

  FHand                 - Handle to file to flush.
  Token                 - A pointer to the token associated with the transaction.

Returns:

  EFI_SUCCESS           - Flushed the file successfully.
  EFI_WRITE_PROTECTED   - The volume is read only.
  EFI_ACCESS_DENIED     - The file is read only.
  Others                - Flushing of the file failed.

--*/
{
  FAT_IFILE   *IFile;
  FAT_OFILE   *OFile;
  FAT_VOLUME  *Volume;
  EFI_STATUS  Status;
  FAT_TASK    *Task;

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

  //
  // If the file has a permanent error, return it
  //
  if (EFI_ERROR (OFile->Error)) {
    return OFile->Error;
  }

  if (Volume->ReadOnly) {
    return EFI_WRITE_PROTECTED;
  }
  //
  // If read only, return error
  //
  if (IFile->ReadOnly) {
    return EFI_ACCESS_DENIED;
  }

  if (Token == NULL) {
    FatWaitNonblockingTask (IFile);
  } else {
    //
    // Caller shouldn't call the non-blocking interfaces if the low layer doesn't support DiskIo2.
    // But if it calls, the below check can avoid crash.
    //
    if (FHand->Revision < EFI_FILE_PROTOCOL_REVISION2) {
      return EFI_UNSUPPORTED;
    }
    Task = FatCreateTask (IFile, Token);
    if (Task == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  //
  // Flush the OFile
  //
  FatAcquireLock ();
  Status  = FatOFileFlush (OFile);
  Status  = FatCleanupVolume (OFile->Volume, OFile, Status, Task);
  FatReleaseLock ();

  if (Token != NULL) {
    if (!EFI_ERROR (Status)) {
      Status = FatQueueTask (IFile, Task);
    } else {
      FatDestroyTask (Task);
    }
  }

  return Status;
}

EFI_STATUS
EFIAPI
FatFlush (
  IN EFI_FILE_PROTOCOL  *FHand
  )
/*++

Routine Description:

  Flushes all data associated with the file handle.

Arguments:

  FHand                 - Handle to file to flush.

Returns:

  EFI_SUCCESS           - Flushed the file successfully.
  EFI_WRITE_PROTECTED   - The volume is read only.
  EFI_ACCESS_DENIED     - The file is read only.
  Others                - Flushing of the file failed.

--*/
{
  return FatFlushEx (FHand, NULL);
}

EFI_STATUS
EFIAPI
FatClose (
  IN EFI_FILE_PROTOCOL  *FHand
  )
/*++

Routine Description:

  Flushes & Closes the file handle.

Arguments:

  FHand                 - Handle to the file to delete.

Returns:

  EFI_SUCCESS           - Closed the file successfully.

--*/
{
  FAT_IFILE   *IFile;
  FAT_OFILE   *OFile;
  FAT_VOLUME  *Volume;

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

  //
  // Lock the volume
  //
  FatAcquireLock ();

  //
  // Close the file instance handle
  //
  FatIFileClose (IFile);

  //
  // Done. Unlock the volume
  //
  FatCleanupVolume (Volume, OFile, EFI_SUCCESS, NULL);
  FatReleaseLock ();

  //
  // Close always succeed
  //
  return EFI_SUCCESS;
}

EFI_STATUS
FatIFileClose (
  FAT_IFILE           *IFile
  )
/*++

Routine Description:

  Close the open file instance.

Arguments:

  IFile                 - Open file instance.

Returns:

  EFI_SUCCESS           - Closed the file successfully.

--*/
{
  FAT_OFILE   *OFile;
  FAT_VOLUME  *Volume;

  OFile   = IFile->OFile;
  Volume  = OFile->Volume;

  ASSERT_VOLUME_LOCKED (Volume);

  FatWaitNonblockingTask (IFile);

  //
  // Remove the IFile struct
  //
  RemoveEntryList (&IFile->Link);

  //
  // Add the OFile to the check reference list
  //
  if (OFile->CheckLink.ForwardLink == NULL) {
    InsertHeadList (&Volume->CheckRef, &OFile->CheckLink);
  }
  //
  // Done. Free the open instance structure
  //
  FreePool (IFile);
  return EFI_SUCCESS;
}

EFI_STATUS
FatOFileFlush (
  IN FAT_OFILE    *OFile
  )
/*++

Routine Description:

  Flush the data associated with an open file.
  In this implementation, only last Mod/Access time is updated.

Arguments:

  OFile                 - The open file.

Returns:

  EFI_SUCCESS           - The OFile is flushed successfully.
  Others                - An error occurred when flushing this OFile.

--*/
{
  EFI_STATUS    Status;
  FAT_OFILE     *Parent;
  FAT_DIRENT    *DirEnt;
  FAT_DATE_TIME FatNow;

  //
  // Flush each entry up the tree while dirty
  //
  do {
    //
    // If the file has a permanant error, then don't write any
    // of its data to the device (may be from different media)
    //
    if (EFI_ERROR (OFile->Error)) {
      return OFile->Error;
    }

    Parent  = OFile->Parent;
    DirEnt  = OFile->DirEnt;
    if (OFile->Dirty) {
      //
      // Update the last modification time
      //
      FatGetCurrentFatTime (&FatNow);
      CopyMem (&DirEnt->Entry.FileLastAccess, &FatNow.Date, sizeof (FAT_DATE));
      if (!OFile->PreserveLastModification) {
        FatGetCurrentFatTime (&DirEnt->Entry.FileModificationTime);
      }

      OFile->PreserveLastModification = FALSE;
      if (OFile->Archive) {
        DirEnt->Entry.Attributes |= FAT_ATTRIBUTE_ARCHIVE;
        OFile->Archive = FALSE;
      }
      //
      // Write the directory entry
      //
      if (Parent != NULL && !DirEnt->Invalid) {
        //
        // Write the OFile's directory entry
        //
        Status = FatStoreDirEnt (Parent, DirEnt);
        if (EFI_ERROR (Status)) {
          return Status;
        }
      }

      OFile->Dirty = FALSE;
    }
    //
    // Check the parent
    //
    OFile = Parent;
  } while (OFile != NULL);
  return EFI_SUCCESS;
}

BOOLEAN
FatCheckOFileRef (
  IN FAT_OFILE   *OFile
  )
/*++

Routine Description:

  Check the references of the OFile.
  If the OFile (that is checked) is no longer
  referenced, then it is freed.

Arguments:

  OFile                 - The OFile to be checked.

Returns:

  TRUE                  - The OFile is not referenced and freed.
  FALSE                 - The OFile is kept.

--*/
{
  //
  // If the OFile is on the check ref list, remove it
  //
  if (OFile->CheckLink.ForwardLink != NULL) {
    RemoveEntryList (&OFile->CheckLink);
    OFile->CheckLink.ForwardLink = NULL;
  }

  FatOFileFlush (OFile);
  //
  // Are there any references to this OFile?
  //
  if (!IsListEmpty (&OFile->Opens) || !IsListEmpty (&OFile->ChildHead)) {
    //
    // The OFile cannot be freed
    //
    return FALSE;
  }
  //
  // Free the Ofile
  //
  FatCloseDirEnt (OFile->DirEnt);
  return TRUE;
}

STATIC
VOID
FatCheckVolumeRef (
  IN FAT_VOLUME   *Volume
  )
/*++

Routine Description:

  Check the references of all open files on the volume.
  Any open file (that is checked) that is no longer
  referenced, is freed - and it's parent open file
  is then referenced checked.

Arguments:

  Volume                - The volume to check the pending open file list.

Returns:

  None

--*/
{
  FAT_OFILE *OFile;
  FAT_OFILE *Parent;

  //
  // Check all files on the pending check list
  //
  while (!IsListEmpty (&Volume->CheckRef)) {
    //
    // Start with the first file listed
    //
    Parent = OFILE_FROM_CHECKLINK (Volume->CheckRef.ForwardLink);
    //
    // Go up the tree cleaning up any un-referenced OFiles
    //
    while (Parent != NULL) {
      OFile   = Parent;
      Parent  = OFile->Parent;
      if (!FatCheckOFileRef (OFile)) {
        break;
      }
    }
  }
}

EFI_STATUS
FatCleanupVolume (
  IN FAT_VOLUME       *Volume,
  IN FAT_OFILE        *OFile,
  IN EFI_STATUS       EfiStatus,
  IN FAT_TASK         *Task
  )
/*++

Routine Description:

  Set error status for a specific OFile, reference checking the volume.
  If volume is already marked as invalid, and all resources are freed
  after reference checking, the file system protocol is uninstalled and
  the volume structure is freed.

Arguments:

  Volume                - the Volume that is to be reference checked and unlocked.
  OFile                 - the OFile whose permanent error code is to be set.
  EfiStatus             - error code to be set.

Returns:

  EFI_SUCCESS           - Clean up the volume successfully.
  Others                - Cleaning up of the volume is failed.

--*/
{
  EFI_STATUS  Status;
  //
  // Flag the OFile
  //
  if (OFile != NULL) {
    FatSetVolumeError (OFile, EfiStatus);
  }
  //
  // Clean up any dangling OFiles that don't have IFiles
  // we don't check return status here because we want the
  // volume be cleaned up even the volume is invalid.
  //
  FatCheckVolumeRef (Volume);
  if (Volume->Valid) {
    //
    // Update the free hint info. Volume->FreeInfoPos != 0
    // indicates this a FAT32 volume
    //
    if (Volume->FreeInfoValid && Volume->FatDirty && Volume->FreeInfoPos) {
      Status = FatDiskIo (Volume, WRITE_DISK, Volume->FreeInfoPos, sizeof (FAT_INFO_SECTOR), &Volume->FatInfoSector, Task);
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }
    //
    // Update that the volume is not dirty
    //
    if (Volume->FatDirty && Volume->FatType != FAT12) {
      Volume->FatDirty  = FALSE;
      Status            = FatAccessVolumeDirty (Volume, WRITE_FAT, &Volume->NotDirtyValue);
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }
    //
    // Flush all dirty cache entries to disk
    //
    Status = FatVolumeFlushCache (Volume, Task);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }
  //
  // If the volume is cleared , remove it.
  // The only time volume be invalidated is in DriverBindingStop.
  //
  if (Volume->Root == NULL && !Volume->Valid) {
    //
    // Free the volume structure
    //
    FatFreeVolume (Volume);
  }

  return EfiStatus;
}

VOID
FatSetVolumeError (
  IN FAT_OFILE            *OFile,
  IN EFI_STATUS           Status
  )
/*++

Routine Description:

  Set the OFile and its child OFile with the error Status

Arguments:

  OFile                 - The OFile whose permanent error code is to be set.
  Status                - Error code to be set.

Returns:

  None

--*/
{
  LIST_ENTRY      *Link;
  FAT_OFILE       *ChildOFile;

  //
  // If this OFile doesn't already have an error, set one
  //
  if (!EFI_ERROR (OFile->Error)) {
    OFile->Error = Status;
  }
  //
  // Set the error on each child OFile
  //
  for (Link = OFile->ChildHead.ForwardLink; Link != &OFile->ChildHead; Link = Link->ForwardLink) {
    ChildOFile = OFILE_FROM_CHILDLINK (Link);
    FatSetVolumeError (ChildOFile, Status);
  }
}
