/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS kernel
 * FILE:             drivers/fs/vfat/finfo.c
 * PURPOSE:          VFAT Filesystem
 * PROGRAMMER:       Jason Filby (jasonfilby@yahoo.com)
 *                   Herve Poussineau (reactos@poussine.freesurf.fr)
 *
 */

/* INCLUDES *****************************************************************/

#define NDEBUG
#include "vfat.h"

/* GLOBALS ******************************************************************/

const char* FileInformationClassNames[] =
{
  "??????",
  "FileDirectoryInformation",
  "FileFullDirectoryInformation",
  "FileBothDirectoryInformation",
  "FileBasicInformation",
  "FileStandardInformation",
  "FileInternalInformation",
  "FileEaInformation",
  "FileAccessInformation",
  "FileNameInformation",
  "FileRenameInformation",
  "FileLinkInformation",
  "FileNamesInformation",
  "FileDispositionInformation",
  "FilePositionInformation",
  "FileFullEaInformation",
  "FileModeInformation",
  "FileAlignmentInformation",
  "FileAllInformation",
  "FileAllocationInformation",
  "FileEndOfFileInformation",
  "FileAlternateNameInformation",
  "FileStreamInformation",
  "FilePipeInformation",
  "FilePipeLocalInformation",
  "FilePipeRemoteInformation",
  "FileMailslotQueryInformation",
  "FileMailslotSetInformation",
  "FileCompressionInformation",
  "FileObjectIdInformation",
  "FileCompletionInformation",
  "FileMoveClusterInformation",
  "FileQuotaInformation",
  "FileReparsePointInformation",
  "FileNetworkOpenInformation",
  "FileAttributeTagInformation",
  "FileTrackingInformation",
  "FileIdBothDirectoryInformation",
  "FileIdFullDirectoryInformation",
  "FileValidDataLengthInformation",
  "FileShortNameInformation",
  "FileMaximumInformation"
};

/* FUNCTIONS ****************************************************************/

static NTSTATUS
VfatGetStandardInformation(PVFATFCB FCB,
			   PFILE_STANDARD_INFORMATION StandardInfo,
			   PULONG BufferLength)
/*
 * FUNCTION: Retrieve the standard file information
 */
{

  if (*BufferLength < sizeof(FILE_STANDARD_INFORMATION))
    return STATUS_BUFFER_OVERFLOW;

  /* PRECONDITION */
  ASSERT(StandardInfo != NULL);
  ASSERT(FCB != NULL);

  if (vfatFCBIsDirectory(FCB))
    {
      StandardInfo->AllocationSize.QuadPart = 0;
      StandardInfo->EndOfFile.QuadPart = 0;
      StandardInfo->Directory = TRUE;
    }
  else
    {
      StandardInfo->AllocationSize = FCB->RFCB.AllocationSize;
      StandardInfo->EndOfFile = FCB->RFCB.FileSize;
      StandardInfo->Directory = FALSE;
    }
  StandardInfo->NumberOfLinks = 1;
  StandardInfo->DeletePending = FCB->Flags & FCB_DELETE_PENDING ? TRUE : FALSE;

  *BufferLength -= sizeof(FILE_STANDARD_INFORMATION);
  return(STATUS_SUCCESS);
}

static NTSTATUS
VfatSetPositionInformation(PFILE_OBJECT FileObject,
			   PFILE_POSITION_INFORMATION PositionInfo)
{
  DPRINT ("FsdSetPositionInformation()\n");

  DPRINT ("PositionInfo %p\n", PositionInfo);
  DPRINT ("Setting position %d\n", PositionInfo->CurrentByteOffset.u.LowPart);

  FileObject->CurrentByteOffset.QuadPart =
    PositionInfo->CurrentByteOffset.QuadPart;

  return (STATUS_SUCCESS);
}

static NTSTATUS
VfatGetPositionInformation(PFILE_OBJECT FileObject,
			   PVFATFCB FCB,
			   PDEVICE_OBJECT DeviceObject,
			   PFILE_POSITION_INFORMATION PositionInfo,
			   PULONG BufferLength)
{
  UNREFERENCED_PARAMETER(FileObject);
  UNREFERENCED_PARAMETER(FCB);
  UNREFERENCED_PARAMETER(DeviceObject);

  DPRINT ("VfatGetPositionInformation()\n");

  if (*BufferLength < sizeof(FILE_POSITION_INFORMATION))
    return STATUS_BUFFER_OVERFLOW;

  PositionInfo->CurrentByteOffset.QuadPart =
    FileObject->CurrentByteOffset.QuadPart;

  DPRINT("Getting position %I64x\n",
	 PositionInfo->CurrentByteOffset.QuadPart);

  *BufferLength -= sizeof(FILE_POSITION_INFORMATION);
  return(STATUS_SUCCESS);
}

static NTSTATUS
VfatSetBasicInformation(PFILE_OBJECT FileObject,
			PVFATFCB FCB,
			PDEVICE_EXTENSION DeviceExt,
			PFILE_BASIC_INFORMATION BasicInfo)
{
  DPRINT("VfatSetBasicInformation()\n");

  ASSERT(NULL != FileObject);
  ASSERT(NULL != FCB);
  ASSERT(NULL != DeviceExt);
  ASSERT(NULL != BasicInfo);
  /* Check volume label bit */
  ASSERT(0 == (*FCB->Attributes & _A_VOLID));

  if (FCB->Flags & FCB_IS_FATX_ENTRY)
  {
    if (BasicInfo->CreationTime.QuadPart != 0 && BasicInfo->CreationTime.QuadPart != -1)
    {
      FsdSystemTimeToDosDateTime(DeviceExt,
                                 &BasicInfo->CreationTime,
                                 &FCB->entry.FatX.CreationDate,
                                 &FCB->entry.FatX.CreationTime);
    }

    if (BasicInfo->LastAccessTime.QuadPart != 0 && BasicInfo->LastAccessTime.QuadPart != -1)
    {
      FsdSystemTimeToDosDateTime(DeviceExt,
                                 &BasicInfo->LastAccessTime,
                                 &FCB->entry.FatX.AccessDate,
                                 &FCB->entry.FatX.AccessTime);
    }

    if (BasicInfo->LastWriteTime.QuadPart != 0 && BasicInfo->LastWriteTime.QuadPart != -1)
    {
      FsdSystemTimeToDosDateTime(DeviceExt,
                                 &BasicInfo->LastWriteTime,
                                 &FCB->entry.FatX.UpdateDate,
                                 &FCB->entry.FatX.UpdateTime);
    }
  }
  else
  {
    if (BasicInfo->CreationTime.QuadPart != 0 && BasicInfo->CreationTime.QuadPart != -1)
    {
      FsdSystemTimeToDosDateTime(DeviceExt,
                                 &BasicInfo->CreationTime,
                                 &FCB->entry.Fat.CreationDate,
                                 &FCB->entry.Fat.CreationTime);
    }

    if (BasicInfo->LastAccessTime.QuadPart != 0 && BasicInfo->LastAccessTime.QuadPart != -1)
    {
      FsdSystemTimeToDosDateTime(DeviceExt,
                                 &BasicInfo->LastAccessTime,
                                 &FCB->entry.Fat.AccessDate,
                                 NULL);
    }

    if (BasicInfo->LastWriteTime.QuadPart != 0 && BasicInfo->LastWriteTime.QuadPart != -1)
    {
      FsdSystemTimeToDosDateTime(DeviceExt,
                                 &BasicInfo->LastWriteTime,
                                 &FCB->entry.Fat.UpdateDate,
                                 &FCB->entry.Fat.UpdateTime);
    }
  }

  if (BasicInfo->FileAttributes)
  {
    *FCB->Attributes = (unsigned char)((*FCB->Attributes &
                        (FILE_ATTRIBUTE_DIRECTORY | 0x48)) |
                        (BasicInfo->FileAttributes &
                         (FILE_ATTRIBUTE_ARCHIVE |
                          FILE_ATTRIBUTE_SYSTEM |
                          FILE_ATTRIBUTE_HIDDEN |
                          FILE_ATTRIBUTE_READONLY)));
    DPRINT("Setting attributes 0x%02x\n", *FCB->Attributes);
  }

  VfatUpdateEntry(FCB);

  return(STATUS_SUCCESS);
}

static NTSTATUS
VfatGetBasicInformation(PFILE_OBJECT FileObject,
			PVFATFCB FCB,
			PDEVICE_OBJECT DeviceObject,
			PFILE_BASIC_INFORMATION BasicInfo,
			PULONG BufferLength)
{
  PDEVICE_EXTENSION DeviceExt;
  
  UNREFERENCED_PARAMETER(FileObject);
  
  DPRINT("VfatGetBasicInformation()\n");

  DeviceExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

  if (*BufferLength < sizeof(FILE_BASIC_INFORMATION))
    return STATUS_BUFFER_OVERFLOW;

  if (FCB->Flags & FCB_IS_FATX_ENTRY)
  {
    FsdDosDateTimeToSystemTime(DeviceExt,
			     FCB->entry.FatX.CreationDate,
			     FCB->entry.FatX.CreationTime,
			     &BasicInfo->CreationTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     FCB->entry.FatX.AccessDate,
			     FCB->entry.FatX.AccessTime,
			     &BasicInfo->LastAccessTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     FCB->entry.FatX.UpdateDate,
			     FCB->entry.FatX.UpdateTime,
			     &BasicInfo->LastWriteTime);
    BasicInfo->ChangeTime = BasicInfo->LastWriteTime;
  }
  else
  {
    FsdDosDateTimeToSystemTime(DeviceExt,
			     FCB->entry.Fat.CreationDate,
			     FCB->entry.Fat.CreationTime,
			     &BasicInfo->CreationTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     FCB->entry.Fat.AccessDate,
			     0,
			     &BasicInfo->LastAccessTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     FCB->entry.Fat.UpdateDate,
			     FCB->entry.Fat.UpdateTime,
			     &BasicInfo->LastWriteTime);
    BasicInfo->ChangeTime = BasicInfo->LastWriteTime;
  }

  BasicInfo->FileAttributes = *FCB->Attributes & 0x3f;
  /* Synthesize FILE_ATTRIBUTE_NORMAL */
  if (0 == (BasicInfo->FileAttributes & (FILE_ATTRIBUTE_DIRECTORY |
                                         FILE_ATTRIBUTE_ARCHIVE |
                                         FILE_ATTRIBUTE_SYSTEM |
                                         FILE_ATTRIBUTE_HIDDEN |
                                         FILE_ATTRIBUTE_READONLY)))
  {
    DPRINT("Synthesizing FILE_ATTRIBUTE_NORMAL\n");
    BasicInfo->FileAttributes |= FILE_ATTRIBUTE_NORMAL;
  }
  DPRINT("Getting attributes 0x%02x\n", BasicInfo->FileAttributes);

  *BufferLength -= sizeof(FILE_BASIC_INFORMATION);
  return(STATUS_SUCCESS);
}


static NTSTATUS
VfatSetDispositionInformation(PFILE_OBJECT FileObject,
			      PVFATFCB FCB,
			      PDEVICE_OBJECT DeviceObject,
			      PFILE_DISPOSITION_INFORMATION DispositionInfo)
{
#if DBG
   PDEVICE_EXTENSION DeviceExt = DeviceObject->DeviceExtension;
#endif

   DPRINT ("FsdSetDispositionInformation(<%wZ>, Delete %d)\n", &FCB->PathNameU, DispositionInfo->DeleteFile);

   ASSERT(DeviceExt != NULL);
   ASSERT(DeviceExt->FatInfo.BytesPerCluster != 0);
   ASSERT(FCB != NULL);

   if (!DispositionInfo->DeleteFile)
   {
      /* undelete the file */
      FCB->Flags &= ~FCB_DELETE_PENDING;
      FileObject->DeletePending = FALSE;
      return STATUS_SUCCESS;
   }

   if (FCB->Flags & FCB_DELETE_PENDING)
   {
      /* stream already marked for deletion. just update the file object */
      FileObject->DeletePending = TRUE;
      return STATUS_SUCCESS;
   }

   if (*FCB->Attributes & FILE_ATTRIBUTE_READONLY)
   {
      return STATUS_CANNOT_DELETE;
   }

   if (vfatFCBIsRoot(FCB) ||
     (FCB->LongNameU.Length == sizeof(WCHAR) && FCB->LongNameU.Buffer[0] == L'.') ||
     (FCB->LongNameU.Length == 2 * sizeof(WCHAR) && FCB->LongNameU.Buffer[0] == L'.' && FCB->LongNameU.Buffer[1] == L'.'))
   {
      // we cannot delete a '.', '..' or the root directory
      return STATUS_ACCESS_DENIED;
   }


   if (!MmFlushImageSection (FileObject->SectionObjectPointer, MmFlushForDelete))
   {
      /* can't delete a file if its mapped into a process */

      DPRINT("MmFlushImageSection returned FALSE\n");
      return STATUS_CANNOT_DELETE;
   }

   if (vfatFCBIsDirectory(FCB) && !VfatIsDirectoryEmpty(FCB))
   {
      /* can't delete a non-empty directory */

      return STATUS_DIRECTORY_NOT_EMPTY;
   }

   /* all good */
   FCB->Flags |= FCB_DELETE_PENDING;
   FileObject->DeletePending = TRUE;

   return STATUS_SUCCESS;
}

static NTSTATUS
VfatGetNameInformation(PFILE_OBJECT FileObject,
		       PVFATFCB FCB,
		       PDEVICE_OBJECT DeviceObject,
		       PFILE_NAME_INFORMATION NameInfo,
		       PULONG BufferLength)
