/*++

Copyright (c) 2000-2001  Microsoft Corporation

Module Name:

    dirctrl.c

Abstract:

    This module implements routines related to handling
    IRP_MJ_DIRECTORY_CONTROL.

--*/

#include "fatx.h"

NTSTATUS
FatxFindNextDirectoryEntry(
    IN PFAT_VOLUME_EXTENSION VolumeExtension,
    IN PIRP Irp,
    IN PFAT_FCB DirectoryFcb,
    IN ULONG DirectoryByteOffset,
    IN POBJECT_STRING TemplateFileName,
    OUT PDIRENT ReturnedDirectoryEntry,
    OUT PULONG ReturnedDirectoryByteOffset
    )
/*++

Routine Description:

    This routine finds the next directory entry that matches the query template
    specification.

Arguments:

    VolumeExtension - Specifies the extension that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

    DirectoryFcb - Specifies the file control block that describes where to
        read the directory stream from.

    DirectoryByteOffset - Specifies the directory byte offset to start scanning
        from.

    TemplateFileName - Specifies the optional file name to match.

    ReturnedDirectoryEntry - Specifies the buffer to receive the directory entry
        for the file if found.

    ReturnedDirectoryByteOffset - Specifies the buffer to receive the byte
        offset of the entry in the directory stream.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PVOID CacheBuffer;
    ULONG ClusterNumber;
    LONG FileNameLengthLimit;
    ULONGLONG PhysicalByteOffset;
    ULONG ClusterBytesRemaining;
    ULONG ClusterByteOffset;
    ULONG PageBytesRemaining;
    PDIRENT DirectoryEntry;
    PDIRENT EndingDirectoryEntry;
    OBJECT_STRING DirectoryEntryFileName;

    ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());

    CacheBuffer = NULL;

    //
    // If we're in the first cluster, then we can skip accessing the cluster
    // cache and go directly to the first cluster.  Otherwise, we need to go
    // through the cluster cache to map the current byte offset to the cluster
    // number.
    //

    if (DirectoryByteOffset < VolumeExtension->BytesPerCluster) {

        //
        // Get the first cluster of the directory stream and validate it.
        //

        ClusterNumber = DirectoryFcb->FirstCluster;

        if (!FatxIsValidCluster(VolumeExtension, ClusterNumber)) {
            FatxDbgPrint(("FATX: invalid starting cluster for directory\n"));
            status = STATUS_FILE_CORRUPT_ERROR;
            goto CleanupAndExit;
        }

    } else {

        //
        // Get the cluster number corresponding to the current byte offset.
        //

        status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, DirectoryFcb,
            DirectoryByteOffset, &ClusterNumber, NULL);

        if (!NT_SUCCESS(status)) {
            goto CleanupAndExit;
        }
    }

    //
    // Compute the longest file name that we'll allow to be enumerated.  If the
    // directory's path name length is near FAT_PATH_NAME_LIMIT, then we don't
    // want to return names that would cause us to overflow that limit.
    //

    FileNameLengthLimit = FAT_PATH_NAME_LIMIT - (DirectoryFcb->PathNameLength +
        sizeof(OBJ_NAME_PATH_SEPARATOR));

    if (FileNameLengthLimit > FAT_FILE_NAME_LENGTH) {
        FileNameLengthLimit = FAT_FILE_NAME_LENGTH;
    } else if (FileNameLengthLimit <= 0) {
        status = STATUS_END_OF_FILE;
        goto CleanupAndExit;
    }

    //
    // Process the directory stream.
    //

    for (;;) {

        //
        // Compute the physical byte offset of the next cluster and the number
        // of bytes remaining in this cluster.
        //

        PhysicalByteOffset = FatxClusterToPhysicalByteOffset(VolumeExtension,
            ClusterNumber);
        ClusterBytesRemaining = VolumeExtension->BytesPerCluster;

        //
        // If the directory byte offset is not cluster aligned, then bump up the
        // physical byte offset and reduce the number of bytes remaining by the
        // number of misaligned bytes.
        //

        ClusterByteOffset = FatxByteOffsetIntoCluster(VolumeExtension,
            DirectoryByteOffset);
        PhysicalByteOffset += ClusterByteOffset;
        ClusterBytesRemaining -= ClusterByteOffset;

        do {

            //
            // Map in the next page of the cluster.
            //

            status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp,
                PhysicalByteOffset, FALSE, &CacheBuffer);

            if (!NT_SUCCESS(status)) {
                goto CleanupAndExit;
            }

            //
            // Compute the number of bytes remaining on this cache buffer.
            //

            PageBytesRemaining = PAGE_SIZE - BYTE_OFFSET(PhysicalByteOffset);

            //
            // Adjust the number of bytes remaining in this cluster and the next
            // cache byte offset depending on whether we're near the end of the
            // cluster or not.
            //

            if (PageBytesRemaining < ClusterBytesRemaining) {
                ClusterBytesRemaining -= PageBytesRemaining;
                PhysicalByteOffset += PageBytesRemaining;
            } else {
                PageBytesRemaining = ClusterBytesRemaining;
                ClusterBytesRemaining = 0;
            }

            //
            // Process the directory entries on this cache buffer.
            //

            DirectoryEntry = (PDIRENT)CacheBuffer;
            EndingDirectoryEntry = (PDIRENT)((PUCHAR)DirectoryEntry +
                PageBytesRemaining);

            do {

                //
                // If we find a directory entry with a file name starting with
                // FAT_DIRENT_NEVER_USED or FAT_DIRENT_NEVER_USED2, then we're
                // at the end of the directory stream.
                //

                if ((DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED) ||
                    (DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED2)) {
                    status = STATUS_END_OF_FILE;
                    goto CleanupAndExit;
                }

                //
                // Check if the file name matches the name we're looking for.
                //
                // Make sure that the file name is legal so that we don't return
                // names to the caller that FatxFsdCreate will fail to locate.
                //

                DirectoryEntryFileName.Length = DirectoryEntry->FileNameLength;
                DirectoryEntryFileName.Buffer = DirectoryEntry->FileName;

                if ((DirectoryEntry->FileNameLength <= (ULONG)FileNameLengthLimit) &&
                    FatxIsValidFileAttributes(DirectoryEntry->FileAttributes) &&
                    FatxIsValidFatFileName(&DirectoryEntryFileName) &&
                    ((TemplateFileName->Buffer == NULL) ||
                    IoIsNameInExpression(TemplateFileName, &DirectoryEntryFileName))) {

                    //
                    // Copy the directory entry and its file byte offset back
                    // into the caller's buffers.
                    //

                    *ReturnedDirectoryEntry = *DirectoryEntry;
                    *ReturnedDirectoryByteOffset = DirectoryByteOffset;

                    status = STATUS_SUCCESS;
                    goto CleanupAndExit;
                }

                //
                // Advance to the next directory entry.
                //

                DirectoryEntry++;
                DirectoryByteOffset += sizeof(DIRENT);

            } while (DirectoryEntry < EndingDirectoryEntry);

            //
            // Unmap this cache buffer.
            //

            FscUnmapBuffer(CacheBuffer);
            CacheBuffer = NULL;

        } while (ClusterBytesRemaining > 0);

        //
        // Advance to the next cluster.
        //

        status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, DirectoryFcb,
            DirectoryByteOffset, &ClusterNumber, NULL);

        if (!NT_SUCCESS(status)) {
            goto CleanupAndExit;
        }

        //
        // Don't allow a directory to exceed the maximum allowed directory size.
        // We check for this case after the call to FatxFileByteOffsetToCluster
        // so that a directory that's exactly the maximum allowed directory size
        // will properly fill its allocation size and ending cluster number for
        // the sake of code further downstream.
        //

        if (NT_SUCCESS(status) &&
            (DirectoryByteOffset >= FAT_MAXIMUM_DIRECTORY_FILE_SIZE)) {
            status = STATUS_FILE_CORRUPT_ERROR;
            goto CleanupAndExit;
        }
    }

CleanupAndExit:
    if (CacheBuffer != NULL) {
        FscUnmapBuffer(CacheBuffer);
    }

    return status;
}

NTSTATUS
FatxFsdDirectoryControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is called by the I/O manager to handle IRP_MJ_DIRECTORY_CONTROL
    requests.

Arguments:

    DeviceObject - Specifies the device object that the I/O request is for.

    Irp - Specifies the packet that describes the I/O request.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PIO_STACK_LOCATION IrpSp;
    PFILE_OBJECT FileObject;
    PFAT_FCB DirectoryFcb;
    PDIRECTORY_ENUM_CONTEXT DirectoryEnumContext;
    POBJECT_STRING TemplateFileName;
    ULONG FileInformationClass;
    SIZE_T FileInformationBaseLength;
    BOOLEAN InitialQuery;
    DIRENT DirectoryEntry;
    ULONG DirectoryByteOffset;
    PFILE_DIRECTORY_INFORMATION DirectoryInformation;
    ULONG FileNameBytesToCopy;
    ULONG OutputBytesRemaining;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
    IrpSp = IoGetCurrentIrpStackLocation(Irp);
    FileObject = IrpSp->FileObject;
    DirectoryFcb = (PFAT_FCB)FileObject->FsContext;

    //
    // Synchronize the creation and access of the directory context control
    // block by acquiring the exclusive mutex for the volume.
    //

    FatxAcquireVolumeMutexExclusive(VolumeExtension);

    //
    // Check if the volume has been dismounted.
    //

    if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
        status = STATUS_VOLUME_DISMOUNTED;
        goto CleanupAndExit;
    }

    //
    // Ensure that the file object is for a directory.
    //

    if (FatxIsFlagClear(DirectoryFcb->Flags, FAT_FCB_DIRECTORY)) {
        status = STATUS_INVALID_PARAMETER;
        goto CleanupAndExit;
    }

    //
    // If the directory is marked for deletion, then the directory won't contain
    // any files and it won't allow any files to be created, so bail out now.
    // This also catches code that uses a directory file object after its handle
    // has been closed and the directory has been deleted, such as for symbolic
    // link objects.
    //

    if (FatxIsFlagSet(DirectoryFcb->Flags, FAT_FCB_DELETE_ON_CLOSE)) {
        status = STATUS_DELETE_PENDING;
        goto CleanupAndExit;
    }

    //
    // Verify that this is a supported information class.
    //

    FileInformationClass = IrpSp->Parameters.QueryDirectory.FileInformationClass;

    switch (FileInformationClass) {

        case FileDirectoryInformation:
            FileInformationBaseLength = FIELD_OFFSET(FILE_DIRECTORY_INFORMATION,
                FileName[0]);
            break;

        case FileNamesInformation:
            FileInformationBaseLength = FIELD_OFFSET(FILE_NAMES_INFORMATION,
                FileName[0]);
            break;

        default:
            status = STATUS_INVALID_INFO_CLASS;
            goto CleanupAndExit;
    }

    //
    // The query cannot be started relative to a starting index.
    //

    if (FatxIsFlagSet(IrpSp->Flags, SL_INDEX_SPECIFIED)) {
        status = STATUS_NOT_IMPLEMENTED;
        goto CleanupAndExit;
    }

    //
    // If this is the first query for this directory, then allocate a directory
    // context control block and initialize it.
    //

    DirectoryEnumContext = (PDIRECTORY_ENUM_CONTEXT)FileObject->FsContext2;

    if (DirectoryEnumContext == NULL) {

        TemplateFileName = IrpSp->Parameters.QueryDirectory.FileName;

        status = IoCreateDirectoryEnumContext(TemplateFileName,
            &DirectoryEnumContext);

        if (!NT_SUCCESS(status)) {
            goto CleanupAndExit;
        }

        //
        // Connect the directory enumeration context to the file object.
        //

        FileObject->FsContext2 = DirectoryEnumContext;

        InitialQuery = TRUE;

    } else {

        InitialQuery = FALSE;
    }

    //
    // If we're to restart the directory scan, then reset the current index to
    // zero.
    //

    if (FatxIsFlagSet(IrpSp->Flags, SL_RESTART_SCAN)) {
        DirectoryEnumContext->QueryOffset = 0;
    }

    //
    // Find the next directory entry that matches our query criteria.
    //
    // On return, DirectoryEnumContext->QueryOffset still points at the original
    // identifier.  It's only updated after we're about to successfully return
    // so that no entries are lost in the event of an invalid parameter or pool
    // allocation failure.
    //

    status = FatxFindNextDirectoryEntry(VolumeExtension, Irp, DirectoryFcb,
        DirectoryEnumContext->QueryOffset,
        &DirectoryEnumContext->TemplateFileName, &DirectoryEntry,
        &DirectoryByteOffset);

    if (NT_SUCCESS(status)) {

        //
        // The I/O manager has already checked that the user's buffer has enough
        // space to contain at least the header.
        //

        ASSERT(IrpSp->Parameters.QueryDirectory.Length >= FileInformationBaseLength);

        //
        // Zero out the header.
        //

        DirectoryInformation = (PFILE_DIRECTORY_INFORMATION)Irp->UserBuffer;
        RtlZeroMemory(DirectoryInformation, FileInformationBaseLength);

        //
        // For FileDirectoryInformation and FileNamesInformation, the
        // FileNameLength field is immediately before the FileName buffer.
        //

        *((PULONG)((PUCHAR)DirectoryInformation + FileInformationBaseLength -
            sizeof(ULONG))) = DirectoryEntry.FileNameLength;

        //
        // If this is a FileDirectoryInformation request, then fill in more
        // information.  We have to go to dig into the file entry descriptor
        // to get the information we need, so we'll construct a file control
        // block to get the attributes.
        //

        if (FileInformationClass == FileDirectoryInformation) {

            if (FatxIsFlagSet(DirectoryEntry.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {

                DirectoryInformation->EndOfFile.QuadPart = 0;
                DirectoryInformation->AllocationSize.QuadPart = 0;

            } else {

                //
                // AllocationSize should be filled in with the number of clusters
                // actually allocated to the file, but we don't want to have to
                // go parse the entire FAT chain to obtain this piece of
                // information.  We'll let the caller assume that the file has
                // been allocated the number of bytes actually in the file.
                //

                DirectoryInformation->EndOfFile.QuadPart =
                    (ULONGLONG)DirectoryEntry.FileSize;
                DirectoryInformation->AllocationSize.QuadPart =
                    (ULONGLONG)DirectoryEntry.FileSize;
            }

            DirectoryInformation->CreationTime =
                FatxFatTimestampToTime(&DirectoryEntry.CreationTime);
            DirectoryInformation->LastAccessTime =
            DirectoryInformation->LastWriteTime =
            DirectoryInformation->ChangeTime =
                FatxFatTimestampToTime(&DirectoryEntry.LastWriteTime);

            if (DirectoryEntry.FileAttributes == 0) {
                DirectoryInformation->FileAttributes = FILE_ATTRIBUTE_NORMAL;
            } else {
                DirectoryInformation->FileAttributes = DirectoryEntry.FileAttributes;
            }
        }

        //
        // If nothing has gone wrong yet, then copy the file name to the user's
        // buffer.
        //

        if (NT_SUCCESS(status)) {

            OutputBytesRemaining =
                ALIGN_DOWN(IrpSp->Parameters.QueryDirectory.Length -
                FileInformationBaseLength, sizeof(OCHAR));
            FileNameBytesToCopy = DirectoryEntry.FileNameLength;

            if (FileNameBytesToCopy > OutputBytesRemaining) {
                FileNameBytesToCopy = OutputBytesRemaining;
                status = STATUS_BUFFER_OVERFLOW;
            } else {
                status = STATUS_SUCCESS;
            }

            RtlCopyMemory((PUCHAR)DirectoryInformation + FileInformationBaseLength,
                DirectoryEntry.FileName, FileNameBytesToCopy);

            //
            // Fill in the number of bytes that we wrote to the user's buffer.
            //

            Irp->IoStatus.Information = FileInformationBaseLength +
                FileNameBytesToCopy;

            //
            // Check that we didn't overflow the user's buffer.  The I/O manager
            // does the initial check to make sure there's enough space for the
            // static structure for a given information class, but we might
            // overflow the buffer when copying in the variable length file
            // name.
            //

            ASSERT(Irp->IoStatus.Information <=
                IrpSp->Parameters.QueryDirectory.Length);

            //
            // Update the query offset.
            //

            DirectoryEnumContext->QueryOffset = DirectoryByteOffset +
                sizeof(DIRENT);

            //
            // Store the directory byte offset in the directory file control
            // block.  If the caller attempts to open this file, then we'll use
            // this to quickly locate the file's directory entry.
            //

            DirectoryFcb->Directory.DirectoryByteOffsetLookupHint =
                DirectoryByteOffset;
        }

    } else if (status == STATUS_END_OF_FILE) {

        //
        // If we hit the end of the directory stream, then return an appropriate
        // status code depending on whether this was the first pass through this
        // routine for this handle or not.
        //

        status = InitialQuery ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
    }

CleanupAndExit:
    FatxReleaseVolumeMutex(VolumeExtension);

    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return status;
}
