/*++ Copyright (c) 2000-2001 Microsoft Corporation Module Name: dirctrl.c Abstract: This module implements routines related to handling IRP_MJ_DIRECTORY_CONTROL. --*/ #include "gdfx.h" NTSTATUS GdfxFindNextDirectoryEntry( IN PGDF_VOLUME_EXTENSION VolumeExtension, IN PIRP Irp, IN PGDF_FCB DirectoryFcb, IN PDIRECTORY_ENUM_CONTEXT DirectoryEnumContext, OUT PGDF_DIRECTORY_ENTRY *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. DirectoryEnumContext - Specifies the directory enumeration context. ReturnedDirectoryEntry - Specifies the buffer to receive the pointer to 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; ULONG DirectoryByteOffset; POBJECT_STRING TemplateFileName; PVOID CacheBuffer; ULONG CacheBufferByteOffset; PGDF_DIRECTORY_ENTRY DirectoryEntry; OBJECT_STRING DirectoryEntryFileName; ASSERT(GdfxIsFlagSet(DirectoryFcb->Flags, GDF_FCB_DIRECTORY)); DirectoryByteOffset = DirectoryEnumContext->QueryOffset; TemplateFileName = &DirectoryEnumContext->TemplateFileName; // // If this is an empty directory, then return now that the name isn't found. // if (DirectoryFcb->FileSize == 0) { return STATUS_END_OF_FILE; } // // Process the directory stream. // CacheBuffer = NULL; CacheBufferByteOffset = 0; while (DirectoryByteOffset < DirectoryFcb->FileSize) { // // If we haven't mapped in a cache buffer yet or if we're switching // sectors, then we need to switch cache buffers. // if ((CacheBuffer == NULL) || (CacheBufferByteOffset != (DirectoryByteOffset & ~GDF_CD_SECTOR_MASK))) { if (CacheBuffer != NULL) { FscUnmapBuffer(CacheBuffer); CacheBuffer = NULL; } CacheBufferByteOffset = (DirectoryByteOffset & ~GDF_CD_SECTOR_MASK); status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp, GdfxSectorToPhysicalByteOffset(DirectoryFcb->FirstSector) + CacheBufferByteOffset, FALSE, &CacheBuffer); if (!NT_SUCCESS(status)) { goto CleanupAndExit; } } // // Make the directory byte offset relative to the current cache buffer. // DirectoryByteOffset &= GDF_CD_SECTOR_MASK; // // Compute the cache buffer relative pointer to the directory entry. // DirectoryEntry = (PGDF_DIRECTORY_ENTRY)((PUCHAR)CacheBuffer + DirectoryByteOffset); // // If the left and right entry indexes are negative one, then this is // sector padding, so we should advance to the next directory sector and // continue. // if ((DirectoryEntry->LeftEntryIndex == (USHORT)-1) && (DirectoryEntry->RightEntryIndex == (USHORT)-1)) { DirectoryByteOffset = CacheBufferByteOffset + GDF_CD_SECTOR_SIZE; continue; } // // If there's not enough space in the current sector to hold the header // of a directory entry, then the directory entry is invalid and the // disk is corrupt. // // We check for these types of disk corruption in order to guard against // taking a potential page fault if we extend past the current sector. // if (DirectoryByteOffset > (GDF_CD_SECTOR_SIZE - FIELD_OFFSET(GDF_DIRECTORY_ENTRY, FileName))) { GdfxDbgPrint(("GDFX: found invalid directory byte offset\n")); status = STATUS_DISK_CORRUPT_ERROR; goto CleanupAndExit; } // // If there's not enough space in the current sector to hold the header // and the file name, then the directory entry is invalid and the disk // is corrupt. // if ((DirectoryByteOffset + DirectoryEntry->FileNameLength) > (GDF_CD_SECTOR_SIZE - FIELD_OFFSET(GDF_DIRECTORY_ENTRY, FileName))) { GdfxDbgPrint(("GDFX: found invalid directory byte offset\n")); status = STATUS_DISK_CORRUPT_ERROR; goto CleanupAndExit; } // // Check if the file name matches the name we're looking for. // DirectoryEntryFileName.Length = DirectoryEntry->FileNameLength; DirectoryEntryFileName.Buffer = DirectoryEntry->FileName; if (((TemplateFileName->Buffer == NULL) || IoIsNameInExpression(TemplateFileName, &DirectoryEntryFileName))) { // // The file name matches the template file name. Leave the cache // buffer mapped and return a pointer to the directory entry and its // directory byte offset to the caller. The caller is responsible // for unmapping the cache buffer. // *ReturnedDirectoryEntry = DirectoryEntry; *ReturnedDirectoryByteOffset = CacheBufferByteOffset + DirectoryByteOffset; return STATUS_SUCCESS; } // // Advance to the next directory byte offset. // DirectoryByteOffset = CacheBufferByteOffset + DirectoryByteOffset + ALIGN_UP(FIELD_OFFSET(GDF_DIRECTORY_ENTRY, FileName) + DirectoryEntry->FileNameLength, sizeof(ULONG)); } // // We reached the end of the file without finding the template file name. // status = STATUS_END_OF_FILE; CleanupAndExit: if (CacheBuffer != NULL) { FscUnmapBuffer(CacheBuffer); } return status; } NTSTATUS GdfxFsdDirectoryControl( 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; PGDF_VOLUME_EXTENSION VolumeExtension; PIO_STACK_LOCATION IrpSp; PFILE_OBJECT FileObject; PGDF_FCB DirectoryFcb; ULONG FileInformationClass; SIZE_T FileInformationBaseLength; PDIRECTORY_ENUM_CONTEXT DirectoryEnumContext; POBJECT_STRING TemplateFileName; BOOLEAN InitialQuery; PGDF_DIRECTORY_ENTRY DirectoryEntry; ULONG DirectoryByteOffset; PFILE_DIRECTORY_INFORMATION DirectoryInformation; LARGE_INTEGER TimeStamp; ULONG FileSize; ULONG FileNameBytesToCopy; ULONG OutputBytesRemaining; VolumeExtension = (PGDF_VOLUME_EXTENSION)DeviceObject->DeviceExtension; IrpSp = IoGetCurrentIrpStackLocation(Irp); FileObject = IrpSp->FileObject; DirectoryFcb = (PGDF_FCB)FileObject->FsContext; // // Synchronize the creation and access of the directory context control // block by acquiring the global mutex. // GdfxAcquireGlobalMutexExclusive(); // // Ensure that the file object is for a directory. // if (GdfxIsFlagClear(DirectoryFcb->Flags, GDF_FCB_DIRECTORY)) { status = STATUS_INVALID_PARAMETER; 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 (GdfxIsFlagSet(IrpSp->Flags, SL_INDEX_SPECIFIED)) { status = STATUS_NOT_IMPLEMENTED; goto CleanupAndExit; } // // If this is the first query for this directory, then prepare the template // file name. // 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 (GdfxIsFlagSet(IrpSp->Flags, SL_RESTART_SCAN)) { DirectoryEnumContext->QueryOffset = 0; } // // Find the next file identifier descriptor 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 = GdfxFindNextDirectoryEntry(VolumeExtension, Irp, DirectoryFcb, DirectoryEnumContext, &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) { TimeStamp = VolumeExtension->TimeStamp; DirectoryInformation->CreationTime = TimeStamp; DirectoryInformation->LastAccessTime = TimeStamp; DirectoryInformation->LastWriteTime = TimeStamp; DirectoryInformation->ChangeTime = TimeStamp; if (GdfxIsFlagSet(DirectoryEntry->FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) { DirectoryInformation->FileAttributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY; DirectoryInformation->EndOfFile.QuadPart = 0; DirectoryInformation->AllocationSize.QuadPart = 0; } else { DirectoryInformation->FileAttributes = FILE_ATTRIBUTE_READONLY; FileSize = DirectoryEntry->FileSize; DirectoryInformation->EndOfFile.QuadPart = (ULONGLONG)FileSize; DirectoryInformation->AllocationSize.QuadPart = (ULONGLONG)FileSize; } } // // 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 + ALIGN_UP(FIELD_OFFSET(GDF_DIRECTORY_ENTRY, FileName) + DirectoryEntry->FileNameLength, sizeof(ULONG)); } // // Unmap the buffer holding the directory entry. // FscUnmapBuffer(DirectoryEntry); } 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: GdfxReleaseGlobalMutex(); Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }