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

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

#include "vfat.h"

#define NDEBUG
#include <debug.h>

#define NASSERTS_RENAME

/* 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 ****************************************************************/

/*
 * FUNCTION: Retrieve the standard file information
 */
NTSTATUS
VfatGetStandardInformation(
    PVFATFCB FCB,
    PFILE_STANDARD_INFORMATION StandardInfo,
    PULONG BufferLength)
{
    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 = BooleanFlagOn(FCB->Flags, FCB_DELETE_PENDING);

    *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 %u\n", PositionInfo->CurrentByteOffset.u.LowPart);

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

    return STATUS_SUCCESS;
}

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

    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)
{
    ULONG NotifyFilter;

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

    NotifyFilter = 0;

    if (BasicInfo->FileAttributes != 0)
    {
        UCHAR Attributes;

        Attributes = (BasicInfo->FileAttributes & (FILE_ATTRIBUTE_ARCHIVE |
                                                   FILE_ATTRIBUTE_SYSTEM |
                                                   FILE_ATTRIBUTE_HIDDEN |
                                                   FILE_ATTRIBUTE_DIRECTORY |
                                                   FILE_ATTRIBUTE_READONLY));

        if (vfatFCBIsDirectory(FCB))
        {
            if (BooleanFlagOn(BasicInfo->FileAttributes, FILE_ATTRIBUTE_TEMPORARY))
            {
                DPRINT("Setting temporary attribute on a directory!\n");
                return STATUS_INVALID_PARAMETER;
            }

            Attributes |= FILE_ATTRIBUTE_DIRECTORY;
        }
        else
        {
            if (BooleanFlagOn(BasicInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY))
            {
                DPRINT("Setting directory attribute on a file!\n");
                return STATUS_INVALID_PARAMETER;
            }
        }

        if (Attributes != *FCB->Attributes)
        {
            *FCB->Attributes = Attributes;
            DPRINT("Setting attributes 0x%02x\n", *FCB->Attributes);
            NotifyFilter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
        }
    }

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

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

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

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

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

    VfatUpdateEntry(DeviceExt, FCB);

    if (NotifyFilter != 0)
    {
        vfatReportChange(DeviceExt,
                         FCB,
                         NotifyFilter,
                         FILE_ACTION_MODIFIED);
    }

    return STATUS_SUCCESS;
}