/*
 * FUNCTION: Retrieve the file name information
 */
{
  ULONG BytesToCopy;

  UNREFERENCED_PARAMETER(FileObject);
  UNREFERENCED_PARAMETER(DeviceObject);

  ASSERT(NameInfo != NULL);
  ASSERT(FCB != NULL);

  /* If buffer can't hold at least the file name length, bail out */
  if (*BufferLength < (ULONG)FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]))
    return STATUS_BUFFER_OVERFLOW;

  /* Save file name length, and as much file len, as buffer length allows */
  NameInfo->FileNameLength = FCB->PathNameU.Length;

  /* Calculate amount of bytes to copy not to overflow the buffer */
  BytesToCopy = min(FCB->PathNameU.Length,
                    *BufferLength - FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]));

  /* Fill in the bytes */
  RtlCopyMemory(NameInfo->FileName, FCB->PathNameU.Buffer, BytesToCopy);

  /* Check if we could write more but are not able to */
  if (*BufferLength < FCB->PathNameU.Length + (ULONG)FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]))
  {
    /* Return number of bytes written */
    *BufferLength -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]) + BytesToCopy;
    return STATUS_BUFFER_OVERFLOW;
  }

  /* We filled up as many bytes, as needed */
  *BufferLength -= (FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]) + FCB->PathNameU.Length);

  return STATUS_SUCCESS;
}

static NTSTATUS
VfatGetInternalInformation(PVFATFCB Fcb,
			   PFILE_INTERNAL_INFORMATION InternalInfo,
			   PULONG BufferLength)
{
  ASSERT(InternalInfo);
  ASSERT(Fcb);

  if (*BufferLength < sizeof(FILE_INTERNAL_INFORMATION))
    return STATUS_BUFFER_OVERFLOW;
  // FIXME: get a real index, that can be used in a create operation
  InternalInfo->IndexNumber.QuadPart = 0;
  *BufferLength -= sizeof(FILE_INTERNAL_INFORMATION);
  return STATUS_SUCCESS;
}


static NTSTATUS
VfatGetNetworkOpenInformation(PVFATFCB Fcb,
			      PDEVICE_EXTENSION DeviceExt,
			      PFILE_NETWORK_OPEN_INFORMATION NetworkInfo,
			      PULONG BufferLength)
/*
 * FUNCTION: Retrieve the file network open information
 */
{
  ASSERT(NetworkInfo);
  ASSERT(Fcb);

  if (*BufferLength < sizeof(FILE_NETWORK_OPEN_INFORMATION))
    return(STATUS_BUFFER_OVERFLOW);

  if (Fcb->Flags & FCB_IS_FATX_ENTRY)
  {
    FsdDosDateTimeToSystemTime(DeviceExt,
			     Fcb->entry.FatX.CreationDate,
			     Fcb->entry.FatX.CreationTime,
			     &NetworkInfo->CreationTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     Fcb->entry.FatX.AccessDate,
			     Fcb->entry.FatX.AccessTime,
			     &NetworkInfo->LastAccessTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     Fcb->entry.FatX.UpdateDate,
			     Fcb->entry.FatX.UpdateTime,
			     &NetworkInfo->LastWriteTime);
    NetworkInfo->ChangeTime.QuadPart = NetworkInfo->LastWriteTime.QuadPart;
  }
  else
  {
    FsdDosDateTimeToSystemTime(DeviceExt,
			     Fcb->entry.Fat.CreationDate,
			     Fcb->entry.Fat.CreationTime,
			     &NetworkInfo->CreationTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     Fcb->entry.Fat.AccessDate,
			     0,
			     &NetworkInfo->LastAccessTime);
    FsdDosDateTimeToSystemTime(DeviceExt,
			     Fcb->entry.Fat.UpdateDate,
			     Fcb->entry.Fat.UpdateTime,
			     &NetworkInfo->LastWriteTime);
    NetworkInfo->ChangeTime.QuadPart = NetworkInfo->LastWriteTime.QuadPart;
  }
  if (vfatFCBIsDirectory(Fcb))
    {
      NetworkInfo->EndOfFile.QuadPart = 0L;
      NetworkInfo->AllocationSize.QuadPart = 0L;
    }
  else
    {
      NetworkInfo->AllocationSize = Fcb->RFCB.AllocationSize;
      NetworkInfo->EndOfFile = Fcb->RFCB.FileSize;
    }
  NetworkInfo->FileAttributes = *Fcb->Attributes & 0x3f;
  /* Synthesize FILE_ATTRIBUTE_NORMAL */
  if (0 == (NetworkInfo->FileAttributes & (FILE_ATTRIBUTE_DIRECTORY |
                                           FILE_ATTRIBUTE_ARCHIVE |
                                           FILE_ATTRIBUTE_SYSTEM |
                                           FILE_ATTRIBUTE_HIDDEN |
                                           FILE_ATTRIBUTE_READONLY)))
  {
    DPRINT("Synthesizing FILE_ATTRIBUTE_NORMAL\n");
    NetworkInfo->FileAttributes |= FILE_ATTRIBUTE_NORMAL;
  }

  *BufferLength -= sizeof(FILE_NETWORK_OPEN_INFORMATION);
  return STATUS_SUCCESS;
}


static NTSTATUS
VfatGetEaInformation(PFILE_OBJECT FileObject,
		     PVFATFCB Fcb,
		     PDEVICE_OBJECT DeviceObject,
		     PFILE_EA_INFORMATION Info,
		     PULONG BufferLength)
{
    PDEVICE_EXTENSION DeviceExt = DeviceObject->DeviceExtension;

    UNREFERENCED_PARAMETER(FileObject);
    UNREFERENCED_PARAMETER(Fcb);

    /* FIXME - use SEH to access the buffer! */
    Info->EaSize = 0;
    *BufferLength -= sizeof(*Info);
    if (DeviceExt->FatInfo.FatType == FAT12 ||
        DeviceExt->FatInfo.FatType == FAT16)
    {
        /* FIXME */
        DPRINT1("VFAT: FileEaInformation not implemented!\n");
    }
    return STATUS_SUCCESS;
}


static NTSTATUS
VfatGetAllInformation(PFILE_OBJECT FileObject,
		      PVFATFCB Fcb,
		      PDEVICE_OBJECT DeviceObject,
		      PFILE_ALL_INFORMATION Info,
		      PULONG BufferLength)
/*
 * FUNCTION: Retrieve the all file information
 */
{
  NTSTATUS Status;
  ULONG InitialBufferLength = *BufferLength;

  ASSERT(Info);
  ASSERT(Fcb);

  if (*BufferLength < sizeof(FILE_ALL_INFORMATION) + Fcb->PathNameU.Length + sizeof(WCHAR))
    return(STATUS_BUFFER_OVERFLOW);

  /* Basic Information */
  Status = VfatGetBasicInformation(FileObject, Fcb, DeviceObject, &Info->BasicInformation, BufferLength);
  if (!NT_SUCCESS(Status)) return Status;
  /* Standard Information */
  Status = VfatGetStandardInformation(Fcb, &Info->StandardInformation, BufferLength);
  if (!NT_SUCCESS(Status)) return Status;
  /* Internal Information */
  Status = VfatGetInternalInformation(Fcb, &Info->InternalInformation, BufferLength);
  if (!NT_SUCCESS(Status)) return Status;
  /* EA Information */
  Info->EaInformation.EaSize = 0;
  /* Access Information: The IO-Manager adds this information */
  /* Position Information */
  Status = VfatGetPositionInformation(FileObject, Fcb, DeviceObject, &Info->PositionInformation, BufferLength);
  if (!NT_SUCCESS(Status)) return Status;
  /* Mode Information: The IO-Manager adds this information */
  /* Alignment Information: The IO-Manager adds this information */
  /* Name Information */
  Status = VfatGetNameInformation(FileObject, Fcb, DeviceObject, &Info->NameInformation, BufferLength);
  if (!NT_SUCCESS(Status)) return Status;

  *BufferLength = InitialBufferLength - (sizeof(FILE_ALL_INFORMATION) + Fcb->PathNameU.Length + sizeof(WCHAR));

  return STATUS_SUCCESS;
}

static VOID UpdateFileSize(PFILE_OBJECT FileObject, PVFATFCB Fcb, ULONG Size, ULONG ClusterSize)
{
   if (Size > 0)
   {
      Fcb->RFCB.AllocationSize.QuadPart = ROUND_UP(Size, ClusterSize);
   }
   else
   {
      Fcb->RFCB.AllocationSize.QuadPart = (LONGLONG)0;
   }
   if (!vfatFCBIsDirectory(Fcb))
   {
      if (Fcb->Flags & FCB_IS_FATX_ENTRY)
         Fcb->entry.FatX.FileSize = Size;
      else
         Fcb->entry.Fat.FileSize = Size;
   }
   Fcb->RFCB.FileSize.QuadPart = Size;
   Fcb->RFCB.ValidDataLength.QuadPart = Size;

   CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
}