NTSTATUS
VfatGetBasicInformation(
    PFILE_OBJECT FileObject,
    PVFATFCB FCB,
    PDEVICE_EXTENSION DeviceExt,
    PFILE_BASIC_INFORMATION BasicInfo,
    PULONG BufferLength)
{
    UNREFERENCED_PARAMETER(FileObject);

    DPRINT("VfatGetBasicInformation()\n");

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

    if (vfatVolumeIsFatX(DeviceExt))
    {
        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_EXTENSION DeviceExt,
    PFILE_DISPOSITION_INFORMATION DispositionInfo)
{
    DPRINT("FsdSetDispositionInformation(<%wZ>, Delete %u)\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 (BooleanFlagOn(FCB->Flags, FCB_DELETE_PENDING))
    {
        /* stream already marked for deletion. just update the file object */
        FileObject->DeletePending = TRUE;
        return STATUS_SUCCESS;
    }

    if (vfatFCBIsReadOnly(FCB))
    {
        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(DeviceExt, 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
vfatPrepareTargetForRename(
    IN PDEVICE_EXTENSION DeviceExt,
    IN PVFATFCB * ParentFCB,
    IN PUNICODE_STRING NewName,
    IN BOOLEAN ReplaceIfExists,
    IN PUNICODE_STRING ParentName,
    OUT PBOOLEAN Deleted)
{
    NTSTATUS Status;
    PVFATFCB TargetFcb;

    DPRINT("vfatPrepareTargetForRename(%p, %p, %wZ, %d, %wZ, %p)\n", DeviceExt, ParentFCB, NewName, ReplaceIfExists, ParentName);

    *Deleted = FALSE;
    /* Try to open target */
    Status = vfatGetFCBForFile(DeviceExt, ParentFCB, &TargetFcb, NewName);
    /* If it exists */
    if (NT_SUCCESS(Status))
    {
        DPRINT("Target file %wZ exists. FCB Flags %08x\n", NewName, TargetFcb->Flags);
        /* Check whether we are allowed to replace */
        if (ReplaceIfExists)
        {
            /* If that's a directory or a read-only file, we're not allowed */
            if (vfatFCBIsDirectory(TargetFcb) || vfatFCBIsReadOnly(TargetFcb))
            {
                DPRINT("And this is a readonly file!\n");
                vfatReleaseFCB(DeviceExt, *ParentFCB);
                *ParentFCB = NULL;
                vfatReleaseFCB(DeviceExt, TargetFcb);
                return STATUS_OBJECT_NAME_COLLISION;
            }


            /* If we still have a file object, close it. */
            if (TargetFcb->FileObject)
            {
                if (!MmFlushImageSection(TargetFcb->FileObject->SectionObjectPointer, MmFlushForDelete))
                {
                    DPRINT("MmFlushImageSection failed.\n");
                    vfatReleaseFCB(DeviceExt, *ParentFCB);
                    *ParentFCB = NULL;
                    vfatReleaseFCB(DeviceExt, TargetFcb);
                    return STATUS_ACCESS_DENIED;
                }

                TargetFcb->FileObject->DeletePending = TRUE;
                VfatCloseFile(DeviceExt, TargetFcb->FileObject);
            }

            /* If we are here, ensure the file isn't open by anyone! */
            if (TargetFcb->OpenHandleCount != 0)
            {
                DPRINT("There are still open handles for this file.\n");
                vfatReleaseFCB(DeviceExt, *ParentFCB);
                *ParentFCB = NULL;
                vfatReleaseFCB(DeviceExt, TargetFcb);
                return STATUS_ACCESS_DENIED;
            }

            /* Effectively delete old file to allow renaming */
            DPRINT("Effectively deleting the file.\n");
            VfatDelEntry(DeviceExt, TargetFcb, NULL);
            vfatReleaseFCB(DeviceExt, TargetFcb);
            *Deleted = TRUE;
            return STATUS_SUCCESS;
        }
        else
        {
            vfatReleaseFCB(DeviceExt, *ParentFCB);
            *ParentFCB = NULL;
            vfatReleaseFCB(DeviceExt, TargetFcb);
            return STATUS_OBJECT_NAME_COLLISION;
        }
    }
    else if (*ParentFCB != NULL)
    {
        return STATUS_SUCCESS;
    }

    /* Failure */
    return Status;
}

static
BOOLEAN
IsThereAChildOpened(PVFATFCB FCB)
{
    PLIST_ENTRY Entry;
    PVFATFCB VolFCB;

    for (Entry = FCB->ParentListHead.Flink; Entry != &FCB->ParentListHead; Entry = Entry->Flink)
    {
        VolFCB = CONTAINING_RECORD(Entry, VFATFCB, ParentListEntry);
        if (VolFCB->OpenHandleCount != 0)
        {
            ASSERT(VolFCB->parentFcb == FCB);
            DPRINT1("At least one children file opened! %wZ (%u, %u)\n", &VolFCB->PathNameU, VolFCB->RefCount, VolFCB->OpenHandleCount);
            return TRUE;
        }

        if (vfatFCBIsDirectory(VolFCB) && !IsListEmpty(&VolFCB->ParentListHead))
        {
            if (IsThereAChildOpened(VolFCB))
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}

static
VOID
VfatRenameChildFCB(
    PDEVICE_EXTENSION DeviceExt,
    PVFATFCB FCB)
{
    PLIST_ENTRY Entry;
    PVFATFCB Child;

    if (IsListEmpty(&FCB->ParentListHead))
        return;

    for (Entry = FCB->ParentListHead.Flink; Entry != &FCB->ParentListHead; Entry = Entry->Flink)
    {
        NTSTATUS Status;

        Child = CONTAINING_RECORD(Entry, VFATFCB, ParentListEntry);
        DPRINT("Found %wZ with still %lu references (parent: %lu)!\n", &Child->PathNameU, Child->RefCount, FCB->RefCount);

        Status = vfatSetFCBNewDirName(DeviceExt, Child, FCB);
        if (!NT_SUCCESS(Status))
            continue;

        if (vfatFCBIsDirectory(Child))
        {
            VfatRenameChildFCB(DeviceExt, Child);
        }
    }
}

/*
 * FUNCTION: Set the file name information
 */
static
NTSTATUS
VfatSetRenameInformation(
    PFILE_OBJECT FileObject,
    PVFATFCB FCB,
    PDEVICE_EXTENSION DeviceExt,
    PFILE_RENAME_INFORMATION RenameInfo,
    PFILE_OBJECT TargetFileObject)
{
#ifdef NASSERTS_RENAME
#pragma push_macro("ASSERT")
#undef ASSERT
#define ASSERT(x) ((VOID) 0)
#endif
    NTSTATUS Status;
    UNICODE_STRING NewName;
    UNICODE_STRING SourcePath;
    UNICODE_STRING SourceFile;
    UNICODE_STRING NewPath;
    UNICODE_STRING NewFile;
    PFILE_OBJECT RootFileObject;
    PVFATFCB RootFCB;
    UNICODE_STRING RenameInfoString;
    PVFATFCB ParentFCB;
    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE TargetHandle;
    BOOLEAN DeletedTarget;
    ULONG OldReferences, NewReferences;
    PVFATFCB OldParent;

    DPRINT("VfatSetRenameInfo(%p, %p, %p, %p, %p)\n", FileObject, FCB, DeviceExt, RenameInfo, TargetFileObject);

    /* Disallow renaming root */
    if (vfatFCBIsRoot(FCB))
    {
        return STATUS_INVALID_PARAMETER;
    }

    OldReferences = FCB->parentFcb->RefCount;
#ifdef NASSERTS_RENAME
    UNREFERENCED_PARAMETER(OldReferences);
#endif

    /* If we are performing relative opening for rename, get FO for getting FCB and path name */
    if (RenameInfo->RootDirectory != NULL)
    {
        /* We cannot tolerate relative opening with a full path */
        if (RenameInfo->FileName[0] == L'\\')
        {
            return STATUS_OBJECT_NAME_INVALID;
        }

        Status = ObReferenceObjectByHandle(RenameInfo->RootDirectory,
                                           FILE_READ_DATA,
                                           *IoFileObjectType,
                                           ExGetPreviousMode(),
                                           (PVOID *)&RootFileObject,
                                           NULL);
        if (!NT_SUCCESS(Status))
        {
            return Status;
        }

        RootFCB = RootFileObject->FsContext;
    }

    RtlInitEmptyUnicodeString(&NewName, NULL, 0);
    ParentFCB = NULL;

    if (TargetFileObject == NULL)
    {
        /* If we don't have target file object, construct paths thanks to relative FCB, if any, and with
         * information supplied by the user
         */

        /* First, setup a string we'll work on */
        RenameInfoString.Length = RenameInfo->FileNameLength;
        RenameInfoString.MaximumLength = RenameInfo->FileNameLength;
        RenameInfoString.Buffer = RenameInfo->FileName;

        /* Check whether we have FQN */
        if (RenameInfoString.Length > 6 * sizeof(WCHAR))
        {
            if (RenameInfoString.Buffer[0] == L'\\' && RenameInfoString.Buffer[1] == L'?' &&
                RenameInfoString.Buffer[2] == L'?' && RenameInfoString.Buffer[3] == L'\\' &&
                RenameInfoString.Buffer[5] == L':' && (RenameInfoString.Buffer[4] >= L'A' &&
                RenameInfoString.Buffer[4] <= L'Z'))
            {
                /* If so, open its target directory */
                InitializeObjectAttributes(&ObjectAttributes,
                                           &RenameInfoString,
                                           OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                           NULL, NULL);

                Status = IoCreateFile(&TargetHandle,
                                      FILE_WRITE_DATA | SYNCHRONIZE,
                                      &ObjectAttributes,
                                      &IoStatusBlock,
                                      NULL, 0,
                                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                                      FILE_OPEN,
                                      FILE_OPEN_FOR_BACKUP_INTENT,
                                      NULL, 0,
                                      CreateFileTypeNone,
                                      NULL,
                                      IO_FORCE_ACCESS_CHECK | IO_OPEN_TARGET_DIRECTORY);
                if (!NT_SUCCESS(Status))
                {
                    goto Cleanup;
                }

                /* Get its FO to get the FCB */
                Status = ObReferenceObjectByHandle(TargetHandle,
                                                   FILE_WRITE_DATA,
                                                   *IoFileObjectType,
                                                   KernelMode,
                                                   (PVOID *)&TargetFileObject,
                                                   NULL);
                if (!NT_SUCCESS(Status))
                {
                    ZwClose(TargetHandle);
                    goto Cleanup;
                }

                /* Are we working on the same volume? */
                if (IoGetRelatedDeviceObject(TargetFileObject) != IoGetRelatedDeviceObject(FileObject))
                {
                    ObDereferenceObject(TargetFileObject);
                    ZwClose(TargetHandle);
                    TargetFileObject = NULL;
                    Status = STATUS_NOT_SAME_DEVICE;
                    goto Cleanup;
                }
            }
        }

        NewName.Length = 0;
        NewName.MaximumLength = RenameInfo->FileNameLength;
        if (RenameInfo->RootDirectory != NULL)
        {
            NewName.MaximumLength += sizeof(WCHAR) + RootFCB->PathNameU.Length;
        }
        else if (RenameInfo->FileName[0] != L'\\')
        {
            /* We don't have full path, and we don't have root directory:
             * => we move inside the same directory
             */
            NewName.MaximumLength += sizeof(WCHAR) + FCB->DirNameU.Length;
        }
        else if (TargetFileObject != NULL)
        {
            /* We had a FQN:
             * => we need to use its correct path
             */
            NewName.MaximumLength += sizeof(WCHAR) + ((PVFATFCB)TargetFileObject->FsContext)->PathNameU.Length;
        }

        NewName.Buffer = ExAllocatePoolWithTag(NonPagedPool, NewName.MaximumLength, TAG_NAME);
        if (NewName.Buffer == NULL)
        {
            if (TargetFileObject != NULL)
            {
                ObDereferenceObject(TargetFileObject);
                ZwClose(TargetHandle);
                TargetFileObject = NULL;
            }
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto Cleanup;
        }

        if (RenameInfo->RootDirectory != NULL)
        {
            /* Here, copy first absolute and then append relative */ 
            RtlCopyUnicodeString(&NewName, &RootFCB->PathNameU);
            NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
            NewName.Length += sizeof(WCHAR);
            RtlAppendUnicodeStringToString(&NewName, &RenameInfoString);
        }
        else if (RenameInfo->FileName[0] != L'\\')
        {
            /* Here, copy first work directory and then append filename */
            RtlCopyUnicodeString(&NewName, &FCB->DirNameU);
            NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
            NewName.Length += sizeof(WCHAR);
            RtlAppendUnicodeStringToString(&NewName, &RenameInfoString);
        }
        else if (TargetFileObject != NULL)
        {
            /* Here, copy first path name and then append filename */
            RtlCopyUnicodeString(&NewName, &((PVFATFCB)TargetFileObject->FsContext)->PathNameU);
            NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
            NewName.Length += sizeof(WCHAR);
            RtlAppendUnicodeStringToString(&NewName, &RenameInfoString);
        }
        else
        {
            /* Here we should have full path, so simply copy it */
            RtlCopyUnicodeString(&NewName, &RenameInfoString);
        }

        /* Do we have to cleanup some stuff? */
        if (TargetFileObject != NULL)
        {
            ObDereferenceObject(TargetFileObject);
            ZwClose(TargetHandle);
            TargetFileObject = NULL;
        }
    }
    else
    {
        /* At that point, we shouldn't care about whether we are relative opening
         * Target FO FCB should already have full path
         */

        /* Before constructing string, just make a sanity check (just to be sure!) */
        if (IoGetRelatedDeviceObject(TargetFileObject) != IoGetRelatedDeviceObject(FileObject))
        {
            Status = STATUS_NOT_SAME_DEVICE;
            goto Cleanup;
        }

        NewName.Length = 0;
        NewName.MaximumLength = TargetFileObject->FileName.Length + ((PVFATFCB)TargetFileObject->FsContext)->PathNameU.Length + sizeof(WCHAR);
        NewName.Buffer = ExAllocatePoolWithTag(NonPagedPool, NewName.MaximumLength, TAG_NAME);
        if (NewName.Buffer == NULL)
        {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto Cleanup;
        }

        RtlCopyUnicodeString(&NewName, &((PVFATFCB)TargetFileObject->FsContext)->PathNameU);
        /* If \, it's already backslash terminated, don't add it */
        if (!vfatFCBIsRoot(TargetFileObject->FsContext))
        {
            NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
            NewName.Length += sizeof(WCHAR);
        }
        RtlAppendUnicodeStringToString(&NewName, &TargetFileObject->FileName);
    }

    /* Explode our paths to get path & filename */
    vfatSplitPathName(&FCB->PathNameU, &SourcePath, &SourceFile);
    DPRINT("Old dir: %wZ, Old file: %wZ\n", &SourcePath, &SourceFile);
    vfatSplitPathName(&NewName, &NewPath, &NewFile);
    DPRINT("New dir: %wZ, New file: %wZ\n", &NewPath, &NewFile);

    if (vfatFCBIsDirectory(FCB) && !IsListEmpty(&FCB->ParentListHead))
    {
        if (IsThereAChildOpened(FCB))
        {
            Status = STATUS_ACCESS_DENIED;
            ASSERT(OldReferences == FCB->parentFcb->RefCount);
            goto Cleanup;
        }
    }

    /* Are we working in place? */
    if (FsRtlAreNamesEqual(&SourcePath, &NewPath, TRUE, NULL))
    {
        if (FsRtlAreNamesEqual(&SourceFile, &NewFile, FALSE, NULL))
        {
            Status = STATUS_SUCCESS;
            ASSERT(OldReferences == FCB->parentFcb->RefCount);
            goto Cleanup;
        }

        if (FsRtlAreNamesEqual(&SourceFile, &NewFile, TRUE, NULL))
        {
            vfatReportChange(DeviceExt,
                             FCB,
                             (vfatFCBIsDirectory(FCB) ?
                              FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
                             FILE_ACTION_RENAMED_OLD_NAME);
            Status = vfatRenameEntry(DeviceExt, FCB, &NewFile, TRUE);
            if (NT_SUCCESS(Status))
            {
                vfatReportChange(DeviceExt,
                                 FCB,
                                 (vfatFCBIsDirectory(FCB) ?
                                  FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
                                 FILE_ACTION_RENAMED_NEW_NAME);
            }
        }
        else
        {
            /* Try to find target */
            ParentFCB = FCB->parentFcb;
            vfatGrabFCB(DeviceExt, ParentFCB);
            Status = vfatPrepareTargetForRename(DeviceExt,
                                                &ParentFCB,
                                                &NewFile,
                                                RenameInfo->ReplaceIfExists,
                                                &NewPath,
                                                &DeletedTarget);
            if (!NT_SUCCESS(Status))
            {
                ASSERT(OldReferences == FCB->parentFcb->RefCount - 1);
                ASSERT(OldReferences == ParentFCB->RefCount - 1);
                goto Cleanup;
            }

            vfatReportChange(DeviceExt,
                             FCB,
                             (vfatFCBIsDirectory(FCB) ?
                              FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
                             (DeletedTarget ? FILE_ACTION_REMOVED : FILE_ACTION_RENAMED_OLD_NAME));
            Status = vfatRenameEntry(DeviceExt, FCB, &NewFile, FALSE);
            if (NT_SUCCESS(Status))
            {
                if (DeletedTarget)
                {
                    vfatReportChange(DeviceExt,
                                     FCB,
                                     FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE
                                     | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_EA,
                                     FILE_ACTION_MODIFIED);
                }
                else
                {
                    vfatReportChange(DeviceExt,
                                     FCB,
                                     (vfatFCBIsDirectory(FCB) ?
                                      FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
                                     FILE_ACTION_RENAMED_NEW_NAME);
                }
            }
        }

        ASSERT(OldReferences == FCB->parentFcb->RefCount - 1); // extra grab
        ASSERT(OldReferences == ParentFCB->RefCount - 1); // extra grab
    }
    else
    {

        /* Try to find target */
        ParentFCB = NULL;
        OldParent = FCB->parentFcb;
#ifdef NASSERTS_RENAME
        UNREFERENCED_PARAMETER(OldParent);
#endif
        Status = vfatPrepareTargetForRename(DeviceExt,
                                            &ParentFCB,
                                            &NewName,
                                            RenameInfo->ReplaceIfExists,
                                            &NewPath,
                                            &DeletedTarget);
        if (!NT_SUCCESS(Status))
        {
            ASSERT(OldReferences == FCB->parentFcb->RefCount);
            goto Cleanup;
        }

        NewReferences = ParentFCB->RefCount;
#ifdef NASSERTS_RENAME
        UNREFERENCED_PARAMETER(NewReferences);
#endif

        vfatReportChange(DeviceExt,
                         FCB,
                         (vfatFCBIsDirectory(FCB) ?
                          FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
                         FILE_ACTION_REMOVED);
        Status = VfatMoveEntry(DeviceExt, FCB, &NewFile, ParentFCB);
        if (NT_SUCCESS(Status))
        {
            if (DeletedTarget)
            {
                vfatReportChange(DeviceExt,
                                 FCB,
                                 FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE
                                 | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_EA,
                                 FILE_ACTION_MODIFIED);
            }
            else
            {
                vfatReportChange(DeviceExt,
                                 FCB,
                                 (vfatFCBIsDirectory(FCB) ?
                                  FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
                                 FILE_ACTION_ADDED);
            }
        }
    }

    if (NT_SUCCESS(Status) && vfatFCBIsDirectory(FCB))
    {
        VfatRenameChildFCB(DeviceExt, FCB);
    }

    ASSERT(OldReferences == OldParent->RefCount + 1); // removed file
    ASSERT(NewReferences == ParentFCB->RefCount - 1); // new file
Cleanup:
    if (ParentFCB != NULL) vfatReleaseFCB(DeviceExt, ParentFCB);
    if (NewName.Buffer != NULL) ExFreePoolWithTag(NewName.Buffer, TAG_NAME);
    if (RenameInfo->RootDirectory != NULL) ObDereferenceObject(RootFileObject);

    return Status;
#ifdef NASSERTS_RENAME
#pragma pop_macro("ASSERT")
#endif
}

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

    UNREFERENCED_PARAMETER(FileObject);
    UNREFERENCED_PARAMETER(DeviceExt);

    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,
    PDEVICE_EXTENSION DeviceExt,
    PFILE_INTERNAL_INFORMATION InternalInfo,
    PULONG BufferLength)
{
    ASSERT(InternalInfo);
    ASSERT(Fcb);

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

    InternalInfo->IndexNumber.QuadPart = (LONGLONG)vfatDirEntryGetFirstCluster(DeviceExt, &Fcb->entry) * DeviceExt->FatInfo.BytesPerCluster;

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


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

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

    if (vfatVolumeIsFatX(DeviceExt))
    {
        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_EXTENSION DeviceExt,
    PFILE_EA_INFORMATION Info,
    PULONG BufferLength)
{
    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;
}


/*
 * FUNCTION: Retrieve the all file information
 */
static
NTSTATUS
VfatGetAllInformation(
    PFILE_OBJECT FileObject,
    PVFATFCB Fcb,
    PDEVICE_EXTENSION DeviceExt,
    PFILE_ALL_INFORMATION Info,
    PULONG BufferLength)
{
    NTSTATUS Status;

    ASSERT(Info);
    ASSERT(Fcb);

    if (*BufferLength < FIELD_OFFSET(FILE_ALL_INFORMATION, NameInformation.FileName))
        return STATUS_BUFFER_OVERFLOW;

    *BufferLength -= (sizeof(FILE_ACCESS_INFORMATION) + sizeof(FILE_MODE_INFORMATION) + sizeof(FILE_ALIGNMENT_INFORMATION));

    /* Basic Information */
    Status = VfatGetBasicInformation(FileObject, Fcb, DeviceExt, &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, DeviceExt, &Info->InternalInformation, BufferLength);
    if (!NT_SUCCESS(Status)) return Status;
    /* EA Information */
    Status = VfatGetEaInformation(FileObject, Fcb, DeviceExt, &Info->EaInformation, BufferLength);
    if (!NT_SUCCESS(Status)) return Status;
    /* Position Information */
    Status = VfatGetPositionInformation(FileObject, Fcb, DeviceExt, &Info->PositionInformation, BufferLength);
    if (!NT_SUCCESS(Status)) return Status;
    /* Name Information */
    Status = VfatGetNameInformation(FileObject, Fcb, DeviceExt, &Info->NameInformation, BufferLength);

    return Status;
}

static
VOID
UpdateFileSize(
    PFILE_OBJECT FileObject,
    PVFATFCB Fcb,
    ULONG Size,
    ULONG ClusterSize,
    BOOLEAN IsFatX)
{
    if (Size > 0)
    {
        Fcb->RFCB.AllocationSize.QuadPart = ROUND_UP_64(Size, ClusterSize);
    }
    else
    {
        Fcb->RFCB.AllocationSize.QuadPart = (LONGLONG)0;
    }
    if (!vfatFCBIsDirectory(Fcb))
    {
        if (IsFatX)
            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, IsFatX = vfatVolumeIsFatX(DeviceExt);

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

    if (IsFatX)
        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 (IsFatX)
            {
                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, vfatVolumeIsFatX(DeviceExt));
    }
    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, vfatVolumeIsFatX(DeviceExt));
        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 (IsFatX)
            {
                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;
        }

        if (DeviceExt->FatInfo.FatType == FAT32)
        {
            FAT32UpdateFreeClustersCount(DeviceExt);
        }
    }
    else
    {
        UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize, vfatVolumeIsFatX(DeviceExt));
    }

    /* Update the on-disk directory entry */
    Fcb->Flags |= FCB_IS_DIRTY;
    if (AllocSizeChanged)
    {
        VfatUpdateEntry(DeviceExt, Fcb);

        vfatReportChange(DeviceExt, Fcb, FILE_NOTIFY_CHANGE_SIZE, FILE_ACTION_MODIFIED);
    }
    return STATUS_SUCCESS;
}

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

    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]);

    if (FCB == NULL)
    {
        DPRINT1("IRP_MJ_QUERY_INFORMATION without FCB!\n");
        IrpContext->Irp->IoStatus.Information = 0;
        return STATUS_INVALID_PARAMETER;
    }

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

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

    switch (FileInformationClass)
    {
        case FileStandardInformation:
            Status = VfatGetStandardInformation(FCB,
                                                SystemBuffer,
                                                &BufferLength);
            break;

        case FilePositionInformation:
            Status = VfatGetPositionInformation(IrpContext->FileObject,
                                                FCB,
                                                IrpContext->DeviceExt,
                                                SystemBuffer,
                                                &BufferLength);
            break;

        case FileBasicInformation:
            Status = VfatGetBasicInformation(IrpContext->FileObject,
                                             FCB,
                                             IrpContext->DeviceExt,
                                             SystemBuffer,
                                             &BufferLength);
            break;

        case FileNameInformation:
            Status = VfatGetNameInformation(IrpContext->FileObject,
                                            FCB,
                                            IrpContext->DeviceExt,
                                            SystemBuffer,
                                            &BufferLength);
            break;

        case FileInternalInformation:
            Status = VfatGetInternalInformation(FCB,
                                                IrpContext->DeviceExt,
                                                SystemBuffer,
                                                &BufferLength);
            break;

        case FileNetworkOpenInformation:
            Status = VfatGetNetworkOpenInformation(FCB,
                                                   IrpContext->DeviceExt,
                                                   SystemBuffer,
                                                   &BufferLength);
            break;

        case FileAllInformation:
            Status = VfatGetAllInformation(IrpContext->FileObject,
                                           FCB,
                                           IrpContext->DeviceExt,
                                           SystemBuffer,
                                           &BufferLength);
            break;

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

        case FileAlternateNameInformation:
            Status = STATUS_NOT_IMPLEMENTED;
            break;

        default:
            Status = STATUS_INVALID_PARAMETER;
    }

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

    if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW)
        IrpContext->Irp->IoStatus.Information =
            IrpContext->Stack->Parameters.QueryFile.Length - BufferLength;
    else
        IrpContext->Irp->IoStatus.Information = 0;

    return Status;
}

/*
 * FUNCTION: Retrieve the specified file information
 */
NTSTATUS
VfatSetInformation(
    PVFAT_IRP_CONTEXT IrpContext)
{
    FILE_INFORMATION_CLASS FileInformationClass;
    PVFATFCB FCB;
    NTSTATUS Status = STATUS_SUCCESS;
    PVOID SystemBuffer;
    BOOLEAN LockDir;

    /* 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);

    if (FCB == NULL)
    {
        DPRINT1("IRP_MJ_SET_INFORMATION without FCB!\n");
        IrpContext->Irp->IoStatus.Information = 0;
        return STATUS_INVALID_PARAMETER;
    }

    /* 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.Information = 0;
            return STATUS_USER_MAPPED_FILE;
        }
        DPRINT("Can set file size\n");
    }

    LockDir = FALSE;
    if (FileInformationClass == FileRenameInformation || FileInformationClass == FileAllocationInformation ||
        FileInformationClass == FileEndOfFileInformation || FileInformationClass == FileBasicInformation)
    {
        LockDir = TRUE;
    }

    if (LockDir)
    {
        if (!ExAcquireResourceExclusiveLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource,
                                            BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
        {
            return VfatMarkIrpContextForQueue(IrpContext);
        }
    }

    if (!BooleanFlagOn(FCB->Flags, FCB_IS_PAGE_FILE))
    {
        if (!ExAcquireResourceExclusiveLite(&FCB->MainResource,
                                            BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
        {
            if (LockDir)
            {
                ExReleaseResourceLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource);
            }

            return VfatMarkIrpContextForQueue(IrpContext);
        }
    }

    switch (FileInformationClass)
    {
        case FilePositionInformation:
            Status = VfatSetPositionInformation(IrpContext->FileObject,
                                                SystemBuffer);
            break;

        case FileDispositionInformation:
            Status = VfatSetDispositionInformation(IrpContext->FileObject,
                                                   FCB,
                                                   IrpContext->DeviceExt,
                                                   SystemBuffer);
            break;

        case FileAllocationInformation:
        case FileEndOfFileInformation:
            Status = VfatSetAllocationSizeInformation(IrpContext->FileObject,
                                                      FCB,
                                                      IrpContext->DeviceExt,
                                                      (PLARGE_INTEGER)SystemBuffer);
            break;

        case FileBasicInformation:
            Status = VfatSetBasicInformation(IrpContext->FileObject,
                                             FCB,
                                             IrpContext->DeviceExt,
                                             SystemBuffer);
            break;

        case FileRenameInformation:
            Status = VfatSetRenameInformation(IrpContext->FileObject,
                                              FCB,
                                              IrpContext->DeviceExt,
                                              SystemBuffer,
                                              IrpContext->Stack->Parameters.SetFile.FileObject);
            break;

        default:
            Status = STATUS_NOT_SUPPORTED;
    }

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

    if (LockDir)
    {
        ExReleaseResourceLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource);
    }

    IrpContext->Irp->IoStatus.Information = 0;
    return Status;
}

/* EOF */