NTSTATUS
VfatSetAllocationSizeInformation(PFILE_OBJECT FileObject,
				 PVFATFCB Fcb,
				 PDEVICE_EXTENSION DeviceExt,
				 PLARGE_INTEGER AllocationSize)
{
  ULONG OldSize;
  ULONG Cluster, FirstCluster;
  NTSTATUS Status;

  ULONG ClusterSize = DeviceExt->FatInfo.BytesPerCluster;
  ULONG NewSize = AllocationSize->u.LowPart;
  ULONG NCluster;
  BOOLEAN AllocSizeChanged = FALSE;

  DPRINT("VfatSetAllocationSizeInformation(File <%wZ>, AllocationSize %d %d)\n", &Fcb->PathNameU,
      AllocationSize->HighPart, AllocationSize->LowPart);

  if (Fcb->Flags & FCB_IS_FATX_ENTRY)
    OldSize = Fcb->entry.FatX.FileSize;
  else
    OldSize = Fcb->entry.Fat.FileSize;
  if (AllocationSize->u.HighPart > 0)
  {
    return STATUS_INVALID_PARAMETER;
  }
  if (OldSize == NewSize)
    {
      return(STATUS_SUCCESS);
    }

  FirstCluster = vfatDirEntryGetFirstCluster (DeviceExt, &Fcb->entry);

  if (NewSize > Fcb->RFCB.AllocationSize.u.LowPart)
  {
    AllocSizeChanged = TRUE;
    if (FirstCluster == 0)
    {
      Fcb->LastCluster = Fcb->LastOffset = 0;
      Status = NextCluster (DeviceExt, FirstCluster, &FirstCluster, TRUE);
      if (!NT_SUCCESS(Status))
      {
	DPRINT1("NextCluster failed. Status = %x\n", Status);
	return Status;
      }
      if (FirstCluster == 0xffffffff)
      {
         return STATUS_DISK_FULL;
      }
      Status = OffsetToCluster(DeviceExt, FirstCluster,
                               ROUND_DOWN(NewSize - 1, ClusterSize),
                               &NCluster, TRUE);
      if (NCluster == 0xffffffff || !NT_SUCCESS(Status))
      {
         /* disk is full */
         NCluster = Cluster = FirstCluster;
         Status = STATUS_SUCCESS;
         while (NT_SUCCESS(Status) && Cluster != 0xffffffff && Cluster > 1)
         {
            Status = NextCluster (DeviceExt, FirstCluster, &NCluster, FALSE);
            WriteCluster (DeviceExt, Cluster, 0);
            Cluster = NCluster;
         }
         return STATUS_DISK_FULL;
      }
      if (Fcb->Flags & FCB_IS_FATX_ENTRY)
      {
         Fcb->entry.FatX.FirstCluster = FirstCluster;
      }
      else
      {
        if (DeviceExt->FatInfo.FatType == FAT32)
        {
          Fcb->entry.Fat.FirstCluster = (unsigned short)(FirstCluster & 0x0000FFFF);
          Fcb->entry.Fat.FirstClusterHigh = FirstCluster >> 16;
        }
        else
        {
            ASSERT((FirstCluster >> 16) == 0);
            Fcb->entry.Fat.FirstCluster = (unsigned short)(FirstCluster & 0x0000FFFF);
        }
      }
    }
    else
    {
       if (Fcb->LastCluster > 0)
       {
          if (Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize == Fcb->LastOffset)
          {
             Cluster = Fcb->LastCluster;
             Status = STATUS_SUCCESS;
          }
          else
          {
             Status = OffsetToCluster(DeviceExt, Fcb->LastCluster,
                                      Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize - Fcb->LastOffset,
                                      &Cluster, FALSE);
          }
       }
       else
       {
          Status = OffsetToCluster(DeviceExt, FirstCluster,
                                   Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize,
                                   &Cluster, FALSE);
       }
       if (!NT_SUCCESS(Status))
       {
          return Status;
       }

       Fcb->LastCluster = Cluster;
       Fcb->LastOffset = Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize;

       /* FIXME: Check status */
       /* Cluster points now to the last cluster within the chain */
       Status = OffsetToCluster(DeviceExt, Cluster,
	                        ROUND_DOWN(NewSize - 1, ClusterSize) - Fcb->LastOffset,
                                &NCluster, TRUE);
       if (NCluster == 0xffffffff || !NT_SUCCESS(Status))
       {
	  /* disk is full */
	  NCluster = Cluster;
          Status = NextCluster (DeviceExt, FirstCluster, &NCluster, FALSE);
	  WriteCluster(DeviceExt, Cluster, 0xffffffff);
	  Cluster = NCluster;
          while (NT_SUCCESS(Status) && Cluster != 0xffffffff && Cluster > 1)
	  {
	    Status = NextCluster (DeviceExt, FirstCluster, &NCluster, FALSE);
            WriteCluster (DeviceExt, Cluster, 0);
	    Cluster = NCluster;
	  }
	  return STATUS_DISK_FULL;
       }
    }
    UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize);
  }
  else if (NewSize + ClusterSize <= Fcb->RFCB.AllocationSize.u.LowPart)
  {

    DPRINT("Check for the ability to set file size\n");
    if (!MmCanFileBeTruncated
      (FileObject->SectionObjectPointer,
      (PLARGE_INTEGER)AllocationSize))
    {
      DPRINT("Couldn't set file size!\n");
      return STATUS_USER_MAPPED_FILE;
    }
    DPRINT("Can set file size\n");

    AllocSizeChanged = TRUE;
    /* FIXME: Use the cached cluster/offset better way. */
    Fcb->LastCluster = Fcb->LastOffset = 0;
    UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize);
    if (NewSize > 0)
    {
      Status = OffsetToCluster(DeviceExt, FirstCluster,
	          ROUND_DOWN(NewSize - 1, ClusterSize),
		  &Cluster, FALSE);

      NCluster = Cluster;
      Status = NextCluster (DeviceExt, FirstCluster, &NCluster, FALSE);
      WriteCluster(DeviceExt, Cluster, 0xffffffff);
      Cluster = NCluster;
    }
    else
    {
      if (Fcb->Flags & FCB_IS_FATX_ENTRY)
      {
         Fcb->entry.FatX.FirstCluster = 0;
      }
      else
      {
        if (DeviceExt->FatInfo.FatType == FAT32)
        {
          Fcb->entry.Fat.FirstCluster = 0;
          Fcb->entry.Fat.FirstClusterHigh = 0;
        }
        else
        {
            Fcb->entry.Fat.FirstCluster = 0;
        }
      }

      NCluster = Cluster = FirstCluster;
      Status = STATUS_SUCCESS;
    }
    while (NT_SUCCESS(Status) && 0xffffffff != Cluster && Cluster > 1)
    {
       Status = NextCluster (DeviceExt, FirstCluster, &NCluster, FALSE);
       WriteCluster (DeviceExt, Cluster, 0);
       Cluster = NCluster;
    }
  }
  else
  {
     UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize);
  }
  /* Update the on-disk directory entry */
  Fcb->Flags |= FCB_IS_DIRTY;
  if (AllocSizeChanged)
    {
      VfatUpdateEntry(Fcb);
    }
  return STATUS_SUCCESS;
}

NTSTATUS VfatQueryInformation(PVFAT_IRP_CONTEXT IrpContext)
/*
 * FUNCTION: Retrieve the specified file information
 */
{
  FILE_INFORMATION_CLASS FileInformationClass;
  PVFATFCB FCB = NULL;

  NTSTATUS Status = STATUS_SUCCESS;
  PVOID SystemBuffer;
  ULONG BufferLength;

  /* PRECONDITION */
  ASSERT(IrpContext);

  /* INITIALIZATION */
  FileInformationClass = IrpContext->Stack->Parameters.QueryFile.FileInformationClass;
  FCB = (PVFATFCB) IrpContext->FileObject->FsContext;

  DPRINT("VfatQueryInformation is called for '%s'\n",
         FileInformationClass >= FileMaximumInformation - 1 ? "????" : FileInformationClassNames[FileInformationClass]);


  SystemBuffer = IrpContext->Irp->AssociatedIrp.SystemBuffer;
  BufferLength = IrpContext->Stack->Parameters.QueryFile.Length;

  if (!(FCB->Flags & FCB_IS_PAGE_FILE))
  {
     if (!ExAcquireResourceSharedLite(&FCB->MainResource,
                                      (BOOLEAN)(IrpContext->Flags & IRPCONTEXT_CANWAIT)))
     {
        return VfatQueueRequest (IrpContext);
     }
  }


  switch (FileInformationClass)
    {
    case FileStandardInformation:
      Status = VfatGetStandardInformation(FCB,
				      SystemBuffer,
				      &BufferLength);
      break;
    case FilePositionInformation:
      Status = VfatGetPositionInformation(IrpContext->FileObject,
				      FCB,
				      IrpContext->DeviceObject,
				      SystemBuffer,
				      &BufferLength);
      break;
    case FileBasicInformation:
      Status = VfatGetBasicInformation(IrpContext->FileObject,
				   FCB,
				   IrpContext->DeviceObject,
				   SystemBuffer,
				   &BufferLength);
      break;
    case FileNameInformation:
      Status = VfatGetNameInformation(IrpContext->FileObject,
				  FCB,
				  IrpContext->DeviceObject,
				  SystemBuffer,
				  &BufferLength);
      break;
    case FileInternalInformation:
      Status = VfatGetInternalInformation(FCB,
				      SystemBuffer,
				      &BufferLength);
      break;
    case FileNetworkOpenInformation:
      Status = VfatGetNetworkOpenInformation(FCB,
					 IrpContext->DeviceExt,
					 SystemBuffer,
					 &BufferLength);
      break;
    case FileAllInformation:
      Status = VfatGetAllInformation(IrpContext->FileObject,
				 FCB,
				 IrpContext->DeviceObject,
				 SystemBuffer,
				 &BufferLength);
      break;

    case FileEaInformation:
      Status = VfatGetEaInformation(IrpContext->FileObject,
				FCB,
				IrpContext->DeviceObject,
				SystemBuffer,
				&BufferLength);
      break;

    case FileAlternateNameInformation:
      Status = STATUS_NOT_IMPLEMENTED;
      break;
    default:
      Status = STATUS_INVALID_PARAMETER;
    }

  if (!(FCB->Flags & FCB_IS_PAGE_FILE))
  {
     ExReleaseResourceLite(&FCB->MainResource);
  }
  IrpContext->Irp->IoStatus.Status = Status;
  if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW)
    IrpContext->Irp->IoStatus.Information =
      IrpContext->Stack->Parameters.QueryFile.Length - BufferLength;
  else
    IrpContext->Irp->IoStatus.Information = 0;
  IoCompleteRequest(IrpContext->Irp, IO_NO_INCREMENT);
  VfatFreeIrpContext(IrpContext);

  return Status;
}

NTSTATUS VfatSetInformation(PVFAT_IRP_CONTEXT IrpContext)
/*
 * FUNCTION: Retrieve the specified file information
 */
{
  FILE_INFORMATION_CLASS FileInformationClass;
  PVFATFCB FCB = NULL;
  NTSTATUS RC = STATUS_SUCCESS;
  PVOID SystemBuffer;
  BOOLEAN CanWait = (IrpContext->Flags & IRPCONTEXT_CANWAIT) != 0;

  /* PRECONDITION */
  ASSERT(IrpContext);

  DPRINT("VfatSetInformation(IrpContext %p)\n", IrpContext);

  /* INITIALIZATION */
  FileInformationClass =
    IrpContext->Stack->Parameters.SetFile.FileInformationClass;
  FCB = (PVFATFCB) IrpContext->FileObject->FsContext;
  SystemBuffer = IrpContext->Irp->AssociatedIrp.SystemBuffer;

  DPRINT("VfatSetInformation is called for '%s'\n",
         FileInformationClass >= FileMaximumInformation - 1 ? "????" : FileInformationClassNames[ FileInformationClass]);

  DPRINT("FileInformationClass %d\n", FileInformationClass);
  DPRINT("SystemBuffer %p\n", SystemBuffer);

  /* Special: We should call MmCanFileBeTruncated here to determine if changing
     the file size would be allowed.  If not, we bail with the right error.
     We must do this before acquiring the lock. */
  if (FileInformationClass == FileEndOfFileInformation)
  {
      DPRINT("Check for the ability to set file size\n");
      if (!MmCanFileBeTruncated
         (IrpContext->FileObject->SectionObjectPointer,
          (PLARGE_INTEGER)SystemBuffer))
      {
         DPRINT("Couldn't set file size!\n");
         IrpContext->Irp->IoStatus.Status = STATUS_USER_MAPPED_FILE;
         IrpContext->Irp->IoStatus.Information = 0;
         IoCompleteRequest(IrpContext->Irp, IO_NO_INCREMENT);
         VfatFreeIrpContext(IrpContext);
         return STATUS_USER_MAPPED_FILE;
      }
      DPRINT("Can set file size\n");
  }

  if (!(FCB->Flags & FCB_IS_PAGE_FILE))
    {
      if (!ExAcquireResourceExclusiveLite(&FCB->MainResource,
                                          (BOOLEAN)CanWait))
	{
	  return(VfatQueueRequest (IrpContext));
	}
    }

  switch (FileInformationClass)
    {
    case FilePositionInformation:
      RC = VfatSetPositionInformation(IrpContext->FileObject,
				      SystemBuffer);
      break;
    case FileDispositionInformation:
      RC = VfatSetDispositionInformation(IrpContext->FileObject,
					 FCB,
					 IrpContext->DeviceObject,
					 SystemBuffer);
      break;
    case FileAllocationInformation:
    case FileEndOfFileInformation:
      RC = VfatSetAllocationSizeInformation(IrpContext->FileObject,
					    FCB,
					    IrpContext->DeviceExt,
					    (PLARGE_INTEGER)SystemBuffer);
      break;
    case FileBasicInformation:
      RC = VfatSetBasicInformation(IrpContext->FileObject,
				   FCB,
				   IrpContext->DeviceExt,
				   SystemBuffer);
      break;
    case FileRenameInformation:
      RC = STATUS_NOT_IMPLEMENTED;
      break;
    default:
      RC = STATUS_NOT_SUPPORTED;
    }

  if (!(FCB->Flags & FCB_IS_PAGE_FILE))
  {
     ExReleaseResourceLite(&FCB->MainResource);
  }

  IrpContext->Irp->IoStatus.Status = RC;
  IrpContext->Irp->IoStatus.Information = 0;
  IoCompleteRequest(IrpContext->Irp, IO_NO_INCREMENT);
  VfatFreeIrpContext(IrpContext);

  return RC;
}

/* EOF */
