/*++ Copyright (c) 1989-2000 Microsoft Corporation Module Name: FsCtrl.c Abstract: This module implements the File System Control routines for Fat called by the dispatch driver. // @@BEGIN_DDKSPLIT Author: Gary Kimura [GaryKi] 28-Dec-1989 Revision History: Scott Quinn [ScottQ] 05-Apr-1996 Added fat32 support. Mike Sliger [MSliger] 05-Apr-1996 // @@END_DDKSPLIT --*/ #include "FatProcs.h" // // The Bug check file id for this module // #define BugCheckFileId (FAT_BUG_CHECK_FSCTRL) // // The local debug trace level // #define Dbg (DEBUG_TRACE_FSCTRL) // // Local procedure prototypes // NTSTATUS FatMountVolume ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject, IN PVPB Vpb, IN PDEVICE_OBJECT FsDeviceObject ); NTSTATUS FatVerifyVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); BOOLEAN FatIsMediaWriteProtected ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject ); NTSTATUS FatUserFsCtrl ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatOplockRequest ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatLockVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatUnlockVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatDismountVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatDirtyVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatIsVolumeDirty ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatIsVolumeMounted ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatIsPathnameValid ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatInvalidateVolumes ( IN PIRP Irp ); BOOLEAN FatPerformVerifyDiskRead ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PVOID Buffer, IN LBO Lbo, IN ULONG NumberOfBytesToRead, IN BOOLEAN ReturnOnError ); NTSTATUS FatQueryRetrievalPointers ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatQueryBpb ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatGetStatistics ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatAllowExtendedDasdIo ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); // // Local support routine prototypes // NTSTATUS FatGetVolumeBitmap ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatGetRetrievalPointers ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS FatMoveFile ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); VOID FatComputeMoveFileSplicePoints ( PIRP_CONTEXT IrpContext, PFCB FcbOrDcb, ULONG FileOffset, ULONG TargetCluster, ULONG BytesToReallocate, PULONG FirstSpliceSourceCluster, PULONG FirstSpliceTargetCluster, PULONG SecondSpliceSourceCluster, PULONG SecondSpliceTargetCluster, PLARGE_MCB SourceMcb ); VOID FatComputeMoveFileParameter ( IN PIRP_CONTEXT IrpContext, IN PFCB FcbOrDcb, IN ULONG FileOffset, IN OUT PULONG ByteCount, OUT PULONG BytesToReallocate, OUT PULONG BytesToWrite ); NTSTATUS FatSearchBufferForLabel( IN PIRP_CONTEXT IrpContext, IN PVPB Vpb, IN PVOID Buffer, IN ULONG Size, OUT PBOOLEAN LabelFound ); VOID FatVerifyLookupFatEntry ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN ULONG FatIndex, IN OUT PULONG FatEntry ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, FatAddMcbEntry) #pragma alloc_text(PAGE, FatAllowExtendedDasdIo) #pragma alloc_text(PAGE, FatCommonFileSystemControl) #pragma alloc_text(PAGE, FatComputeMoveFileParameter) #pragma alloc_text(PAGE, FatComputeMoveFileSplicePoints) #pragma alloc_text(PAGE, FatDirtyVolume) #pragma alloc_text(PAGE, FatDismountVolume) #pragma alloc_text(PAGE, FatFsdFileSystemControl) #pragma alloc_text(PAGE, FatGetRetrievalPointers) #pragma alloc_text(PAGE, FatGetStatistics) #pragma alloc_text(PAGE, FatGetVolumeBitmap) #pragma alloc_text(PAGE, FatIsMediaWriteProtected) #pragma alloc_text(PAGE, FatIsPathnameValid) #pragma alloc_text(PAGE, FatIsVolumeDirty) #pragma alloc_text(PAGE, FatIsVolumeMounted) #pragma alloc_text(PAGE, FatLockVolume) #pragma alloc_text(PAGE, FatLookupLastMcbEntry) #pragma alloc_text(PAGE, FatMountVolume) #pragma alloc_text(PAGE, FatMoveFile) #pragma alloc_text(PAGE, FatOplockRequest) #pragma alloc_text(PAGE, FatPerformVerifyDiskRead) #pragma alloc_text(PAGE, FatQueryBpb) #pragma alloc_text(PAGE, FatQueryRetrievalPointers) #pragma alloc_text(PAGE, FatRemoveMcbEntry) #pragma alloc_text(PAGE, FatSearchBufferForLabel) #pragma alloc_text(PAGE, FatUnlockVolume) #pragma alloc_text(PAGE, FatUserFsCtrl) #pragma alloc_text(PAGE, FatVerifyLookupFatEntry) #pragma alloc_text(PAGE, FatVerifyVolume) #endif #if DBG BOOLEAN FatMoveFileDebug = 0; #endif // // These wrappers go around the MCB package; we scale the LBO's passed // in (which can be bigger than 32 bits on fat32) by the volume's sector // size. // // Note we now use the real large mcb package. This means these shims // now also convert the -1 unused LBN number to the 0 of the original // mcb package. // #define MCB_SCALE_LOG2 (Vcb->AllocationSupport.LogOfBytesPerSector) #define MCB_SCALE (1 << MCB_SCALE_LOG2) #define MCB_SCALE_MODULO (MCB_SCALE - 1) BOOLEAN FatAddMcbEntry ( IN PVCB Vcb, IN PLARGE_MCB Mcb, IN VBO Vbo, IN LBO Lbo, IN ULONG SectorCount ) { PAGED_CODE(); if (SectorCount) { // // Round up sectors, but be careful as SectorCount approaches 4Gb. // Note that for x>0, (x+m-1)/m = ((x-1)/m)+(m/m) = ((x-1)/m)+1 // SectorCount--; SectorCount >>= MCB_SCALE_LOG2; SectorCount++; } Vbo >>= MCB_SCALE_LOG2; Lbo >>= MCB_SCALE_LOG2; return FsRtlAddLargeMcbEntry( Mcb, ((LONGLONG) Vbo), ((LONGLONG) Lbo), ((LONGLONG) SectorCount) ); } BOOLEAN FatLookupMcbEntry ( IN PVCB Vcb, IN PLARGE_MCB Mcb, IN VBO Vbo, OUT PLBO Lbo, OUT PULONG SectorCount OPTIONAL, OUT PULONG Index OPTIONAL ) { BOOLEAN Results; LONGLONG LiLbo; LONGLONG LiSectorCount; ULONG Remainder; LiLbo = 0; LiSectorCount = 0; Remainder = Vbo & MCB_SCALE_MODULO; Results = FsRtlLookupLargeMcbEntry( Mcb, (Vbo >> MCB_SCALE_LOG2), &LiLbo, ARGUMENT_PRESENT(SectorCount) ? &LiSectorCount : NULL, NULL, NULL, Index ); if ((ULONG) LiLbo != -1) { *Lbo = (((LBO) LiLbo) << MCB_SCALE_LOG2); if (Results) { *Lbo += Remainder; } } else { *Lbo = 0; } if (ARGUMENT_PRESENT(SectorCount)) { *SectorCount = (ULONG) LiSectorCount; if (*SectorCount) { *SectorCount <<= MCB_SCALE_LOG2; if (*SectorCount == 0) { *SectorCount = (ULONG) -1; } if (Results) { *SectorCount -= Remainder; } } } return Results; } // // NOTE: Vbo/Lbn undefined if MCB is empty & return code false. // BOOLEAN FatLookupLastMcbEntry ( IN PVCB Vcb, IN PLARGE_MCB Mcb, OUT PVBO Vbo, OUT PLBO Lbo, OUT PULONG Index ) { BOOLEAN Results; LONGLONG LiVbo; LONGLONG LiLbo; ULONG LocalIndex; PAGED_CODE(); LiVbo = LiLbo = 0; LocalIndex = 0; Results = FsRtlLookupLastLargeMcbEntryAndIndex( Mcb, &LiVbo, &LiLbo, &LocalIndex ); *Vbo = ((VBO) LiVbo) << MCB_SCALE_LOG2; if (((ULONG) LiLbo) != -1) { *Lbo = ((LBO) LiLbo) << MCB_SCALE_LOG2; *Lbo += (MCB_SCALE - 1); *Vbo += (MCB_SCALE - 1); } else { *Lbo = 0; } if (Index) { *Index = LocalIndex; } return Results; } BOOLEAN FatGetNextMcbEntry ( IN PVCB Vcb, IN PLARGE_MCB Mcb, IN ULONG RunIndex, OUT PVBO Vbo, OUT PLBO Lbo, OUT PULONG SectorCount ) { BOOLEAN Results; LONGLONG LiVbo; LONGLONG LiLbo; LONGLONG LiSectorCount; PAGED_CODE(); LiVbo = LiLbo = 0; Results = FsRtlGetNextLargeMcbEntry( Mcb, RunIndex, &LiVbo, &LiLbo, &LiSectorCount ); if (Results) { *Vbo = ((VBO) LiVbo) << MCB_SCALE_LOG2; if (((ULONG) LiLbo) != -1) { *Lbo = ((LBO) LiLbo) << MCB_SCALE_LOG2; } else { *Lbo = 0; } *SectorCount = ((ULONG) LiSectorCount) << MCB_SCALE_LOG2; if ((*SectorCount == 0) && (LiSectorCount != 0)) { *SectorCount = (ULONG) -1; /* it overflowed */ } } return Results; } VOID FatRemoveMcbEntry ( IN PVCB Vcb, IN PLARGE_MCB Mcb, IN VBO Vbo, IN ULONG SectorCount ) { if ((SectorCount) && (SectorCount != 0xFFFFFFFF)) { SectorCount--; SectorCount >>= MCB_SCALE_LOG2; SectorCount++; } Vbo >>= MCB_SCALE_LOG2; #if DBG try { #endif FsRtlRemoveLargeMcbEntry( Mcb, (LONGLONG) Vbo, (LONGLONG) SectorCount); #if DBG } except(FatBugCheckExceptionFilter( GetExceptionInformation() )) { NOTHING; } #endif } NTSTATUS FatFsdFileSystemControl ( IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the FSD part of FileSystem control operations Arguments: VolumeDeviceObject - Supplies the volume device object where the file exists Irp - Supplies the Irp being processed Return Value: NTSTATUS - The FSD status for the IRP --*/ { BOOLEAN Wait; NTSTATUS Status; PIRP_CONTEXT IrpContext = NULL; BOOLEAN TopLevel; DebugTrace(+1, Dbg,"FatFsdFileSystemControl\n", 0); // // Call the common FileSystem Control routine, with blocking allowed if // synchronous. This opeation needs to special case the mount // and verify suboperations because we know they are allowed to block. // We identify these suboperations by looking at the file object field // and seeing if its null. // if (IoGetCurrentIrpStackLocation(Irp)->FileObject == NULL) { Wait = TRUE; } else { Wait = CanFsdWait( Irp ); } FsRtlEnterFileSystem(); TopLevel = FatIsIrpTopLevel( Irp ); try { PIO_STACK_LOCATION IrpSp; IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // We need to made a special check here for the InvalidateVolumes // FSCTL as that comes in with a FileSystem device object instead // of a volume device object. // if (FatDeviceIsFatFsdo( IrpSp->DeviceObject) && (IrpSp->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL) && (IrpSp->MinorFunction == IRP_MN_USER_FS_REQUEST) && (IrpSp->Parameters.FileSystemControl.FsControlCode == FSCTL_INVALIDATE_VOLUMES)) { Status = FatInvalidateVolumes( Irp ); } else { IrpContext = FatCreateIrpContext( Irp, Wait ); Status = FatCommonFileSystemControl( IrpContext, Irp ); } } except(FatExceptionFilter( IrpContext, GetExceptionInformation() )) { // // We had some trouble trying to perform the requested // operation, so we'll abort the I/O request with // the error status that we get back from the // execption code // Status = FatProcessException( IrpContext, Irp, GetExceptionCode() ); } if (TopLevel) { IoSetTopLevelIrp( NULL ); } FsRtlExitFileSystem(); // // And return to our caller // DebugTrace(-1, Dbg, "FatFsdFileSystemControl -> %08lx\n", Status); return Status; } NTSTATUS FatCommonFileSystemControl ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This is the common routine for doing FileSystem control operations called by both the fsd and fsp threads Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; // // Get a pointer to the current Irp stack location // IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg,"FatCommonFileSystemControl\n", 0); DebugTrace( 0, Dbg,"Irp = %08lx\n", Irp); DebugTrace( 0, Dbg,"MinorFunction = %08lx\n", IrpSp->MinorFunction); // // We know this is a file system control so we'll case on the // minor function, and call a internal worker routine to complete // the irp. // switch (IrpSp->MinorFunction) { case IRP_MN_USER_FS_REQUEST: Status = FatUserFsCtrl( IrpContext, Irp ); break; case IRP_MN_MOUNT_VOLUME: Status = FatMountVolume( IrpContext, IrpSp->Parameters.MountVolume.DeviceObject, IrpSp->Parameters.MountVolume.Vpb, IrpSp->DeviceObject ); // // Complete the request. // // We do this here because FatMountVolume can be called recursively, // but the Irp is only to be completed once. // // NOTE: I don't think this is true anymore (danlo 3/15/1999). Probably // an artifact of the old doublespace attempt. // FatCompleteRequest( IrpContext, Irp, Status ); break; case IRP_MN_VERIFY_VOLUME: Status = FatVerifyVolume( IrpContext, Irp ); break; default: DebugTrace( 0, Dbg, "Invalid FS Control Minor Function %08lx\n", IrpSp->MinorFunction); FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); Status = STATUS_INVALID_DEVICE_REQUEST; break; } DebugTrace(-1, Dbg, "FatCommonFileSystemControl -> %08lx\n", Status); return Status; } // // Local Support Routine // NTSTATUS FatMountVolume ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject, IN PVPB Vpb, IN PDEVICE_OBJECT FsDeviceObject ) /*++ Routine Description: This routine performs the mount volume operation. It is responsible for either completing of enqueuing the input Irp. Its job is to verify that the volume denoted in the IRP is a Fat volume, and create the VCB and root DCB structures. The algorithm it uses is essentially as follows: 1. Create a new Vcb Structure, and initialize it enough to do cached volume file I/O. 2. Read the disk and check if it is a Fat volume. 3. If it is not a Fat volume then free the cached volume file, delete the VCB, and complete the IRP with STATUS_UNRECOGNIZED_VOLUME 4. Check if the volume was previously mounted and if it was then do a remount operation. This involves reinitializing the cached volume file, checking the dirty bit, resetting up the allocation support, deleting the VCB, hooking in the old VCB, and completing the IRP. 5. Otherwise create a root DCB, create Fsp threads as necessary, and complete the IRP. Arguments: TargetDeviceObject - This is where we send all of our requests. Vpb - This gives us additional information needed to complete the mount. Return Value: NTSTATUS - The return status for the operation --*/ { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp ); NTSTATUS Status; PBCB BootBcb; PPACKED_BOOT_SECTOR BootSector; PBCB DirentBcb; PDIRENT Dirent; ULONG ByteOffset; BOOLEAN MountNewVolume = FALSE; BOOLEAN WeClearedVerifyRequiredBit = FALSE; PDEVICE_OBJECT RealDevice; PVOLUME_DEVICE_OBJECT VolDo = NULL; PVCB Vcb = NULL; PLIST_ENTRY Links; IO_STATUS_BLOCK Iosb; ULONG ChangeCount = 0; DISK_GEOMETRY Geometry; PARTITION_INFORMATION_EX PartitionInformation; NTSTATUS StatusPartInfo; DebugTrace(+1, Dbg, "FatMountVolume\n", 0); DebugTrace( 0, Dbg, "TargetDeviceObject = %08lx\n", TargetDeviceObject); DebugTrace( 0, Dbg, "Vpb = %08lx\n", Vpb); ASSERT( FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) ); ASSERT( FatDeviceIsFatFsdo( FsDeviceObject)); // // Verify that there is a disk here and pick up the change count. // Status = FatPerformDevIoCtrl( IrpContext, IOCTL_DISK_CHECK_VERIFY, TargetDeviceObject, &ChangeCount, sizeof(ULONG), FALSE, TRUE, &Iosb ); if (!NT_SUCCESS( Status )) { // // If we will allow a raw mount then avoid sending the popup. // // Only send this on "true" disk devices to handle the accidental // legacy of FAT. No other FS will throw a harderror on empty // drives. // // Cmd should really handle this per 9x. // if (!FlagOn( IrpSp->Flags, SL_ALLOW_RAW_MOUNT ) && Vpb->RealDevice->DeviceType == FILE_DEVICE_DISK) { FatNormalizeAndRaiseStatus( IrpContext, Status ); } return Status; } if (Iosb.Information != sizeof(ULONG)) { // // Be safe about the count in case the driver didn't fill it in // ChangeCount = 0; } // // If this is a CD class device, then check to see if there is a // 'data track' or not. This is to avoid issuing paging reads which will // fail later in the mount process (e.g. CD-DA or blank CD media) // if ((TargetDeviceObject->DeviceType == FILE_DEVICE_CD_ROM) && !FatScanForDataTrack( IrpContext, TargetDeviceObject)) { return STATUS_UNRECOGNIZED_VOLUME; } // // Ping the volume with a partition query and pick up the partition // type. We'll check this later to avoid some scurrilous volumes. // StatusPartInfo = FatPerformDevIoCtrl( IrpContext, IOCTL_DISK_GET_PARTITION_INFO_EX, TargetDeviceObject, &PartitionInformation, sizeof(PARTITION_INFORMATION_EX), FALSE, TRUE, &Iosb ); // // Make sure we can wait. // SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT); // // Initialize the Bcbs and our final state so that the termination // handlers will know what to free or unpin // BootBcb = NULL; DirentBcb = NULL; Vcb = NULL; VolDo = NULL; MountNewVolume = FALSE; try { BOOLEAN DoARemount = FALSE; PVCB OldVcb; PVPB OldVpb; // // Synchronize with FatCheckForDismount(), which modifies the vpb. // (VOID)FatAcquireExclusiveGlobal( IrpContext ); // // Create a new volume device object. This will have the Vcb // hanging off of its end, and set its alignment requirement // from the device we talk to. // if (!NT_SUCCESS(Status = IoCreateDevice( FatData.DriverObject, sizeof(VOLUME_DEVICE_OBJECT) - sizeof(DEVICE_OBJECT), NULL, FILE_DEVICE_DISK_FILE_SYSTEM, 0, FALSE, (PDEVICE_OBJECT *)&VolDo))) { try_return( Status ); } #ifdef _PNP_POWER_ // // This driver doesn't talk directly to a device, and (at the moment) // isn't otherwise concerned about power management. // VolDo->DeviceObject.DeviceObjectExtension->PowerControlNeeded = FALSE; #endif // // Our alignment requirement is the larger of the processor alignment requirement // already in the volume device object and that in the TargetDeviceObject // if (TargetDeviceObject->AlignmentRequirement > VolDo->DeviceObject.AlignmentRequirement) { VolDo->DeviceObject.AlignmentRequirement = TargetDeviceObject->AlignmentRequirement; } // // Initialize the overflow queue for the volume // VolDo->OverflowQueueCount = 0; InitializeListHead( &VolDo->OverflowQueue ); VolDo->PostedRequestCount = 0; KeInitializeSpinLock( &VolDo->OverflowQueueSpinLock ); // // We must initialize the stack size in our device object before // the following reads, because the I/O system has not done it yet. // This must be done before we clear the device initializing flag // otherwise a filter could attach and copy the wrong stack size into // it's device object. // VolDo->DeviceObject.StackSize = (CCHAR)(TargetDeviceObject->StackSize + 1); // // We must also set the sector size correctly in our device object // before clearing the device initializing flag. // Status = FatPerformDevIoCtrl( IrpContext, IOCTL_DISK_GET_DRIVE_GEOMETRY, TargetDeviceObject, &Geometry, sizeof( DISK_GEOMETRY ), FALSE, TRUE, NULL ); VolDo->DeviceObject.SectorSize = (USHORT)Geometry.BytesPerSector; // // Indicate that this device object is now completely initialized // ClearFlag(VolDo->DeviceObject.Flags, DO_DEVICE_INITIALIZING); // // Now Before we can initialize the Vcb we need to set up the device // object field in the Vpb to point to our new volume device object. // This is needed when we create the virtual volume file's file object // in initialize vcb. // Vpb->DeviceObject = (PDEVICE_OBJECT)VolDo; // // If the real device needs verification, temporarily clear the // field. // RealDevice = Vpb->RealDevice; if ( FlagOn(RealDevice->Flags, DO_VERIFY_VOLUME) ) { ClearFlag(RealDevice->Flags, DO_VERIFY_VOLUME); WeClearedVerifyRequiredBit = TRUE; } // // Initialize the new vcb // FatInitializeVcb( IrpContext, &VolDo->Vcb, TargetDeviceObject, Vpb, FsDeviceObject); // // Get a reference to the Vcb hanging off the end of the device object // Vcb = &VolDo->Vcb; // // Read in the boot sector, and have the read be the minumum size // needed. We know we can wait. // // // We need to commute errors on CD so that CDFS will get its crack. Audio // and even data media may not be universally readable on sector zero. // try { FatReadVolumeFile( IrpContext, Vcb, 0, // Starting Byte sizeof(PACKED_BOOT_SECTOR), &BootBcb, (PVOID *)&BootSector ); } except( Vpb->RealDevice->DeviceType == FILE_DEVICE_CD_ROM ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { NOTHING; } // // Call a routine to check the boot sector to see if it is fat // if (BootBcb == NULL || !FatIsBootSectorFat( BootSector)) { DebugTrace(0, Dbg, "Not a Fat Volume\n", 0); // // Complete the request and return to our caller // try_return( Status = STATUS_UNRECOGNIZED_VOLUME ); } // // Unpack the BPB. We used to do some sanity checking of the FATs at // this point, but authoring errors on third-party devices prevent // us from continuing to safeguard ourselves. We can only hope the // boot sector check is good enough. // // (read: digital cameras) // // Win9x does the same. // FatUnpackBios( &Vcb->Bpb, &BootSector->PackedBpb ); // // Check if we have an OS/2 Boot Manager partition and treat it as an // unknown file system. We'll check the partition type in from the // partition table and we ensure that it has less than 0x80 sectors, // which is just a heuristic that will capture all real OS/2 BM partitions // and avoid the chance we'll discover partitions which erroneously // (but to this point, harmlessly) put down the OS/2 BM type. // // Note that this is only conceivable on good old MBR media. // // The OS/2 Boot Manager boot format mimics a FAT16 partition in sector // zero but does is not a real FAT16 file system. For example, the boot // sector indicates it has 2 FATs but only really has one, with the boot // manager code overlaying the second FAT. If we then set clean bits in // FAT[0] we'll corrupt that code. // if (NT_SUCCESS( StatusPartInfo ) && (PartitionInformation.PartitionStyle == PARTITION_STYLE_MBR && PartitionInformation.Mbr.PartitionType == PARTITION_OS2BOOTMGR) && (Vcb->Bpb.Sectors != 0 && Vcb->Bpb.Sectors < 0x80)) { DebugTrace( 0, Dbg, "OS/2 Boot Manager volume detected, volume not mounted. \n", 0 ); // // Complete the request and return to our caller // try_return( Status = STATUS_UNRECOGNIZED_VOLUME ); } // // Verify that the sector size recorded in the Bpb matches what the // device currently reports it's sector size to be. // if ( !NT_SUCCESS( Status) || (Geometry.BytesPerSector != Vcb->Bpb.BytesPerSector)) { try_return( Status = STATUS_UNRECOGNIZED_VOLUME ); } // // This is a fat volume, so extract the bpb, serial number. The // label we'll get later after we've created the root dcb. // // Note that the way data caching is done, we set neither the // direct I/O or Buffered I/O bit in the device object flags. // if (Vcb->Bpb.Sectors != 0) { Vcb->Bpb.LargeSectors = 0; } if (IsBpbFat32(&BootSector->PackedBpb)) { CopyUchar4( &Vpb->SerialNumber, ((PPACKED_BOOT_SECTOR_EX)BootSector)->Id ); } else { CopyUchar4( &Vpb->SerialNumber, BootSector->Id ); // // Allocate space for the stashed boot sector chunk. This only has meaning on // FAT12/16 volumes since this only is kept for the FSCTL_QUERY_FAT_BPB and it and // its users are a bit wierd, thinking that a BPB exists wholly in the first 0x24 // bytes. // Vcb->First0x24BytesOfBootSector = FsRtlAllocatePoolWithTag( PagedPool, 0x24, TAG_STASHED_BPB ); // // Stash a copy of the first 0x24 bytes // RtlCopyMemory( Vcb->First0x24BytesOfBootSector, BootSector, 0x24 ); } // // Now unpin the boot sector, so when we set up allocation eveything // works. // FatUnpinBcb( IrpContext, BootBcb ); // // Compute a number of fields for Vcb.AllocationSupport // FatSetupAllocationSupport( IrpContext, Vcb ); // // Sanity check the FsInfo information for FAT32 volumes. Silently deal // with messed up information by effectively disabling FsInfo updates. // if (FatIsFat32( Vcb )) { if (Vcb->Bpb.FsInfoSector >= Vcb->Bpb.ReservedSectors) { Vcb->Bpb.FsInfoSector = 0; } } // // Create a root Dcb so we can read in the volume label. If this is FAT32, we can // discover corruption in the FAT chain. // // NOTE: this exception handler presumes that this is the only spot where we can // discover corruption in the mount process. If this ever changes, this handler // MUST be expanded. The reason we have this guy here is because we have to rip // the structures down now (in the finally below) and can't wait for the outer // exception handling to do it for us, at which point everything will have vanished. // try { FatCreateRootDcb( IrpContext, Vcb ); } except (GetExceptionCode() == STATUS_FILE_CORRUPT_ERROR ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { // // The volume needs to be dirtied, do it now. Note that at this point we have built // enough of the Vcb to pull this off. // FatMarkVolume( IrpContext, Vcb, VolumeDirty ); // // Now keep bailing out ... // FatRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR ); } FatLocateVolumeLabel( IrpContext, Vcb, &Dirent, &DirentBcb, &ByteOffset ); if (Dirent != NULL) { OEM_STRING OemString; UNICODE_STRING UnicodeString; // // Compute the length of the volume name // OemString.Buffer = &Dirent->FileName[0]; OemString.MaximumLength = 11; for ( OemString.Length = 11; OemString.Length > 0; OemString.Length -= 1) { if ( (Dirent->FileName[OemString.Length-1] != 0x00) && (Dirent->FileName[OemString.Length-1] != 0x20) ) { break; } } UnicodeString.MaximumLength = MAXIMUM_VOLUME_LABEL_LENGTH; UnicodeString.Buffer = &Vcb->Vpb->VolumeLabel[0]; Status = RtlOemStringToCountedUnicodeString( &UnicodeString, &OemString, FALSE ); if ( !NT_SUCCESS( Status ) ) { try_return( Status ); } Vpb->VolumeLabelLength = UnicodeString.Length; } else { Vpb->VolumeLabelLength = 0; } // // Use the change count we noted initially *before* doing any work. // If something came along in the midst of this operation, we'll // verify and discover the problem. // Vcb->ChangeCount = ChangeCount; // // Now scan the list of previously mounted volumes and compare // serial numbers and volume labels off not currently mounted // volumes to see if we have a match. // for (Links = FatData.VcbQueue.Flink; Links != &FatData.VcbQueue; Links = Links->Flink) { OldVcb = CONTAINING_RECORD( Links, VCB, VcbLinks ); OldVpb = OldVcb->Vpb; // // Skip over ourselves since we're already in the VcbQueue // if (OldVpb == Vpb) { continue; } // // Check for a match: // // Serial Number, VolumeLabel and Bpb must all be the same. // Also the volume must have failed a verify before (ie. // VolumeNotMounted), and it must be in the same physical // drive than it was mounted in before. // if ( (OldVpb->SerialNumber == Vpb->SerialNumber) && (OldVcb->VcbCondition == VcbNotMounted) && (OldVpb->RealDevice == RealDevice) && (OldVpb->VolumeLabelLength == Vpb->VolumeLabelLength) && (RtlEqualMemory(&OldVpb->VolumeLabel[0], &Vpb->VolumeLabel[0], Vpb->VolumeLabelLength)) && (RtlEqualMemory(&OldVcb->Bpb, &Vcb->Bpb, IsBpbFat32(&Vcb->Bpb) ? sizeof(BIOS_PARAMETER_BLOCK) : FIELD_OFFSET(BIOS_PARAMETER_BLOCK, LargeSectorsPerFat) ))) { DoARemount = TRUE; break; } } if ( DoARemount ) { PVPB *IrpVpb; DebugTrace(0, Dbg, "Doing a remount\n", 0); DebugTrace(0, Dbg, "Vcb = %08lx\n", Vcb); DebugTrace(0, Dbg, "Vpb = %08lx\n", Vpb); DebugTrace(0, Dbg, "OldVcb = %08lx\n", OldVcb); DebugTrace(0, Dbg, "OldVpb = %08lx\n", OldVpb); // // The old target device object is about to be overwritten. // Drop the reference being held on that device object. // ObDereferenceObject( OldVcb->TargetDeviceObject ); // // This is a remount, so link the old vpb in place // of the new vpb and release the new vpb and the extra // volume device object we created earlier. // OldVpb->RealDevice = Vpb->RealDevice; OldVpb->RealDevice->Vpb = OldVpb; OldVcb->TargetDeviceObject = TargetDeviceObject; OldVcb->VcbCondition = VcbGood; // // Use the new changecount. // OldVcb->ChangeCount = Vcb->ChangeCount; // // Delete the extra new vpb, and make sure we don't use it again. // // Also if this is the Vpb referenced in the original Irp, set // that reference back to the old VPB. // IrpVpb = &IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->Parameters.MountVolume.Vpb; if (*IrpVpb == Vpb) { *IrpVpb = OldVpb; } ExFreePool( Vpb ); Vpb = NULL; // // Make sure the remaining stream files are orphaned. // Vcb->VirtualVolumeFile->Vpb = NULL; Vcb->RootDcb->Specific.Dcb.DirectoryFile->Vpb = NULL; // // Reinitialize the volume file cache and allocation support. // { CC_FILE_SIZES FileSizes; FileSizes.AllocationSize.QuadPart = FileSizes.FileSize.QuadPart = ( 0x40000 + 0x1000 ); FileSizes.ValidDataLength = FatMaxLarge; DebugTrace(0, Dbg, "Truncate and reinitialize the volume file\n", 0); CcInitializeCacheMap( OldVcb->VirtualVolumeFile, &FileSizes, TRUE, &FatData.CacheManagerNoOpCallbacks, Vcb ); // // Redo the allocation support // FatSetupAllocationSupport( IrpContext, OldVcb ); // // Get the state of the dirty bit. // FatCheckDirtyBit( IrpContext, OldVcb ); // // Check for write protected media. // if (FatIsMediaWriteProtected(IrpContext, TargetDeviceObject)) { SetFlag( OldVcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED ); } else { ClearFlag( OldVcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED ); } } // // Complete the request and return to our caller // try_return( Status = STATUS_SUCCESS ); } DebugTrace(0, Dbg, "Mount a new volume\n", 0); // // This is a new mount // // Create a blank ea data file fcb, just not for Fat32. // if (!FatIsFat32(Vcb)) { DIRENT TempDirent; PFCB EaFcb; RtlZeroMemory( &TempDirent, sizeof(DIRENT) ); RtlCopyMemory( &TempDirent.FileName[0], "EA DATA SF", 11 ); EaFcb = FatCreateFcb( IrpContext, Vcb, Vcb->RootDcb, 0, 0, &TempDirent, NULL, FALSE, TRUE ); // // Deny anybody who trys to open the file. // SetFlag( EaFcb->FcbState, FCB_STATE_SYSTEM_FILE ); Vcb->EaFcb = EaFcb; } // // Get the state of the dirty bit. // FatCheckDirtyBit( IrpContext, Vcb ); // // Check for write protected media. // if (FatIsMediaWriteProtected(IrpContext, TargetDeviceObject)) { SetFlag( Vcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED ); } else { ClearFlag( Vcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED ); } // // Lock volume in drive if we just mounted the boot drive. // if (FlagOn(RealDevice->Flags, DO_SYSTEM_BOOT_PARTITION)) { SetFlag(Vcb->VcbState, VCB_STATE_FLAG_BOOT_OR_PAGING_FILE); if (FlagOn(Vcb->VcbState, VCB_STATE_FLAG_REMOVABLE_MEDIA)) { FatToggleMediaEjectDisable( IrpContext, Vcb, TRUE ); } } // // Indicate to our termination handler that we have mounted // a new volume. // MountNewVolume = TRUE; // // Complete the request // Status = STATUS_SUCCESS; // // Ref the root dir stream object so we can send mount notification. // ObReferenceObject( Vcb->RootDcb->Specific.Dcb.DirectoryFile ); // // Remove the extra reference to this target DO made on behalf of us // by the IO system. In the remount case, we permit regular Vcb // deletion to do this work. // ObDereferenceObject( TargetDeviceObject ); try_exit: NOTHING; } finally { DebugUnwind( FatMountVolume ); FatUnpinBcb( IrpContext, BootBcb ); FatUnpinBcb( IrpContext, DirentBcb ); // // Check if a volume was mounted. If not then we need to // mark the Vpb not mounted again and delete the volume. // if ( !MountNewVolume ) { if ( Vpb != NULL ) { Vpb->DeviceObject = NULL; } if ( Vcb != NULL ) { // // Make sure we clean up the IrpContext field if the Vcb is // being taken down. // IrpContext->Vcb = NULL; FatDeleteVcb( IrpContext, Vcb ); } if ( VolDo != NULL ) { IoDeleteDevice( &VolDo->DeviceObject ); } } if ( WeClearedVerifyRequiredBit == TRUE ) { SetFlag(RealDevice->Flags, DO_VERIFY_VOLUME); } FatReleaseGlobal( IrpContext ); DebugTrace(-1, Dbg, "FatMountVolume -> %08lx\n", Status); } // // Now send mount notification. Note that since this is outside of any // synchronization since the synchronous delivery of this may go to // folks that provoke re-entrance to the FS. // if (MountNewVolume) { FsRtlNotifyVolumeEvent( Vcb->RootDcb->Specific.Dcb.DirectoryFile, FSRTL_VOLUME_MOUNT ); ObDereferenceObject( Vcb->RootDcb->Specific.Dcb.DirectoryFile ); } return Status; } // // Local Support Routine // NTSTATUS FatVerifyVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine performs the verify volume operation by checking the volume label and serial number physically on the media with the the Vcb currently claiming to have the volume mounted. It is responsible for either completing or enqueuing the input Irp. Regardless of whether the verify operation succeeds, the following operations are performed: - Set Vcb->VirtualEaFile back to its virgin state. - Purge all cached data (flushing first if verify succeeds) - Mark all Fcbs as needing verification If the volumes verifies correctly we also must: - Check the volume dirty bit. - Reinitialize the allocation support - Flush any dirty data If the volume verify fails, it may never be mounted again. If it is mounted again, it will happen as a remount operation. In preparation for that, and to leave the volume in a state that can be "lazy deleted" the following operations are performed: - Set the Vcb condition to VcbNotMounted - Uninitialize the volume file cachemap - Tear down the allocation support In the case of an abnormal termination we haven't determined the state of the volume, so we set the Device Object as needing verification again. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - If the verify operation completes, it will return either STATUS_SUCCESS or STATUS_WRONG_VOLUME, exactly. If an IO or other error is encountered, that status will be returned. --*/ { NTSTATUS Status = STATUS_SUCCESS; PIO_STACK_LOCATION IrpSp; PDIRENT RootDirectory = NULL; PPACKED_BOOT_SECTOR BootSector = NULL; BIOS_PARAMETER_BLOCK Bpb; PVOLUME_DEVICE_OBJECT VolDo; PVCB Vcb; PVPB Vpb; ULONG SectorSize; BOOLEAN ClearVerify = FALSE; BOOLEAN ReleaseEntireVolume = FALSE; BOOLEAN VerifyAlreadyDone = FALSE; DISK_GEOMETRY DiskGeometry; LBO RootDirectoryLbo; ULONG RootDirectorySize; BOOLEAN LabelFound; ULONG ChangeCount = 0; IO_STATUS_BLOCK Iosb; // // Get the current Irp stack location // IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatVerifyVolume\n", 0); DebugTrace( 0, Dbg, "DeviceObject = %08lx\n", IrpSp->Parameters.VerifyVolume.DeviceObject); DebugTrace( 0, Dbg, "Vpb = %08lx\n", IrpSp->Parameters.VerifyVolume.Vpb); // // Save some references to make our life a little easier. Note the Vcb for the purposes // of exception handling. // VolDo = (PVOLUME_DEVICE_OBJECT)IrpSp->Parameters.VerifyVolume.DeviceObject; Vpb = IrpSp->Parameters.VerifyVolume.Vpb; IrpContext->Vcb = Vcb = &VolDo->Vcb; // // If we cannot wait then enqueue the irp to the fsp and // return the status to our caller. // if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) { DebugTrace(0, Dbg, "Cannot wait for verify.\n", 0); Status = FatFsdPostRequest( IrpContext, Irp ); DebugTrace(-1, Dbg, "FatVerifyVolume -> %08lx\n", Status ); return Status; } // // We are serialized at this point allowing only one thread to // actually perform the verify operation. Any others will just // wait and then no-op when checking if the volume still needs // verification. // (VOID)FatAcquireExclusiveGlobal( IrpContext ); (VOID)FatAcquireExclusiveVcb( IrpContext, Vcb ); try { BOOLEAN AllowRawMount = BooleanFlagOn( IrpSp->Flags, SL_ALLOW_RAW_MOUNT ); // // Mark ourselves as verifying this volume so that recursive I/Os // will be able to complete. // ASSERT( Vcb->VerifyThread == NULL ); Vcb->VerifyThread = KeGetCurrentThread(); // // Check if the real device still needs to be verified. If it doesn't // then obviously someone beat us here and already did the work // so complete the verify irp with success. Otherwise reenable // the real device and get to work. // if (!FlagOn(Vpb->RealDevice->Flags, DO_VERIFY_VOLUME)) { DebugTrace(0, Dbg, "RealDevice has already been verified\n", 0); VerifyAlreadyDone = TRUE; try_return( Status = STATUS_SUCCESS ); } // // Ping the volume with a partition query to make Jeff happy. // { PARTITION_INFORMATION_EX PartitionInformation; (VOID) FatPerformDevIoCtrl( IrpContext, IOCTL_DISK_GET_PARTITION_INFO_EX, Vcb->TargetDeviceObject, &PartitionInformation, sizeof(PARTITION_INFORMATION_EX), FALSE, TRUE, &Iosb ); } // // Verify that there is a disk here and pick up the change count. // Status = FatPerformDevIoCtrl( IrpContext, IOCTL_DISK_CHECK_VERIFY, Vcb->TargetDeviceObject, &ChangeCount, sizeof(ULONG), FALSE, TRUE, &Iosb ); if (!NT_SUCCESS( Status )) { // // If we will allow a raw mount then return WRONG_VOLUME to // allow the volume to be mounted by raw. // if (AllowRawMount) { try_return( Status = STATUS_WRONG_VOLUME ); } FatNormalizeAndRaiseStatus( IrpContext, Status ); } if (Iosb.Information != sizeof(ULONG)) { // // Be safe about the count in case the driver didn't fill it in // ChangeCount = 0; } // // Whatever happens we will have verified this volume at this change // count, so record that fact. // Vcb->ChangeCount = ChangeCount; // // If this is a CD class device, then check to see if there is a // 'data track' or not. This is to avoid issuing paging reads which will // fail later in the mount process (e.g. CD-DA or blank CD media) // if ((Vcb->TargetDeviceObject->DeviceType == FILE_DEVICE_CD_ROM) && !FatScanForDataTrack( IrpContext, Vcb->TargetDeviceObject)) { try_return( Status = STATUS_WRONG_VOLUME); } // // Some devices can change sector sizes on the fly. Obviously, it // isn't the same volume if that happens. // Status = FatPerformDevIoCtrl( IrpContext, IOCTL_DISK_GET_DRIVE_GEOMETRY, Vcb->TargetDeviceObject, &DiskGeometry, sizeof( DISK_GEOMETRY ), FALSE, TRUE, NULL ); if (!NT_SUCCESS( Status )) { // // If we will allow a raw mount then return WRONG_VOLUME to // allow the volume to be mounted by raw. // if (AllowRawMount) { try_return( Status = STATUS_WRONG_VOLUME ); } FatNormalizeAndRaiseStatus( IrpContext, Status ); } // // Read in the boot sector // SectorSize = (ULONG)Vcb->Bpb.BytesPerSector; if (SectorSize != DiskGeometry.BytesPerSector) { try_return( Status = STATUS_WRONG_VOLUME ); } BootSector = FsRtlAllocatePoolWithTag(NonPagedPoolCacheAligned, (ULONG) ROUND_TO_PAGES( SectorSize ), TAG_VERIFY_BOOTSECTOR); // // If this verify is on behalf of a DASD open, allow a RAW mount. // if (!FatPerformVerifyDiskRead( IrpContext, Vcb, BootSector, 0, SectorSize, AllowRawMount )) { try_return( Status = STATUS_WRONG_VOLUME ); } // // Call a routine to check the boot sector to see if it is fat. // If it is not fat then mark the vcb as not mounted tell our // caller its the wrong volume // if (!FatIsBootSectorFat( BootSector )) { DebugTrace(0, Dbg, "Not a Fat Volume\n", 0); try_return( Status = STATUS_WRONG_VOLUME ); } // // This is a fat volume, so extract serial number and see if it is // ours. // { ULONG SerialNumber; if (IsBpbFat32(&BootSector->PackedBpb)) { CopyUchar4( &SerialNumber, ((PPACKED_BOOT_SECTOR_EX)BootSector)->Id ); } else { CopyUchar4( &SerialNumber, BootSector->Id ); } if (SerialNumber != Vpb->SerialNumber) { DebugTrace(0, Dbg, "Not our serial number\n", 0); try_return( Status = STATUS_WRONG_VOLUME ); } } // // Make sure the Bpbs are not different. We have to zero out our // stack version of the Bpb since unpacking leaves holes. // RtlZeroMemory( &Bpb, sizeof(BIOS_PARAMETER_BLOCK) ); FatUnpackBios( &Bpb, &BootSector->PackedBpb ); if (Bpb.Sectors != 0) { Bpb.LargeSectors = 0; } if ( !RtlEqualMemory( &Bpb, &Vcb->Bpb, IsBpbFat32(&Bpb) ? sizeof(BIOS_PARAMETER_BLOCK) : FIELD_OFFSET(BIOS_PARAMETER_BLOCK, LargeSectorsPerFat) )) { DebugTrace(0, Dbg, "Bpb is different\n", 0); try_return( Status = STATUS_WRONG_VOLUME ); } // // Check the volume label. We do this by trying to locate the // volume label, making two strings one for the saved volume label // and the other for the new volume label and then we compare the // two labels. // if (FatRootDirectorySize(&Bpb) > 0) { RootDirectorySize = FatRootDirectorySize(&Bpb); } else { RootDirectorySize = FatBytesPerCluster(&Bpb); } RootDirectory = FsRtlAllocatePoolWithTag( NonPagedPoolCacheAligned, (ULONG) ROUND_TO_PAGES( RootDirectorySize ), TAG_VERIFY_ROOTDIR); if (!IsBpbFat32(&BootSector->PackedBpb)) { // // The Fat12/16 case is simple -- read the root directory in and // search it. // RootDirectoryLbo = FatRootDirectoryLbo(&Bpb); if (!FatPerformVerifyDiskRead( IrpContext, Vcb, RootDirectory, RootDirectoryLbo, RootDirectorySize, AllowRawMount )) { try_return( Status = STATUS_WRONG_VOLUME ); } Status = FatSearchBufferForLabel(IrpContext, Vpb, RootDirectory, RootDirectorySize, &LabelFound); if (!NT_SUCCESS(Status)) { try_return( Status ); } if (!LabelFound && Vpb->VolumeLabelLength > 0) { try_return( Status = STATUS_WRONG_VOLUME ); } } else { ULONG RootDirectoryCluster; RootDirectoryCluster = Bpb.RootDirFirstCluster; while (RootDirectoryCluster != FAT_CLUSTER_LAST) { RootDirectoryLbo = FatGetLboFromIndex(Vcb, RootDirectoryCluster); if (!FatPerformVerifyDiskRead( IrpContext, Vcb, RootDirectory, RootDirectoryLbo, RootDirectorySize, AllowRawMount )) { try_return( Status = STATUS_WRONG_VOLUME ); } Status = FatSearchBufferForLabel(IrpContext, Vpb, RootDirectory, RootDirectorySize, &LabelFound); if (!NT_SUCCESS(Status)) { try_return( Status ); } if (LabelFound) { // // Found a matching label. // break; } // // Set ourselves up for the next loop iteration. // FatVerifyLookupFatEntry( IrpContext, Vcb, RootDirectoryCluster, &RootDirectoryCluster ); switch (FatInterpretClusterType(Vcb, RootDirectoryCluster)) { case FatClusterAvailable: case FatClusterReserved: case FatClusterBad: // // Bail all the way out if we have a bad root. // FatRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR ); break; default: break; } } if (RootDirectoryCluster == FAT_CLUSTER_LAST && Vpb->VolumeLabelLength > 0) { // // Should have found a label, didn't find any. // try_return( Status = STATUS_WRONG_VOLUME ); } } try_exit: NOTHING; // // Note that we have previously acquired the Vcb to serialize // the EA file stuff the marking all the Fcbs as NeedToBeVerified. // // Put the Ea file back in a virgin state. // FatCloseEaFile( IrpContext, Vcb, (BOOLEAN)(Status == STATUS_SUCCESS) ); // // Mark all Fcbs as needing verification, but only if we really have // to do it. // if (!VerifyAlreadyDone) { FatMarkFcbCondition( IrpContext, Vcb->RootDcb, FcbNeedsToBeVerified, TRUE ); } // // If the verify didn't succeed, get the volume ready for a // remount or eventual deletion. // if (Vcb->VcbCondition == VcbNotMounted) { // // If the volume was already in an unmounted state, just bail // and make sure we return STATUS_WRONG_VOLUME. // Status = STATUS_WRONG_VOLUME; } else if ( Status == STATUS_WRONG_VOLUME ) { // // Grab everything so we can safely transition the volume state without // having a thread stumble into the torn-down allocation engine. // FatAcquireExclusiveVolume( IrpContext, Vcb ); ReleaseEntireVolume = TRUE; // // Get rid of any cached data, without flushing // FatPurgeReferencedFileObjects( IrpContext, Vcb->RootDcb, NoFlush ); // // Uninitialize the volume file cache map. Note that we cannot // do a "FatSyncUninit" because of deadlock problems. However, // since this FileObject is referenced by us, and thus included // in the Vpb residual count, it is OK to do a normal CcUninit. // CcUninitializeCacheMap( Vcb->VirtualVolumeFile, &FatLargeZero, NULL ); FatTearDownAllocationSupport( IrpContext, Vcb ); Vcb->VcbCondition = VcbNotMounted; ClearVerify = TRUE; } else if (!VerifyAlreadyDone) { // // Grab everything so we can safely transition the volume state without // having a thread stumble into the torn-down allocation engine. // FatAcquireExclusiveVolume( IrpContext, Vcb ); ReleaseEntireVolume = TRUE; // // Get rid of any cached data, flushing first. // // Future work (and for bonus points, around the other flush points) // could address the possibility that the dirent filesize hasn't been // updated yet, causing us to fail the re-verification of a file in // DetermineAndMark. This is pretty subtle and very very uncommon. // FatPurgeReferencedFileObjects( IrpContext, Vcb->RootDcb, Flush ); // // Flush and Purge the volume file. // (VOID)FatFlushFat( IrpContext, Vcb ); CcPurgeCacheSection( &Vcb->SectionObjectPointers, NULL, 0, FALSE ); // // Redo the allocation support with newly paged stuff. // FatTearDownAllocationSupport( IrpContext, Vcb ); FatSetupAllocationSupport( IrpContext, Vcb ); FatCheckDirtyBit( IrpContext, Vcb ); // // Check for write protected media. // if (FatIsMediaWriteProtected(IrpContext, Vcb->TargetDeviceObject)) { SetFlag( Vcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED ); } else { ClearFlag( Vcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED ); } ClearVerify = TRUE; } if (ClearVerify) { // // Mark the device as no longer needing verification. // ClearFlag( Vpb->RealDevice->Flags, DO_VERIFY_VOLUME ); } } finally { DebugUnwind( FatVerifyVolume ); // // Free any buffer we may have allocated // if ( BootSector != NULL ) { ExFreePool( BootSector ); } if ( RootDirectory != NULL ) { ExFreePool( RootDirectory ); } // // Show that we are done with this volume. // ASSERT( Vcb->VerifyThread == KeGetCurrentThread() ); Vcb->VerifyThread = NULL; if (ReleaseEntireVolume) { FatReleaseVolume( IrpContext, Vcb ); } FatReleaseVcb( IrpContext, Vcb ); FatReleaseGlobal( IrpContext ); // // If this was not an abnormal termination, complete the irp. // if (!AbnormalTermination()) { FatCompleteRequest( IrpContext, Irp, Status ); } DebugTrace(-1, Dbg, "FatVerifyVolume -> %08lx\n", Status); } return Status; } // // Local Support Routine // BOOLEAN FatIsBootSectorFat ( IN PPACKED_BOOT_SECTOR BootSector ) /*++ Routine Description: This routine checks if the boot sector is for a fat file volume. Arguments: BootSector - Supplies the packed boot sector to check Return Value: BOOLEAN - TRUE if the volume is Fat and FALSE otherwise. --*/ { BOOLEAN Result; BIOS_PARAMETER_BLOCK Bpb; DebugTrace(+1, Dbg, "FatIsBootSectorFat, BootSector = %08lx\n", BootSector); // // The result is true unless we decide that it should be false // Result = TRUE; // // Unpack the bios and then test everything // FatUnpackBios( &Bpb, &BootSector->PackedBpb ); if (Bpb.Sectors != 0) { Bpb.LargeSectors = 0; } if ((BootSector->Jump[0] != 0xe9) && (BootSector->Jump[0] != 0xeb) && (BootSector->Jump[0] != 0x49)) { Result = FALSE; // // Enforce some sanity on the sector size (easy check) // } else if ((Bpb.BytesPerSector != 128) && (Bpb.BytesPerSector != 256) && (Bpb.BytesPerSector != 512) && (Bpb.BytesPerSector != 1024) && (Bpb.BytesPerSector != 2048) && (Bpb.BytesPerSector != 4096)) { Result = FALSE; // // Likewise on the clustering. // } else if ((Bpb.SectorsPerCluster != 1) && (Bpb.SectorsPerCluster != 2) && (Bpb.SectorsPerCluster != 4) && (Bpb.SectorsPerCluster != 8) && (Bpb.SectorsPerCluster != 16) && (Bpb.SectorsPerCluster != 32) && (Bpb.SectorsPerCluster != 64) && (Bpb.SectorsPerCluster != 128)) { Result = FALSE; // // Likewise on the reserved sectors (must reflect at least the boot sector!) // } else if (Bpb.ReservedSectors == 0) { Result = FALSE; // // No FATs? Wrong ... // } else if (Bpb.Fats == 0) { Result = FALSE; // // Prior to DOS 3.2 might contains value in both of Sectors and // Sectors Large. // } else if ((Bpb.Sectors == 0) && (Bpb.LargeSectors == 0)) { Result = FALSE; // // Check that FAT32 (SectorsPerFat == 0) claims some FAT space and // is of a version we recognize, currently Version 0.0. // } else if (Bpb.SectorsPerFat == 0 && ( Bpb.LargeSectorsPerFat == 0 || Bpb.FsVersion != 0 )) { Result = FALSE; } else if ((Bpb.Media != 0xf0) && (Bpb.Media != 0xf8) && (Bpb.Media != 0xf9) && (Bpb.Media != 0xfb) && (Bpb.Media != 0xfc) && (Bpb.Media != 0xfd) && (Bpb.Media != 0xfe) && (Bpb.Media != 0xff) && (!FatData.FujitsuFMR || ((Bpb.Media != 0x00) && (Bpb.Media != 0x01) && (Bpb.Media != 0xfa)))) { Result = FALSE; // // If this isn't FAT32, then there better be a claimed root directory // size here ... // } else if (Bpb.SectorsPerFat != 0 && Bpb.RootEntries == 0) { Result = FALSE; // // If this is FAT32 (i.e., extended BPB), look for and refuse to mount // mirror-disabled volumes. If we did, we would need to only write to // the FAT# indicated in the ActiveFat field. The only user of this is // the FAT->FAT32 converter after the first pass of protected mode work // (booting into realmode) and NT should absolutely not be attempting // to mount such an in-transition volume. // } else if (Bpb.SectorsPerFat == 0 && Bpb.MirrorDisabled) { Result = FALSE; } DebugTrace(-1, Dbg, "FatIsBootSectorFat -> %08lx\n", Result); return Result; } // // Local Support Routine // BOOLEAN FatIsMediaWriteProtected ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject ) /*++ Routine Description: This routine determines if the target media is write protected. Arguments: TargetDeviceObject - The target of the query Return Value: NTSTATUS - The return status for the operation --*/ { PIRP Irp; KEVENT Event; NTSTATUS Status; IO_STATUS_BLOCK Iosb; // // Query the partition table // KeInitializeEvent( &Event, NotificationEvent, FALSE ); // // See if the media is write protected. On success or any kind // of error (possibly illegal device function), assume it is // writeable, and only complain if he tells us he is write protected. // Irp = IoBuildDeviceIoControlRequest( IOCTL_DISK_IS_WRITABLE, TargetDeviceObject, NULL, 0, NULL, 0, FALSE, &Event, &Iosb ); // // Just return FALSE in the unlikely event we couldn't allocate an Irp. // if ( Irp == NULL ) { return FALSE; } SetFlag( IoGetNextIrpStackLocation( Irp )->Flags, SL_OVERRIDE_VERIFY_VOLUME ); Status = IoCallDriver( TargetDeviceObject, Irp ); if ( Status == STATUS_PENDING ) { (VOID) KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL ); Status = Iosb.Status; } return (BOOLEAN)(Status == STATUS_MEDIA_WRITE_PROTECTED); } // // Local Support Routine // NTSTATUS FatUserFsCtrl ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This is the common routine for implementing the user's requests made through NtFsControlFile. Arguments: Irp - Supplies the Irp being processed Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; ULONG FsControlCode; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // Save some references to make our life a little easier // FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode; DebugTrace(+1, Dbg,"FatUserFsCtrl...\n", 0); DebugTrace( 0, Dbg,"FsControlCode = %08lx\n", FsControlCode); // // Some of these Fs Controls use METHOD_NEITHER buffering. If the previous mode // of the caller was userspace and this is a METHOD_NEITHER, we have the choice // of realy buffering the request through so we can possibly post, or making the // request synchronous. Since the former was not done by design, do the latter. // if (Irp->RequestorMode != KernelMode && (FsControlCode & 3) == METHOD_NEITHER) { SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT); } // // Case on the control code. // switch ( FsControlCode ) { case FSCTL_REQUEST_OPLOCK_LEVEL_1: case FSCTL_REQUEST_OPLOCK_LEVEL_2: case FSCTL_REQUEST_BATCH_OPLOCK: case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE: case FSCTL_OPBATCH_ACK_CLOSE_PENDING: case FSCTL_OPLOCK_BREAK_NOTIFY: case FSCTL_OPLOCK_BREAK_ACK_NO_2: case FSCTL_REQUEST_FILTER_OPLOCK : Status = FatOplockRequest( IrpContext, Irp ); break; case FSCTL_LOCK_VOLUME: Status = FatLockVolume( IrpContext, Irp ); break; case FSCTL_UNLOCK_VOLUME: Status = FatUnlockVolume( IrpContext, Irp ); break; case FSCTL_DISMOUNT_VOLUME: Status = FatDismountVolume( IrpContext, Irp ); break; case FSCTL_MARK_VOLUME_DIRTY: Status = FatDirtyVolume( IrpContext, Irp ); break; case FSCTL_IS_VOLUME_DIRTY: Status = FatIsVolumeDirty( IrpContext, Irp ); break; case FSCTL_IS_VOLUME_MOUNTED: Status = FatIsVolumeMounted( IrpContext, Irp ); break; case FSCTL_IS_PATHNAME_VALID: Status = FatIsPathnameValid( IrpContext, Irp ); break; case FSCTL_QUERY_RETRIEVAL_POINTERS: Status = FatQueryRetrievalPointers( IrpContext, Irp ); break; case FSCTL_QUERY_FAT_BPB: Status = FatQueryBpb( IrpContext, Irp ); break; case FSCTL_FILESYSTEM_GET_STATISTICS: Status = FatGetStatistics( IrpContext, Irp ); break; case FSCTL_GET_VOLUME_BITMAP: Status = FatGetVolumeBitmap( IrpContext, Irp ); break; case FSCTL_GET_RETRIEVAL_POINTERS: Status = FatGetRetrievalPointers( IrpContext, Irp ); break; case FSCTL_MOVE_FILE: Status = FatMoveFile( IrpContext, Irp ); break; case FSCTL_ALLOW_EXTENDED_DASD_IO: Status = FatAllowExtendedDasdIo( IrpContext, Irp ); break; default : DebugTrace(0, Dbg, "Invalid control code -> %08lx\n", FsControlCode ); FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); Status = STATUS_INVALID_DEVICE_REQUEST; break; } DebugTrace(-1, Dbg, "FatUserFsCtrl -> %08lx\n", Status ); return Status; } // // Local support routine // NTSTATUS FatOplockRequest ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This is the common routine to handle oplock requests made via the NtFsControlFile call. Arguments: Irp - Supplies the Irp being processed Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; ULONG FsControlCode; PFCB Fcb; PVCB Vcb; PCCB Ccb; ULONG OplockCount = 0; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); BOOLEAN AcquiredVcb = FALSE; BOOLEAN AcquiredFcb = FALSE; // // Save some references to make our life a little easier // FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode; DebugTrace(+1, Dbg, "FatOplockRequest...\n", 0); DebugTrace( 0, Dbg, "FsControlCode = %08lx\n", FsControlCode); // // We only permit oplock requests on files. // if ( FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserFileOpen ) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatOplockRequest -> STATUS_INVALID_PARAMETER\n", 0); return STATUS_INVALID_PARAMETER; } // // Make this a waitable Irpcontext so we don't fail to acquire // the resources. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); // // Use a try finally to free the Fcb/Vcb // try { // // Switch on the function control code. We grab the Fcb exclusively // for oplock requests, shared for oplock break acknowledgement. // switch ( FsControlCode ) { case FSCTL_REQUEST_OPLOCK_LEVEL_1: case FSCTL_REQUEST_OPLOCK_LEVEL_2: case FSCTL_REQUEST_BATCH_OPLOCK: case FSCTL_REQUEST_FILTER_OPLOCK : FatAcquireSharedVcb( IrpContext, Fcb->Vcb ); AcquiredVcb = TRUE; FatAcquireExclusiveFcb( IrpContext, Fcb ); AcquiredFcb = TRUE; if (FsControlCode == FSCTL_REQUEST_OPLOCK_LEVEL_2) { OplockCount = (ULONG) FsRtlAreThereCurrentFileLocks( &Fcb->Specific.Fcb.FileLock ); } else { OplockCount = Fcb->UncleanCount; } break; case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE: case FSCTL_OPBATCH_ACK_CLOSE_PENDING : case FSCTL_OPLOCK_BREAK_NOTIFY: case FSCTL_OPLOCK_BREAK_ACK_NO_2: FatAcquireSharedFcb( IrpContext, Fcb ); AcquiredFcb = TRUE; break; default: FatBugCheck( FsControlCode, 0, 0 ); } // // Call the FsRtl routine to grant/acknowledge oplock. // Status = FsRtlOplockFsctrl( &Fcb->Specific.Fcb.Oplock, Irp, OplockCount ); // // Set the flag indicating if Fast I/O is possible // Fcb->Header.IsFastIoPossible = FatIsFastIoPossible( Fcb ); } finally { DebugUnwind( FatOplockRequest ); // // Release all of our resources // if (AcquiredVcb) { FatReleaseVcb( IrpContext, Fcb->Vcb ); } if (AcquiredFcb) { FatReleaseFcb( IrpContext, Fcb ); } if (!AbnormalTermination()) { FatCompleteRequest( IrpContext, FatNull, 0 ); } DebugTrace(-1, Dbg, "FatOplockRequest -> %08lx\n", Status ); } return Status; } // // Local Support Routine // NTSTATUS FatLockVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine performs the lock volume operation. It is responsible for either completing of enqueuing the input Irp. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB Fcb; PCCB Ccb; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatLockVolume...\n", 0); // // Decode the file object, the only type of opens we accept are // user volume opens. // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatLockVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatLockVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } // // Send our notification so that folks that like to hold handles on // volumes can get out of the way. // FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_LOCK ); // // Acquire exclusive access to the Vcb and enqueue the Irp if we // didn't get access. // if (!FatAcquireExclusiveVcb( IrpContext, Vcb )) { DebugTrace( 0, Dbg, "Cannot acquire Vcb\n", 0); Status = FatFsdPostRequest( IrpContext, Irp ); DebugTrace(-1, Dbg, "FatUnlockVolume -> %08lx\n", Status); return Status; } try { Status = FatLockVolumeInternal( IrpContext, Vcb, IrpSp->FileObject ); } finally { // // Since we drop and release the vcb while trying to punch the volume // down, it may be the case that we decide the operation should not // continue if the user raced a CloeseHandle() with us (and it finished // the cleanup) while we were waiting for our closes to finish. // // In this case, we will have been raised out of the acquire logic with // STATUS_FILE_CLOSED, and the volume will not be held. // if (!AbnormalTermination() || ExIsResourceAcquiredExclusiveLite( &Vcb->Resource )) { FatReleaseVcb( IrpContext, Vcb ); } if (!NT_SUCCESS( Status ) || AbnormalTermination()) { // // The volume lock will be failing. // FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_LOCK_FAILED ); } } FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatLockVolume -> %08lx\n", Status); return Status; } // // Local Support Routine // NTSTATUS FatUnlockVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine performs the unlock volume operation. It is responsible for either completing of enqueuing the input Irp. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB Fcb; PCCB Ccb; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatUnlockVolume...\n", 0); // // Decode the file object, the only type of opens we accept are // user volume opens. // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatUnlockVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatUnlockVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } Status = FatUnlockVolumeInternal( IrpContext, Vcb, IrpSp->FileObject ); // // Send notification that the volume is avaliable. // if (NT_SUCCESS( Status )) { FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_UNLOCK ); } FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatUnlockVolume -> %08lx\n", Status); return Status; } NTSTATUS FatLockVolumeInternal ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PFILE_OBJECT FileObject OPTIONAL ) /*++ Routine Description: This routine performs the actual lock volume operation. It will be called by anyone wishing to try to protect the volume for a long duration. PNP operations are such a user. The volume must be held exclusive by the caller. Arguments: Vcb - The volume being locked. FileObject - File corresponding to the handle locking the volume. If this is not specified, a system lock is assumed. Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status = STATUS_SUCCESS; KIRQL SavedIrql; ULONG RemainingUserReferences = (FileObject? 1: 0); ASSERT( ExIsResourceAcquiredExclusiveLite( &Vcb->Resource ) && !ExIsResourceAcquiredExclusiveLite( &FatData.Resource )); // // Go synchronous for the rest of the lock operation. It may be // reasonable to try to revisit this in the future, but for now // the purge below expects to be able to wait. // // We know it is OK to leave the flag up given how we're used at // the moment. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); // // If there are any open handles, this will fail. // if (!FatIsHandleCountZero( IrpContext, Vcb )) { return STATUS_ACCESS_DENIED; } // // Force Mm to get rid of its referenced file objects. // FatFlushFat( IrpContext, Vcb ); FatPurgeReferencedFileObjects( IrpContext, Vcb->RootDcb, Flush ); FatCloseEaFile( IrpContext, Vcb, TRUE ); // // Now back out of our synchronization and wait for the lazy writer // to finish off any lazy closes that could have been outstanding. // // Since we flushed, we know that the lazy writer will issue all // possible lazy closes in the next tick - if we hadn't, an otherwise // unopened file with a large amount of dirty data could have hung // around for a while as the data trickled out to the disk. // // This is even more important now since we send notification to // alert other folks that this style of check is about to happen so // that they can close their handles. We don't want to enter a fast // race with the lazy writer tearing down his references to the file. // FatReleaseVcb( IrpContext, Vcb ); Status = CcWaitForCurrentLazyWriterActivity(); FatAcquireExclusiveVcb( IrpContext, Vcb ); if (!NT_SUCCESS( Status )) { return Status; } // // Now rundown the delayed closes one last time. We appear to be able // to have additional collisions. // FatFspClose( Vcb ); // // Check if the Vcb is already locked, or if the open file count // is greater than 1 (which implies that someone else also is // currently using the volume, or a file on the volume), and that the // VPB reference count only includes our residual and the handle (as // appropriate). // // We used to only check for the vpb refcount. This is unreliable since // the vpb refcount is dropped immediately before final close, meaning // that even though we had a good refcount, the close was inflight and // subsequent operations could get confused. Especially if the PNP path // was the lock caller, we delete the VCB with an outstanding opencount! // IoAcquireVpbSpinLock( &SavedIrql ); if (!FlagOn(Vcb->Vpb->Flags, VPB_LOCKED) && (Vcb->Vpb->ReferenceCount <= 2 + RemainingUserReferences) && (Vcb->OpenFileCount == (CLONG)( FileObject? 1: 0 ))) { SetFlag(Vcb->Vpb->Flags, VPB_LOCKED); SetFlag(Vcb->VcbState, VCB_STATE_FLAG_LOCKED); Vcb->FileObjectWithVcbLocked = FileObject; } else { Status = STATUS_ACCESS_DENIED; } IoReleaseVpbSpinLock( SavedIrql ); // // If we successully locked the volume, see if it is clean now. // if (NT_SUCCESS( Status ) && FlagOn( Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DIRTY ) && !FlagOn( Vcb->VcbState, VCB_STATE_FLAG_MOUNTED_DIRTY ) && !CcIsThereDirtyData(Vcb->Vpb)) { FatMarkVolume( IrpContext, Vcb, VolumeClean ); ClearFlag( Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DIRTY ); } ASSERT( !NT_SUCCESS(Status) || (Vcb->OpenFileCount == (CLONG)( FileObject? 1: 0 ))); return Status; } NTSTATUS FatUnlockVolumeInternal ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PFILE_OBJECT FileObject OPTIONAL ) /*++ Routine Description: This routine performs the actual unlock volume operation. The volume must be held exclusive by the caller. Arguments: Vcb - The volume being locked. FileObject - File corresponding to the handle locking the volume. If this is not specified, a system lock is assumed. Return Value: NTSTATUS - The return status for the operation Attempting to remove a system lock that did not exist is OK. --*/ { KIRQL SavedIrql; NTSTATUS Status = STATUS_NOT_LOCKED; IoAcquireVpbSpinLock( &SavedIrql ); if (FlagOn(Vcb->Vpb->Flags, VPB_LOCKED) && FileObject == Vcb->FileObjectWithVcbLocked) { // // This one locked it, unlock the volume // ClearFlag( Vcb->Vpb->Flags, VPB_LOCKED ); ClearFlag( Vcb->VcbState, VCB_STATE_FLAG_LOCKED ); Vcb->FileObjectWithVcbLocked = NULL; Status = STATUS_SUCCESS; } IoReleaseVpbSpinLock( SavedIrql ); return Status; } // // Local Support Routine // NTSTATUS FatDismountVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine performs the dismount volume operation. It is responsible for either completing of enqueuing the input Irp. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { PIO_STACK_LOCATION IrpSp; NTSTATUS Status; BOOLEAN VcbHeld = FALSE; PVCB Vcb; PFCB Fcb; PCCB Ccb; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatDismountVolume...\n", 0); // // Decode the file object, the only type of opens we accept are // user volume opens on media that is not boot/paging and is not // already dismounted ... (but we need to check that stuff while // synchronized) // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserVolumeOpen) { Status = STATUS_INVALID_PARAMETER; goto fn_return; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatDismountVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } // // Make some unsynchronized checks to see if this operation is possible. // We will repeat the appropriate ones inside synchronization, but it is // good to avoid bogus notifications. // if (FlagOn( Vcb->VcbState, VCB_STATE_FLAG_BOOT_OR_PAGING_FILE )) { Status = STATUS_ACCESS_DENIED; goto fn_return; } if (FlagOn( Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DISMOUNTED )) { Status = STATUS_VOLUME_DISMOUNTED; goto fn_return; } // // A bit of historical comment is in order. // // In all versions prior to NT5, we only permitted dismount if the volume had // previously been locked. Now we must permit a forced dismount, meaning that // we grab ahold of the whole kit-n-kaboodle - regardless of activity, open // handles, etc. - to flush and invalidate the volume. // // Previously, dismount assumed that lock had come along earlier and done some // of the work that we are now going to do - i.e., flush, tear down the eas. All // we had to do here is flush the device out and kill off as many of the orphan // fcbs as possible. This now changes. // // In fact, everything is a forced dismount now. This changes one interesting // aspect, which is that it used to be the case that the handle used to dismount // could come back, read, and induce a verify/remount. This is just not possible // now. The point of forced dismount is that very shortly someone will come along // and be destructive to the possibility of using the media further - format, eject, // etc. By using this path, callers are expected to tolerate the consequences. // // Note that the volume can still be successfully unlocked by this handle. // // // Send notification. // FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_DISMOUNT ); // // Force ourselves to wait and grab everything. // SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT); (VOID)FatAcquireExclusiveGlobal( IrpContext ); try { // // Guess what? This can raise if a cleanup on the fileobject we // got races in ahead of us. // FatAcquireExclusiveVolume( IrpContext, Vcb ); VcbHeld = TRUE; if (FlagOn( Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DISMOUNTED )) { try_return( Status = STATUS_VOLUME_DISMOUNTED ); } FatFlushAndCleanVolume( IrpContext, Irp, Vcb, FlushAndInvalidate ); // // We defer the physical dismount until this handle is closed, per symmetric // implemntation in the other FS. This permits a dismounter to issue IOCTL // through this handle and perform device manipulation without racing with // creates attempting to mount the volume again. // // Raise a flag to tell the cleanup path to complete the dismount. // SetFlag( Ccb->Flags, CCB_FLAG_COMPLETE_DISMOUNT ); // // Indicate that the volume was dismounted so that we may return the // correct error code when operations are attempted via open handles. // Vcb->VcbCondition = VcbBad; SetFlag( Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DISMOUNTED ); Status = STATUS_SUCCESS; try_exit: NOTHING; } finally { if (VcbHeld) { FatReleaseVolume( IrpContext, Vcb ); } FatReleaseGlobal( IrpContext ); // // I do not believe it is possible to raise, but for completeness // notice and send notification of failure. We absolutely // cannot have raised in CheckForDismount. // // We decline to call an attempt to dismount a dismounted volume // a failure to do so. // if ((!NT_SUCCESS( Status ) && Status != STATUS_VOLUME_DISMOUNTED) || AbnormalTermination()) { FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_DISMOUNT_FAILED ); } } fn_return: FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatDismountVolume -> %08lx\n", Status); return Status; } // // Local Support Routine // NTSTATUS FatDirtyVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine marks the volume as dirty. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB Fcb; PCCB Ccb; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatDirtyVolume...\n", 0); // // Decode the file object, the only type of opens we accept are // user volume opens. // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatDirtyVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatDirtyVolume -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } // // Disable popups, we will just return any error. // SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_DISABLE_POPUPS); // // Verify the Vcb. We want to make sure we don't dirty some // random chunk of media that happens to be in the drive now. // FatVerifyVcb( IrpContext, Vcb ); SetFlag( Vcb->VcbState, VCB_STATE_FLAG_MOUNTED_DIRTY ); FatMarkVolume( IrpContext, Vcb, VolumeDirty ); FatCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); DebugTrace(-1, Dbg, "FatDirtyVolume -> STATUS_SUCCESS\n", 0); return STATUS_SUCCESS; } // // Local Support Routine // NTSTATUS FatIsVolumeDirty ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine determines if a volume is currently dirty. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { PIO_STACK_LOCATION IrpSp; TYPE_OF_OPEN TypeOfOpen; PVCB Vcb; PFCB Fcb; PCCB Ccb; PULONG VolumeState; // // Get the current stack location and extract the output // buffer information. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // Get a pointer to the output buffer. Look at the system buffer field in the // irp first. Then the Irp Mdl. // if (Irp->AssociatedIrp.SystemBuffer != NULL) { VolumeState = Irp->AssociatedIrp.SystemBuffer; } else if (Irp->MdlAddress != NULL) { VolumeState = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, LowPagePriority ); if (VolumeState == NULL) { FatCompleteRequest( IrpContext, Irp, STATUS_INSUFFICIENT_RESOURCES ); return STATUS_INSUFFICIENT_RESOURCES; } } else { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_USER_BUFFER ); return STATUS_INVALID_USER_BUFFER; } // // Make sure the output buffer is large enough and then initialize // the answer to be that the volume isn't dirty. // if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(ULONG)) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } *VolumeState = 0; // // Decode the file object // TypeOfOpen = FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ); if (TypeOfOpen != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } if (Vcb->VcbCondition != VcbGood) { FatCompleteRequest( IrpContext, Irp, STATUS_VOLUME_DISMOUNTED ); return STATUS_VOLUME_DISMOUNTED; } // // Disable PopUps, we want to return any error. // SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_DISABLE_POPUPS); // // Verify the Vcb. We want to make double sure that this volume // is around so that we know our information is good. // FatVerifyVcb( IrpContext, Vcb ); // // Now set the returned information. We can avoid probing the disk since // we know our internal state is in sync. // if ( FlagOn(Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DIRTY | VCB_STATE_FLAG_MOUNTED_DIRTY) ) { SetFlag( *VolumeState, VOLUME_IS_DIRTY ); } Irp->IoStatus.Information = sizeof( ULONG ); FatCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } // // Local Support Routine // NTSTATUS FatIsVolumeMounted ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine determines if a volume is currently mounted. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PVCB Vcb = NULL; PFCB Fcb; PCCB Ccb; IrpSp = IoGetCurrentIrpStackLocation( Irp ); Status = STATUS_SUCCESS; DebugTrace(+1, Dbg, "FatIsVolumeMounted...\n", 0); // // Decode the file object. // (VOID)FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ); ASSERT( Vcb != NULL ); // // Disable PopUps, we want to return any error. // SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_DISABLE_POPUPS); // // Verify the Vcb. // FatVerifyVcb( IrpContext, Vcb ); FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatIsVolumeMounted -> %08lx\n", Status); return Status; } // // Local Support Routine // NTSTATUS FatIsPathnameValid ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine determines if a pathname is a-priori illegal by inspecting the the characters used. It is required to be correct on a FALSE return. N.B.: current implementation is intentioanlly a no-op. This may change in the future. A careful reader of the previous implementation of this FSCTL in FAT would discover that it violated the requirement stated above and could return FALSE for a valid (createable) pathname. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { DebugTrace(+1, Dbg, "FatIsPathnameValid...\n", 0); FatCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); DebugTrace(-1, Dbg, "FatIsPathnameValid -> %08lx\n", STATUS_SUCCESS); return STATUS_SUCCESS; } // // Local Support Routine // NTSTATUS FatQueryBpb ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine simply returns the first 0x24 bytes of sector 0. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFSCTL_QUERY_FAT_BPB_BUFFER BpbBuffer; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatQueryBpb...\n", 0); // // Get the Vcb. If we didn't keep the information needed for this call, // we had a reason ... // Vcb = &((PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject)->Vcb; if (Vcb->First0x24BytesOfBootSector == NULL) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); DebugTrace(-1, Dbg, "FatQueryBpb -> %08lx\n", STATUS_INVALID_DEVICE_REQUEST ); return STATUS_INVALID_DEVICE_REQUEST; } // // Extract the buffer // BpbBuffer = (PFSCTL_QUERY_FAT_BPB_BUFFER)Irp->AssociatedIrp.SystemBuffer; // // Make sure the buffer is big enough. // if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < 0x24) { FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL ); DebugTrace(-1, Dbg, "FatQueryBpb -> %08lx\n", STATUS_BUFFER_TOO_SMALL ); return STATUS_BUFFER_TOO_SMALL; } // // Fill in the output buffer // RtlCopyMemory( BpbBuffer->First0x24BytesOfBootSector, Vcb->First0x24BytesOfBootSector, 0x24 ); Irp->IoStatus.Information = 0x24; FatCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); DebugTrace(-1, Dbg, "FatQueryBpb -> %08lx\n", STATUS_SUCCESS); return STATUS_SUCCESS; } // // Local Support Routine // NTSTATUS FatInvalidateVolumes ( IN PIRP Irp ) /*++ Routine Description: This routine searches for all the volumes mounted on the same real device of the current DASD handle, and marks them all bad. The only operation that can be done on such handles is cleanup and close. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; IRP_CONTEXT IrpContext; PIO_STACK_LOCATION IrpSp; LUID TcbPrivilege = {SE_TCB_PRIVILEGE, 0}; HANDLE Handle; PLIST_ENTRY Links; PFILE_OBJECT FileToMarkBad; PDEVICE_OBJECT DeviceToMarkBad; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatInvalidateVolumes...\n", 0); // // Check for the correct security access. // The caller must have the SeTcbPrivilege. // if (!SeSinglePrivilegeCheck(TcbPrivilege, Irp->RequestorMode)) { FatCompleteRequest( FatNull, Irp, STATUS_PRIVILEGE_NOT_HELD ); DebugTrace(-1, Dbg, "FatInvalidateVolumes -> %08lx\n", STATUS_PRIVILEGE_NOT_HELD); return STATUS_PRIVILEGE_NOT_HELD; } // // Try to get a pointer to the device object from the handle passed in. // #if defined(_WIN64) if (IoIs32bitProcess( Irp )) { if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(UINT32)) { FatCompleteRequest( FatNull, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatInvalidateVolumes -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } Handle = (HANDLE) LongToHandle( (*(PUINT32)Irp->AssociatedIrp.SystemBuffer) ); } else { #endif if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(HANDLE)) { FatCompleteRequest( FatNull, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatInvalidateVolumes -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } Handle = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer; #if defined(_WIN64) } #endif Status = ObReferenceObjectByHandle( Handle, 0, *IoFileObjectType, KernelMode, &FileToMarkBad, NULL ); if (!NT_SUCCESS(Status)) { FatCompleteRequest( FatNull, Irp, Status ); DebugTrace(-1, Dbg, "FatInvalidateVolumes -> %08lx\n", Status); return Status; } else { // // We only needed the pointer, not a reference. // ObDereferenceObject( FileToMarkBad ); // // Grab the DeviceObject from the FileObject. // DeviceToMarkBad = FileToMarkBad->DeviceObject; } RtlZeroMemory( &IrpContext, sizeof(IRP_CONTEXT) ); SetFlag( IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT ); IrpContext.MajorFunction = IrpSp->MajorFunction; IrpContext.MinorFunction = IrpSp->MinorFunction; FatAcquireExclusiveGlobal( &IrpContext ); // // First acquire the FatData resource shared, then walk through all the // mounted VCBs looking for candidates to mark BAD. // // On volumes we mark bad, check for dismount possibility (which is // why we have to get the next link early). // Links = FatData.VcbQueue.Flink; while (Links != &FatData.VcbQueue) { PVCB ExistingVcb; ExistingVcb = CONTAINING_RECORD(Links, VCB, VcbLinks); Links = Links->Flink; // // If we get a match, mark the volume Bad, and also check to // see if the volume should go away. // if (ExistingVcb->Vpb->RealDevice == DeviceToMarkBad) { // // Here we acquire the Vcb exclusive and try to purge // all the open files. The idea is to have as little as // possible stale data visible and to hasten the volume // going away. // (VOID)FatAcquireExclusiveVcb( &IrpContext, ExistingVcb ); if (ExistingVcb->Vpb == DeviceToMarkBad->Vpb) { KIRQL OldIrql; IoAcquireVpbSpinLock( &OldIrql ); if (FlagOn( DeviceToMarkBad->Vpb->Flags, VPB_MOUNTED )) { PVPB NewVpb; NewVpb = ExistingVcb->SwapVpb; ExistingVcb->SwapVpb = NULL; RtlZeroMemory( NewVpb, sizeof( VPB ) ); NewVpb->Type = IO_TYPE_VPB; NewVpb->Size = sizeof( VPB ); NewVpb->RealDevice = DeviceToMarkBad; NewVpb->Flags = FlagOn( DeviceToMarkBad->Vpb->Flags, VPB_REMOVE_PENDING ); DeviceToMarkBad->Vpb = NewVpb; } ASSERT( DeviceToMarkBad->Vpb->DeviceObject == NULL ); IoReleaseVpbSpinLock( OldIrql ); } ExistingVcb->VcbCondition = VcbBad; FatMarkFcbCondition( &IrpContext, ExistingVcb->RootDcb, FcbBad, TRUE ); FatPurgeReferencedFileObjects( &IrpContext, ExistingVcb->RootDcb, NoFlush ); // // If the volume was not deleted, drop the resource. // if (Links->Blink == &ExistingVcb->VcbLinks) { PVPB Vpb; FatReleaseVcb( &IrpContext, ExistingVcb ); // // If the volume does go away now, then we have to free // up the Vpb as nobody else will. // Vpb = ExistingVcb->Vpb; if (FatCheckForDismount( &IrpContext, ExistingVcb, FALSE )) { ExFreePool( Vpb ); } } } } FatReleaseGlobal( &IrpContext ); FatCompleteRequest( FatNull, Irp, STATUS_SUCCESS ); DebugTrace(-1, Dbg, "FatInvalidateVolumes -> STATUS_SUCCESS\n", 0); return STATUS_SUCCESS; } // // Local Support routine // BOOLEAN FatPerformVerifyDiskRead ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PVOID Buffer, IN LBO Lbo, IN ULONG NumberOfBytesToRead, IN BOOLEAN ReturnOnError ) /*++ Routine Description: This routine is used to read in a range of bytes from the disk. It bypasses all of the caching and regular I/O logic, and builds and issues the requests itself. It does this operation overriding the verify volume flag in the device object. Arguments: Vcb - Supplies the target device object for this operation. Buffer - Supplies the buffer that will recieve the results of this operation Lbo - Supplies the byte offset of where to start reading NumberOfBytesToRead - Supplies the number of bytes to read, this must be in multiple of bytes units acceptable to the disk driver. ReturnOnError - Indicates that we should return on an error, instead of raising. Return Value: BOOLEAN - TRUE if the operation succeded, FALSE otherwise. --*/ { KEVENT Event; PIRP Irp; LARGE_INTEGER ByteOffset; NTSTATUS Status; IO_STATUS_BLOCK Iosb; DebugTrace(0, Dbg, "FatPerformVerifyDiskRead, Lbo = %08lx\n", Lbo ); // // Initialize the event we're going to use // KeInitializeEvent( &Event, NotificationEvent, FALSE ); // // Build the irp for the operation and also set the overrride flag // ByteOffset.QuadPart = Lbo; Irp = IoBuildSynchronousFsdRequest( IRP_MJ_READ, Vcb->TargetDeviceObject, Buffer, NumberOfBytesToRead, &ByteOffset, &Event, &Iosb ); if ( Irp == NULL ) { FatRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES ); } SetFlag( IoGetNextIrpStackLocation( Irp )->Flags, SL_OVERRIDE_VERIFY_VOLUME ); // // Call the device to do the read and wait for it to finish. // Status = IoCallDriver( Vcb->TargetDeviceObject, Irp ); if (Status == STATUS_PENDING) { (VOID)KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL ); Status = Iosb.Status; } ASSERT( Status != STATUS_VERIFY_REQUIRED ); // // Special case this error code because this probably means we used // the wrong sector size and we want to reject STATUS_WRONG_VOLUME. // if (Status == STATUS_INVALID_PARAMETER) { return FALSE; } // // If it doesn't succeed then either return or raise the error. // if (!NT_SUCCESS(Status)) { if (ReturnOnError) { return FALSE; } else { FatNormalizeAndRaiseStatus( IrpContext, Status ); } } // // And return to our caller // return TRUE; } // // Local Support Routine // NTSTATUS FatQueryRetrievalPointers ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine performs the query retrieval pointers operation. It returns the retrieval pointers for the specified input file from the start of the file to the request map size specified in the input buffer. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB Fcb; PCCB Ccb; PLARGE_INTEGER RequestedMapSize; PLARGE_INTEGER *MappingPairs; ULONG Index; ULONG i; ULONG SectorCount; LBO Lbo; ULONG Vbo; ULONG MapSize; // // Get the current stack location // IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // Decode the file object and ensure that it is the paging file // // Only Kernel mode clients may query retrieval pointer information about // a file. Ensure that this is the case for this caller. // (VOID)FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ); if (Irp->RequestorMode != KernelMode || Fcb == NULL || !FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) ) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } // // Extract the input and output buffer information. The input contains // the requested size of the mappings in terms of VBO. The output // parameter will receive a pointer to nonpaged pool where the mapping // pairs are stored. // ASSERT( IrpSp->Parameters.FileSystemControl.InputBufferLength == sizeof(LARGE_INTEGER) ); ASSERT( IrpSp->Parameters.FileSystemControl.OutputBufferLength == sizeof(PVOID) ); RequestedMapSize = IrpSp->Parameters.FileSystemControl.Type3InputBuffer; MappingPairs = Irp->UserBuffer; // // Acquire exclusive access to the Fcb // if (!FatAcquireExclusiveFcb( IrpContext, Fcb )) { return FatFsdPostRequest( IrpContext, Irp ); } try { // // Verify the Fcb is still OK // FatVerifyFcb( IrpContext, Fcb ); // // Check if the mapping the caller requested is too large // if ((*RequestedMapSize).QuadPart > Fcb->Header.FileSize.QuadPart) { try_return( Status = STATUS_INVALID_PARAMETER ); } // // Now get the index for the mcb entry that will contain the // callers request and allocate enough pool to hold the // output mapping pairs // (VOID)FatLookupMcbEntry( Fcb->Vcb, &Fcb->Mcb, RequestedMapSize->LowPart - 1, &Lbo, NULL, &Index ); *MappingPairs = FsRtlAllocatePoolWithTag( NonPagedPool, (Index + 2) * (2 * sizeof(LARGE_INTEGER)), TAG_OUTPUT_MAPPINGPAIRS ); // // Now copy over the mapping pairs from the mcb // to the output buffer. We store in [sector count, lbo] // mapping pairs and end with a zero sector count. // MapSize = RequestedMapSize->LowPart; for (i = 0; i <= Index; i += 1) { (VOID)FatGetNextMcbEntry( Fcb->Vcb, &Fcb->Mcb, i, &Vbo, &Lbo, &SectorCount ); if (SectorCount > MapSize) { SectorCount = MapSize; } (*MappingPairs)[ i*2 + 0 ].QuadPart = SectorCount; (*MappingPairs)[ i*2 + 1 ].QuadPart = Lbo; MapSize -= SectorCount; } (*MappingPairs)[ i*2 + 0 ].QuadPart = 0; Status = STATUS_SUCCESS; try_exit: NOTHING; } finally { DebugUnwind( FatQueryRetrievalPointers ); // // Release all of our resources // FatReleaseFcb( IrpContext, Fcb ); // // If this is an abnormal termination then undo our work, otherwise // complete the irp // if (!AbnormalTermination()) { FatCompleteRequest( IrpContext, Irp, Status ); } } return Status; } // // Local Support Routine // NTSTATUS FatGetStatistics ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine returns the filesystem performance counters from the appropriate VCB. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { PIO_STACK_LOCATION IrpSp; NTSTATUS Status; PVCB Vcb; PFILE_SYSTEM_STATISTICS Buffer; ULONG BufferLength; ULONG StatsSize; ULONG BytesToCopy; IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatGetStatistics...\n", 0); // // Extract the buffer // Buffer = Irp->AssociatedIrp.SystemBuffer; BufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength; // // Get a pointer to the output buffer. // Buffer = Irp->AssociatedIrp.SystemBuffer; // // Make sure the buffer is big enough for at least the common part. // if (BufferLength < sizeof(FILESYSTEM_STATISTICS)) { FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL ); DebugTrace(-1, Dbg, "FatGetStatistics -> %08lx\n", STATUS_BUFFER_TOO_SMALL ); return STATUS_BUFFER_TOO_SMALL; } // // Now see how many bytes we can copy. // StatsSize = sizeof(FILE_SYSTEM_STATISTICS) * KeNumberProcessors; if (BufferLength < StatsSize) { BytesToCopy = BufferLength; Status = STATUS_BUFFER_OVERFLOW; } else { BytesToCopy = StatsSize; Status = STATUS_SUCCESS; } // // Get the Vcb. // Vcb = &((PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject)->Vcb; // // Fill in the output buffer // RtlCopyMemory( Buffer, Vcb->Statistics, BytesToCopy ); Irp->IoStatus.Information = BytesToCopy; FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatGetStatistics -> %08lx\n", Status); return Status; } // // Local Support Routine // NTSTATUS FatGetVolumeBitmap( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine returns the volume allocation bitmap. Input = the STARTING_LCN_INPUT_BUFFER data structure is passed in through the input buffer. Output = the VOLUME_BITMAP_BUFFER data structure is returned through the output buffer. We return as much as the user buffer allows starting the specified input LCN (trucated to a byte). If there is no input buffer, we start at zero. Arguments: Irp - Supplies the Irp being processed. Return Value: NTSTATUS - The return status for the operation. --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB Fcb; PCCB Ccb; ULONG BytesToCopy; ULONG TotalClusters; ULONG DesiredClusters; ULONG StartingCluster; ULONG InputBufferLength; ULONG OutputBufferLength; LARGE_INTEGER StartingLcn; PVOLUME_BITMAP_BUFFER OutputBuffer; // // Get the current Irp stack location and save some references. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatGetVolumeBitmap, FsControlCode = %08lx\n", IrpSp->Parameters.FileSystemControl.FsControlCode); // // Extract and decode the file object and check for type of open. // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatGetVolumeBitmap -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } InputBufferLength = IrpSp->Parameters.FileSystemControl.InputBufferLength; OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength; OutputBuffer = (PVOLUME_BITMAP_BUFFER)FatMapUserBuffer( IrpContext, Irp ); // // Check for a minimum length on the input and output buffers. // if ((InputBufferLength < sizeof(STARTING_LCN_INPUT_BUFFER)) || (OutputBufferLength < sizeof(VOLUME_BITMAP_BUFFER))) { FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL ); return STATUS_BUFFER_TOO_SMALL; } // // Check if a starting cluster was specified. // TotalClusters = Vcb->AllocationSupport.NumberOfClusters; // // Check for valid buffers // try { if (Irp->RequestorMode != KernelMode) { ProbeForRead( IrpSp->Parameters.FileSystemControl.Type3InputBuffer, InputBufferLength, sizeof(UCHAR) ); ProbeForWrite( OutputBuffer, OutputBufferLength, sizeof(UCHAR) ); } StartingLcn = ((PSTARTING_LCN_INPUT_BUFFER)IrpSp->Parameters.FileSystemControl.Type3InputBuffer)->StartingLcn; } except( Irp->RequestorMode != KernelMode ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH ) { Status = GetExceptionCode(); FatRaiseStatus( IrpContext, FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER ); } if (StartingLcn.HighPart || StartingLcn.LowPart >= TotalClusters) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } else { StartingCluster = StartingLcn.LowPart & ~7; } // // Acquire exclusive access to the Vcb and enqueue the Irp if we // didn't get access. // if (!FatAcquireExclusiveVcb( IrpContext, Vcb )) { DebugTrace( 0, Dbg, "Cannot acquire Vcb\n", 0); ASSERT( Irp->RequestorMode == KernelMode ); Status = FatFsdPostRequest( IrpContext, Irp ); DebugTrace(-1, Dbg, "FatGetVolumeBitmap -> %08lx\n", Status); return Status; } // // Only return what will fit in the user buffer. // OutputBufferLength -= FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer); DesiredClusters = TotalClusters - StartingCluster; if (OutputBufferLength < (DesiredClusters + 7) / 8) { BytesToCopy = OutputBufferLength; Status = STATUS_BUFFER_OVERFLOW; } else { BytesToCopy = (DesiredClusters + 7) / 8; Status = STATUS_SUCCESS; } // // Use try/finally for cleanup. // try { try { // // Verify the Vcb is still OK // FatQuickVerifyVcb( IrpContext, Vcb ); // // Fill in the fixed part of the output buffer // OutputBuffer->StartingLcn.QuadPart = StartingCluster; OutputBuffer->BitmapSize.QuadPart = DesiredClusters; if (Vcb->NumberOfWindows == 1) { // // Just copy the volume bitmap into the user buffer. // ASSERT( Vcb->FreeClusterBitMap.Buffer != NULL ); RtlCopyMemory( &OutputBuffer->Buffer[0], (PUCHAR)Vcb->FreeClusterBitMap.Buffer + StartingCluster/8, BytesToCopy ); } else { // // Call out to analyze the FAT. We must bias by two to account for // the zero base of this API and FAT's physical reality of starting // the file heap at cluster 2. // // Note that the end index is inclusive - we need to subtract one to // calculcate it. // // I.e.: StartingCluster 0 for one byte of bitmap means a start cluster // of 2 and end cluster of 9, a run of eight clusters. // FatExamineFatEntries( IrpContext, Vcb, StartingCluster + 2, StartingCluster + BytesToCopy * 8 + 2 - 1, FALSE, NULL, (PULONG)&OutputBuffer->Buffer[0] ); } } except( Irp->RequestorMode != KernelMode ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH ) { Status = GetExceptionCode(); FatRaiseStatus( IrpContext, FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER ); } } finally { FatReleaseVcb( IrpContext, Vcb ); } Irp->IoStatus.Information = FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer) + BytesToCopy; FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatGetVolumeBitmap -> VOID\n", 0); return Status; } // // Local Support Routine // NTSTATUS FatGetRetrievalPointers ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine scans the MCB and builds an extent list. The first run in the output extent list will start at the begining of the contiguous run specified by the input parameter. Input = STARTING_VCN_INPUT_BUFFER; Output = RETRIEVAL_POINTERS_BUFFER. Arguments: Irp - Supplies the Irp being processed. Return Value: NTSTATUS - The return status for the operation. --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB FcbOrDcb; PCCB Ccb; TYPE_OF_OPEN TypeOfOpen; ULONG Index; ULONG ClusterShift; ULONG AllocationSize; ULONG Run; ULONG RunCount; ULONG StartingRun; LARGE_INTEGER StartingVcn; ULONG InputBufferLength; ULONG OutputBufferLength; PRETRIEVAL_POINTERS_BUFFER OutputBuffer; BOOLEAN FcbLocked; // // Get the current Irp stack location and save some references. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatGetRetrievalPointers, FsControlCode = %08lx\n", IrpSp->Parameters.FileSystemControl.FsControlCode); // // Extract and decode the file object and check for type of open. // TypeOfOpen = FatDecodeFileObject( IrpSp->FileObject, &Vcb, &FcbOrDcb, &Ccb ); if ((TypeOfOpen != UserFileOpen) && (TypeOfOpen != UserDirectoryOpen)) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } // // Get the input and output buffer lengths and pointers. // Initialize some variables. // InputBufferLength = IrpSp->Parameters.FileSystemControl.InputBufferLength; OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength; OutputBuffer = (PRETRIEVAL_POINTERS_BUFFER)FatMapUserBuffer( IrpContext, Irp ); // // Check for a minimum length on the input and ouput buffers. // if ((InputBufferLength < sizeof(STARTING_VCN_INPUT_BUFFER)) || (OutputBufferLength < sizeof(RETRIEVAL_POINTERS_BUFFER))) { FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL ); return STATUS_BUFFER_TOO_SMALL; } // // Acquire the Fcb and enqueue the Irp if we didn't get access. Go for // shared on read-only media so we can allow prototype XIP to get // recursive, as well as recognizing this is safe anyway. // if (FlagOn( FcbOrDcb->Vcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED )) { FcbLocked = FatAcquireSharedFcb( IrpContext, FcbOrDcb ); } else { FcbLocked = FatAcquireExclusiveFcb( IrpContext, FcbOrDcb ); } if (!FcbLocked) { DebugTrace( 0, Dbg, "Cannot acquire Vcb\n", 0); ASSERT( Irp->RequestorMode == KernelMode ); Status = FatFsdPostRequest( IrpContext, Irp ); DebugTrace(-1, Dbg, "FatGetRetrievalPointers -> %08lx\n", Status); return Status; } try { // // Verify the Fcb is still OK // FatVerifyFcb( IrpContext, FcbOrDcb ); // // If we haven't yet set the correct AllocationSize, do so. // if (FcbOrDcb->Header.AllocationSize.QuadPart == FCB_LOOKUP_ALLOCATIONSIZE_HINT) { FatLookupFileAllocationSize( IrpContext, FcbOrDcb ); // // If this is a non-root directory, we have a bit more to // do since it has not gone through FatOpenDirectoryFile(). // if (NodeType(FcbOrDcb) == FAT_NTC_DCB || (NodeType(FcbOrDcb) == FAT_NTC_ROOT_DCB && FatIsFat32(Vcb))) { FcbOrDcb->Header.FileSize.LowPart = FcbOrDcb->Header.AllocationSize.LowPart; } } // // Check if a starting cluster was specified. // ClusterShift = Vcb->AllocationSupport.LogOfBytesPerCluster; AllocationSize = FcbOrDcb->Header.AllocationSize.LowPart; try { if (Irp->RequestorMode != KernelMode) { ProbeForRead( IrpSp->Parameters.FileSystemControl.Type3InputBuffer, InputBufferLength, sizeof(UCHAR) ); ProbeForWrite( OutputBuffer, OutputBufferLength, sizeof(UCHAR) ); } StartingVcn = ((PSTARTING_VCN_INPUT_BUFFER)IrpSp->Parameters.FileSystemControl.Type3InputBuffer)->StartingVcn; } except( Irp->RequestorMode != KernelMode ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH ) { Status = GetExceptionCode(); FatRaiseStatus( IrpContext, FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER ); } if (StartingVcn.HighPart || StartingVcn.LowPart >= (AllocationSize >> ClusterShift)) { try_return( Status = STATUS_END_OF_FILE ); } else { // // If we don't find the run, something is very wrong. // LBO Lbo; if (!FatLookupMcbEntry( FcbOrDcb->Vcb, &FcbOrDcb->Mcb, StartingVcn.LowPart << ClusterShift, &Lbo, NULL, &StartingRun)) { FatBugCheck( (ULONG_PTR)FcbOrDcb, (ULONG_PTR)&FcbOrDcb->Mcb, StartingVcn.LowPart ); } } // // Now go fill in the ouput buffer with run information // RunCount = FsRtlNumberOfRunsInLargeMcb( &FcbOrDcb->Mcb ); for (Index = 0, Run = StartingRun; Run < RunCount; Index++, Run++) { ULONG Vcn; LBO Lbo; ULONG ByteLength; // // Check for an exhausted output buffer. // if ((ULONG)FIELD_OFFSET(RETRIEVAL_POINTERS_BUFFER, Extents[Index+1]) > OutputBufferLength) { // // We've run out of space, so we won't be storing as many runs to the // user's buffer as we had originally planned. We need to return the // number of runs that we did have room for. // try { OutputBuffer->ExtentCount = Index; } except( Irp->RequestorMode != KernelMode ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH ) { Status = GetExceptionCode(); FatRaiseStatus( IrpContext, FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER ); } Irp->IoStatus.Information = FIELD_OFFSET(RETRIEVAL_POINTERS_BUFFER, Extents[Index]); try_return( Status = STATUS_BUFFER_OVERFLOW ); } // // Get the extent. If it's not there or malformed, something is very wrong. // if (!FatGetNextMcbEntry(Vcb, &FcbOrDcb->Mcb, Run, &Vcn, &Lbo, &ByteLength)) { FatBugCheck( (ULONG_PTR)FcbOrDcb, (ULONG_PTR)&FcbOrDcb->Mcb, Run ); } // // Fill in the next array element. // try { OutputBuffer->Extents[Index].NextVcn.QuadPart = (Vcn + ByteLength) >> ClusterShift; OutputBuffer->Extents[Index].Lcn.QuadPart = FatGetIndexFromLbo( Vcb, Lbo ) - 2; // // If this is the first run, fill in the starting Vcn // if (Index == 0) { OutputBuffer->ExtentCount = RunCount - StartingRun; OutputBuffer->StartingVcn.QuadPart = Vcn >> ClusterShift; } } except( Irp->RequestorMode != KernelMode ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH ) { Status = GetExceptionCode(); FatRaiseStatus( IrpContext, FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER ); } } // // We successfully retrieved extent info to the end of the allocation. // Irp->IoStatus.Information = FIELD_OFFSET(RETRIEVAL_POINTERS_BUFFER, Extents[Index]); Status = STATUS_SUCCESS; try_exit: NOTHING; } finally { DebugUnwind( FatGetRetrievalPointers ); // // Release resources // FatReleaseFcb( IrpContext, FcbOrDcb ); // // If nothing raised then complete the irp. // if (!AbnormalTermination()) { FatCompleteRequest( IrpContext, Irp, Status ); } DebugTrace(-1, Dbg, "FatGetRetrievalPointers -> VOID\n", 0); } return Status; } // // Local Support Routine // NTSTATUS FatMoveFile ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: Routine moves a file to the requested Starting Lcn from Starting Vcn for the length of cluster count. These values are passed in through the the input buffer as a MOVE_DATA structure. The call must be made with a DASD handle. The file to move is passed in as a parameter. Arguments: Irp - Supplies the Irp being processed. Return Value: NTSTATUS - The return status for the operation. --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PFILE_OBJECT FileObject; TYPE_OF_OPEN TypeOfOpen; PVCB Vcb; PFCB FcbOrDcb; PCCB Ccb; ULONG InputBufferLength; PMOVE_FILE_DATA InputBuffer; ULONG ClusterShift; ULONG MaxClusters; ULONG FileOffset; LARGE_INTEGER LargeFileOffset; LBO TargetLbo; ULONG TargetCluster; LARGE_INTEGER LargeTargetLbo; ULONG ByteCount; ULONG BytesToWrite; ULONG BytesToReallocate; ULONG TargetAllocation; ULONG FirstSpliceSourceCluster; ULONG FirstSpliceTargetCluster; ULONG SecondSpliceSourceCluster; ULONG SecondSpliceTargetCluster; LARGE_MCB SourceMcb; LARGE_MCB TargetMcb; KEVENT StackEvent; PBCB Bcb = NULL; PMDL Mdl = NULL; PVOID Buffer; BOOLEAN SourceMcbInitialized = FALSE; BOOLEAN TargetMcbInitialized = FALSE; BOOLEAN CacheMapInitialized = FALSE; BOOLEAN FcbAcquired = FALSE; BOOLEAN LockedPages = FALSE; BOOLEAN EventArmed = FALSE; BOOLEAN DiskSpaceAllocated = FALSE; PDIRENT Dirent; PBCB DirentBcb = NULL; #if defined(_WIN64) MOVE_FILE_DATA LocalMoveFileData; PMOVE_FILE_DATA32 MoveFileData32; #endif ULONG LocalAbnormalTermination = 0; // // Get the current Irp stack location and save some references. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace(+1, Dbg, "FatMoveFile, FsControlCode = %08lx\n", IrpSp->Parameters.FileSystemControl.FsControlCode); // // Force WAIT to true. We have a handle in the input buffer which can only // be referenced within the originating process. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); // // Extract and decode the file object and check for type of open. // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &FcbOrDcb, &Ccb ) != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatMoveFile -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatMoveFile -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } InputBufferLength = IrpSp->Parameters.FileSystemControl.InputBufferLength; InputBuffer = (PMOVE_FILE_DATA)Irp->AssociatedIrp.SystemBuffer; // // Do a quick check on the input buffer. // #if defined(_WIN64) if (IoIs32bitProcess( Irp )) { if (InputBuffer == NULL || InputBufferLength < sizeof(MOVE_FILE_DATA32)) { FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL ); return STATUS_BUFFER_TOO_SMALL; } MoveFileData32 = (PMOVE_FILE_DATA32) InputBuffer; LocalMoveFileData.FileHandle = (HANDLE) LongToHandle( MoveFileData32->FileHandle ); LocalMoveFileData.StartingVcn = MoveFileData32->StartingVcn; LocalMoveFileData.StartingLcn = MoveFileData32->StartingLcn; LocalMoveFileData.ClusterCount = MoveFileData32->ClusterCount; InputBuffer = &LocalMoveFileData; } else { #endif if (InputBuffer == NULL || InputBufferLength < sizeof(MOVE_FILE_DATA)) { FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL ); return STATUS_BUFFER_TOO_SMALL; } #if defined(_WIN64) } #endif MaxClusters = Vcb->AllocationSupport.NumberOfClusters; TargetCluster = InputBuffer->StartingLcn.LowPart + 2; if (InputBuffer->StartingVcn.HighPart || InputBuffer->StartingLcn.HighPart || (TargetCluster < 2) || (TargetCluster + InputBuffer->ClusterCount < TargetCluster) || (TargetCluster + InputBuffer->ClusterCount > MaxClusters + 2) || (InputBuffer->StartingVcn.LowPart >= MaxClusters)) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatMoveFile -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } // // Try to get a pointer to the file object from the handle passed in. // Status = ObReferenceObjectByHandle( InputBuffer->FileHandle, 0, *IoFileObjectType, Irp->RequestorMode, &FileObject, NULL ); if (!NT_SUCCESS(Status)) { FatCompleteRequest( IrpContext, Irp, Status ); DebugTrace(-1, Dbg, "FatMoveFile -> %08lx\n", Status); return Status; } // // There are three basic ways this could be an invalid attempt, so // we need to // // - check that this file object is opened on the same volume as the // DASD handle used to call this routine. // // - extract and decode the file object and check for type of open. // // - if this is a directory, verify that it's not the root and that // we are not trying to move the first cluster. We cannot move the // first cluster because sub-directories have this cluster number // in them and there is no safe way to simultaneously update them // all. // // We'll allow movefile on the root dir if its fat32, since the root dir // is a real chained file there. // if (FileObject->Vpb != Vcb->Vpb) { ObDereferenceObject( FileObject ); FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatMoveFile -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } TypeOfOpen = FatDecodeFileObject( FileObject, &Vcb, &FcbOrDcb, &Ccb ); if ((TypeOfOpen != UserFileOpen && TypeOfOpen != UserDirectoryOpen) || ((TypeOfOpen == UserDirectoryOpen) && ((NodeType(FcbOrDcb) == FAT_NTC_ROOT_DCB && !FatIsFat32(Vcb)) || (InputBuffer->StartingVcn.QuadPart == 0)))) { ObDereferenceObject( FileObject ); FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatMoveFile -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } // // Indicate we're getting to parents of this fcb by their child, and that // this is a sufficient assertion of our ability to by synchronized // with respect to the parent directory going away. // // The defrag path is an example of one which arrives at an Fcb by // a means which would be unreasonable to duplicate in the assertion // code. See FatOpenDirectoryFile. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_PARENT_BY_CHILD ); ClusterShift = Vcb->AllocationSupport.LogOfBytesPerCluster; try { // // Initialize our state variables and the event. // FileOffset = InputBuffer->StartingVcn.LowPart << ClusterShift; LargeFileOffset.QuadPart = FileOffset; ByteCount = InputBuffer->ClusterCount << ClusterShift; TargetLbo = FatGetLboFromIndex( Vcb, TargetCluster ); LargeTargetLbo.QuadPart = TargetLbo; // // Do a quick check on parameters here // if (FileOffset + ByteCount < FileOffset) { try_return( Status = STATUS_INVALID_PARAMETER ); } KeInitializeEvent( &StackEvent, NotificationEvent, FALSE ); // // Initialize two MCBs we will be using // FsRtlInitializeLargeMcb( &SourceMcb, PagedPool ); SourceMcbInitialized = TRUE; FsRtlInitializeLargeMcb( &TargetMcb, PagedPool ); TargetMcbInitialized = TRUE; // // Ok, now if this is a directory open we need to switch to the internal // stream fileobject since it is set up for caching. The top-level // fileobject has no section object pointers in order prevent folks from // mapping it. // if (TypeOfOpen == UserDirectoryOpen) { PFILE_OBJECT DirStreamFileObject; // // Open the stream fileobject if neccesary. We must acquire the Fcb // now to synchronize with other operations (such as dismount ripping // apart the allocator). // (VOID)FatAcquireExclusiveFcb( IrpContext, FcbOrDcb ); FcbAcquired = TRUE; FatVerifyFcb( IrpContext, FcbOrDcb ); FatOpenDirectoryFile( IrpContext, FcbOrDcb ); DirStreamFileObject = FcbOrDcb->Specific.Dcb.DirectoryFile; // // Transfer our reference to the internal stream and proceed. Note that // if we dereferenced first, the user could sneak a teardown through since // we'd have no references. // ObReferenceObject( DirStreamFileObject ); ObDereferenceObject( FileObject ); FileObject = DirStreamFileObject; } while (ByteCount) { VBO TempVbo; LBO TempLbo; ULONG TempByteCount; // // We must throttle our writes. // CcCanIWrite( FileObject, 0x40000, TRUE, FALSE ); // // Aqcuire file resource exclusive to freeze FileSize and block // user non-cached I/O. Verify the integrity of the fcb - the // media may have changed (or been dismounted) on us. // if (FcbAcquired == FALSE) { (VOID)FatAcquireExclusiveFcb( IrpContext, FcbOrDcb ); FcbAcquired = TRUE; FatVerifyFcb( IrpContext, FcbOrDcb ); } // // Analyzes the range of file allocation we are moving // and determines the actual amount of allocation to be // moved and how much needs to be written. In addition // it guarantees that the Mcb in the file is large enough // so that later MCB operations cannot fail. // FatComputeMoveFileParameter( IrpContext, FcbOrDcb, FileOffset, &ByteCount, &BytesToReallocate, &BytesToWrite ); // // If ByteCount comes back zero, break here. // if (ByteCount == 0) { break; } // // At this point (before actually doing anything with the disk // meta data), calculate the FAT splice clusters and build an // MCB describing the space to be deallocated. // FatComputeMoveFileSplicePoints( IrpContext, FcbOrDcb, FileOffset, TargetCluster, BytesToReallocate, &FirstSpliceSourceCluster, &FirstSpliceTargetCluster, &SecondSpliceSourceCluster, &SecondSpliceTargetCluster, &SourceMcb ); // // Now attempt to allocate the new disk storage using the // Target Lcn as a hint. // TempByteCount = BytesToReallocate; FatAllocateDiskSpace( IrpContext, Vcb, TargetCluster, &TempByteCount, TRUE, &TargetMcb ); DiskSpaceAllocated = TRUE; // // If we didn't get EXACTLY what we wanted, return immediately. // if ((FsRtlNumberOfRunsInLargeMcb( &TargetMcb ) != 1) || !FatGetNextMcbEntry( Vcb, &TargetMcb, 0, &TempVbo, &TempLbo, &TempByteCount ) || (FatGetIndexFromLbo( Vcb, TempLbo) != TargetCluster ) || (TempByteCount != BytesToReallocate)) { // // It would be nice if we could be more specific, but such is life. // try_return( Status = STATUS_INVALID_PARAMETER ); } #if DBG // // We are going to attempt a move, note it. // if (FatMoveFileDebug) { DbgPrint("%lx: Vcn 0x%lx, Lcn 0x%lx, Count 0x%lx.\n", PsGetCurrentThread(), FileOffset >> ClusterShift, TargetCluster, BytesToReallocate >> ClusterShift ); } #endif // // Now attempt to commit the new allocation to disk. If this // raises, the allocation will be deallocated. // FatFlushFatEntries( IrpContext, Vcb, TargetCluster, BytesToReallocate >> ClusterShift ); // // If we are going to write, we have to lock the pages down BEFORE // closing off the paging I/O path to avoid a deadlock from // colided page faults. // if (BytesToWrite) { // // Ensure the shared cache map is set up. // if (FileObject->PrivateCacheMap == NULL ) { CcInitializeCacheMap( FileObject, (PCC_FILE_SIZES)&FcbOrDcb->Header.AllocationSize, TRUE, &FatData.CacheManagerCallbacks, FcbOrDcb ); CacheMapInitialized = TRUE; } // // Map the next range of the file. // CcMapData( FileObject, &LargeFileOffset, BytesToWrite, TRUE, &Bcb, &Buffer ); // // Now attempt to allocate an Mdl to describe the mapped data. // Mdl = IoAllocateMdl( Buffer, (ULONG)BytesToWrite, FALSE, FALSE, NULL ); if (Mdl == NULL) { FatRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES ); } // // Lock the data into memory so that we can safely reallocate the // space. // MmProbeAndLockPages( Mdl, KernelMode, IoReadAccess ); LockedPages = TRUE; } // // Aqcuire both resources exclusive now, guaranteeing that NOBODY // is in either the read or write paths. // ExAcquireResourceExclusiveLite( FcbOrDcb->Header.PagingIoResource, TRUE ); // // This is the first part of some tricky synchronization. // // Set the Event pointer in the FCB. Any paging I/O will block on // this event (if set in FCB) after acquiring the PagingIo resource. // // This is how I keep ALL I/O out of this path without holding the // PagingIo resource exclusive for an extended time. // FcbOrDcb->MoveFileEvent = &StackEvent; EventArmed = TRUE; ExReleaseResourceLite( FcbOrDcb->Header.PagingIoResource ); // // Now write out the data, but only if we have to. We don't have // to copy any file data if the range being reallocated is wholly // beyond valid data length. // if (BytesToWrite) { PIRP IoIrp; KEVENT IoEvent; IO_STATUS_BLOCK Iosb; KeInitializeEvent( &IoEvent, NotificationEvent, FALSE ); ASSERT( LargeTargetLbo.QuadPart >= Vcb->AllocationSupport.FileAreaLbo); IoIrp = IoBuildSynchronousFsdRequest( IRP_MJ_WRITE, Vcb->TargetDeviceObject, Buffer, BytesToWrite, &LargeTargetLbo, &IoEvent, &Iosb ); if (!IoIrp) { FatRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES ); } // // Set a flag indicating that we want to write through any // cache on the controller. This eliminates the need for // an explicit flush-device after the write. // SetFlag( IoGetNextIrpStackLocation(IoIrp)->Flags, SL_WRITE_THROUGH ); Status = IoCallDriver( Vcb->TargetDeviceObject, IoIrp ); if (Status == STATUS_PENDING) { (VOID)KeWaitForSingleObject( &IoEvent, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL ); Status = Iosb.Status; } if (!NT_SUCCESS(Status)) { FatNormalizeAndRaiseStatus( IrpContext, Status ); } // // Now we can get rid of this Mdl. // MmUnlockPages( Mdl ); LockedPages = FALSE; IoFreeMdl( Mdl ); Mdl = NULL; // // Now we can safely unpin. // CcUnpinData( Bcb ); Bcb = NULL; } // // Now that the file data has been moved successfully, we'll go // to fix up the links in the FAT table and perhaps change the // entry in the parent directory. // // First we'll do the second splice and commit it. At that point, // while the volume is in an inconsistent state, the file is // still OK. // FatSetFatEntry( IrpContext, Vcb, SecondSpliceSourceCluster, (FAT_ENTRY)SecondSpliceTargetCluster ); FatFlushFatEntries( IrpContext, Vcb, SecondSpliceSourceCluster, 1 ); // // Now do the first splice OR update the dirent in the parent // and flush the respective object. After this flush the file // now points to the new allocation. // if (FirstSpliceSourceCluster == 0) { ASSERT( NodeType(FcbOrDcb) == FAT_NTC_FCB ); // // We are moving the first cluster of the file, so we need // to update our parent directory. // FatGetDirentFromFcbOrDcb( IrpContext, FcbOrDcb, &Dirent, &DirentBcb ); Dirent->FirstClusterOfFile = (USHORT)FirstSpliceTargetCluster; if (FatIsFat32(Vcb)) { Dirent->FirstClusterOfFileHi = (USHORT)(FirstSpliceTargetCluster >> 16); } FatSetDirtyBcb( IrpContext, DirentBcb, Vcb, TRUE ); FatUnpinBcb( IrpContext, DirentBcb ); DirentBcb = NULL; FatFlushDirentForFile( IrpContext, FcbOrDcb ); FcbOrDcb->FirstClusterOfFile = FirstSpliceTargetCluster; } else { FatSetFatEntry( IrpContext, Vcb, FirstSpliceSourceCluster, (FAT_ENTRY)FirstSpliceTargetCluster ); FatFlushFatEntries( IrpContext, Vcb, FirstSpliceSourceCluster, 1 ); } // // This was successfully committed. We no longer want to free // this allocation on error. // DiskSpaceAllocated = FALSE; // // Now we just have to free the orphaned space. We don't have // to commit this right now as the integrity of the file doesn't // depend on it. // FatDeallocateDiskSpace( IrpContext, Vcb, &SourceMcb ); FatUnpinRepinnedBcbs( IrpContext ); Status = FatHijackIrpAndFlushDevice( IrpContext, Irp, Vcb->TargetDeviceObject ); if (!NT_SUCCESS(Status)) { FatNormalizeAndRaiseStatus( IrpContext, Status ); } // // Finally we must replace the old MCB extent information with // the new. If this fails from pool allocation, we fix it in // the finally clause by resetting the file's Mcb. // FatRemoveMcbEntry( Vcb, &FcbOrDcb->Mcb, FileOffset, BytesToReallocate ); FatAddMcbEntry( Vcb, &FcbOrDcb->Mcb, FileOffset, TargetLbo, BytesToReallocate ); // // Now this is the second part of the tricky synchronization. // // We drop the paging I/O here and signal the notification // event which allows all waiters (present or future) to proceed. // Then we block again on the PagingIo exclusive. When // we have it, we again know that there can be nobody in the // read/write path and thus nobody touching the event, so we // NULL the pointer to it and then drop the PagingIo resource. // // This combined with our synchronization before the write above // guarantees that while we were moving the allocation, there // was no other I/O to this file and because we do not hold // the paging resource across a flush, we are not exposed to // a deadlock. // KeSetEvent( &StackEvent, 0, FALSE ); ExAcquireResourceExclusiveLite( FcbOrDcb->Header.PagingIoResource, TRUE ); FcbOrDcb->MoveFileEvent = NULL; EventArmed = FALSE; ExReleaseResourceLite( FcbOrDcb->Header.PagingIoResource ); // // Release the resources and let anyone else access the file before // looping back. // FatReleaseFcb( IrpContext, FcbOrDcb ); FcbAcquired = FALSE; // // Advance the state variables. // TargetCluster += BytesToReallocate >> ClusterShift; FileOffset += BytesToReallocate; TargetLbo += BytesToReallocate; ByteCount -= BytesToReallocate; LargeFileOffset.LowPart += BytesToReallocate; LargeTargetLbo.QuadPart += BytesToReallocate; // // Clear the two Mcbs // FatRemoveMcbEntry( Vcb, &SourceMcb, 0, 0xFFFFFFFF ); FatRemoveMcbEntry( Vcb, &TargetMcb, 0, 0xFFFFFFFF ); // // Make the event blockable again. // KeClearEvent( &StackEvent ); } Status = STATUS_SUCCESS; try_exit: NOTHING; } finally { DebugUnwind( FatMoveFile ); LocalAbnormalTermination |= AbnormalTermination(); ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_PARENT_BY_CHILD ); // // Cleanup the Mdl, Bcb, and cache map as appropriate. // if (Mdl != NULL) { ASSERT(LocalAbnormalTermination); if (LockedPages) { MmUnlockPages( Mdl ); } IoFreeMdl( Mdl ); } if (Bcb != NULL) { ASSERT(LocalAbnormalTermination); CcUnpinData( Bcb ); } if (CacheMapInitialized) { CcUninitializeCacheMap( FileObject, NULL, NULL ); } // // Use a nested try-finally for cleanup if our unpinrepinned // encounters write-through errors. This may even be a re-raise. // try { // // If we have some new allocation hanging around, remove it. The // pages needed to do this are guaranteed to be resident because // we have already repinned them. // if (DiskSpaceAllocated) { FatDeallocateDiskSpace( IrpContext, Vcb, &TargetMcb ); FatUnpinRepinnedBcbs( IrpContext ); } } finally { LocalAbnormalTermination |= AbnormalTermination(); // // Check on the directory Bcb // if (DirentBcb != NULL) { FatUnpinBcb( IrpContext, DirentBcb ); } // // Uninitialize our MCBs // if (SourceMcbInitialized) { FsRtlUninitializeLargeMcb( &SourceMcb ); } if (TargetMcbInitialized) { FsRtlUninitializeLargeMcb( &TargetMcb ); } // // If this is an abnormal termination then presumably something // bad happened. Set the Allocation size to unknown and clear // the Mcb, but only if we still own the Fcb. // // It is important to make sure we use a 64bit form of -1. This is // what will convince the fastIO path that it cannot extend the file // in the cache until we have picked up the mapping pairs again. // // Also, we have to do this while owning PagingIo or we can tear the // Mcb down in the midst of the noncached IO path looking up extents // (after we drop it and let them all in). // if (LocalAbnormalTermination && FcbAcquired) { FcbOrDcb->Header.AllocationSize.QuadPart = FCB_LOOKUP_ALLOCATIONSIZE_HINT; FatRemoveMcbEntry( Vcb, &FcbOrDcb->Mcb, 0, 0xFFFFFFFF ); } // // If we broke out of the loop with the Event armed, defuse it // in the same way we do it after a write. // if (EventArmed) { KeSetEvent( &StackEvent, 0, FALSE ); ExAcquireResourceExclusiveLite( FcbOrDcb->Header.PagingIoResource, TRUE ); FcbOrDcb->MoveFileEvent = NULL; ExReleaseResourceLite( FcbOrDcb->Header.PagingIoResource ); } // // Finally release the main file resource. // if (FcbAcquired) { FatReleaseFcb( IrpContext, FcbOrDcb ); } // // Now dereference the fileobject. If the user was a wacko they could have // tried to nail us by closing the handle right after they threw this move // down, so we had to keep the fileobject referenced across the entire // operation. // ObDereferenceObject( FileObject ); } } // // Complete the irp if we terminated normally. // FatCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Local Support Routine // VOID FatComputeMoveFileParameter ( IN PIRP_CONTEXT IrpContext, IN PFCB FcbOrDcb, IN ULONG FileOffset, IN OUT PULONG ByteCount, OUT PULONG BytesToReallocate, OUT PULONG BytesToWrite ) /*++ Routine Description: This is a helper routine for FatMoveFile that analyses the range of file allocation we are moving and determines the actual amount of allocation to be moved and how much needs to be written. Arguments: FcbOrDcb - Supplies the file and its various sizes. FileOffset - Supplies the beginning Vbo of the reallocation zone. ByteCount - Supplies the request length to reallocate. This will be bounded by allocation size on return. BytesToReallocate - Receives ByteCount bounded by the file allocation size and a 0x40000 boundry. BytesToWrite - Receives BytesToReallocate bounded by ValidDataLength. Return Value: VOID --*/ { ULONG ClusterSize; ULONG AllocationSize; ULONG ValidDataLength; ULONG ClusterAlignedVDL; // // If we haven't yet set the correct AllocationSize, do so. // if (FcbOrDcb->Header.AllocationSize.QuadPart == FCB_LOOKUP_ALLOCATIONSIZE_HINT) { FatLookupFileAllocationSize( IrpContext, FcbOrDcb ); // // If this is a non-root directory, we have a bit more to // do since it has not gone through FatOpenDirectoryFile(). // if (NodeType(FcbOrDcb) == FAT_NTC_DCB || (NodeType(FcbOrDcb) == FAT_NTC_ROOT_DCB && FatIsFat32(FcbOrDcb->Vcb))) { FcbOrDcb->Header.FileSize.LowPart = FcbOrDcb->Header.AllocationSize.LowPart; } } // // Get the number of bytes left to write and ensure that it does // not extend beyond allocation size. We return here if FileOffset // is beyond AllocationSize which can happn on a truncation. // AllocationSize = FcbOrDcb->Header.AllocationSize.LowPart; ValidDataLength = FcbOrDcb->Header.ValidDataLength.LowPart; if (FileOffset + *ByteCount > AllocationSize) { if (FileOffset >= AllocationSize) { *ByteCount = 0; *BytesToReallocate = 0; *BytesToWrite = 0; return; } *ByteCount = AllocationSize - FileOffset; } // // If there is more than our max, then reduce the byte count for this // pass to our maximum. We must also align the file offset to a 0x40000 // byte boundary. // if ((FileOffset & 0x3ffff) + *ByteCount > 0x40000) { *BytesToReallocate = 0x40000 - (FileOffset & 0x3ffff); } else { *BytesToReallocate = *ByteCount; } // // We may be able to skip some (or all) of the write // if allocation size is significantly greater than valid data length. // ClusterSize = 1 << FcbOrDcb->Vcb->AllocationSupport.LogOfBytesPerCluster; ClusterAlignedVDL = (ValidDataLength + (ClusterSize - 1)) & ~(ClusterSize - 1); if ((NodeType(FcbOrDcb) == FAT_NTC_FCB) && (FileOffset + *BytesToReallocate > ClusterAlignedVDL)) { if (FileOffset > ClusterAlignedVDL) { *BytesToWrite = 0; } else { *BytesToWrite = ClusterAlignedVDL - FileOffset; } } else { *BytesToWrite = *BytesToReallocate; } } // // Local Support Routine // VOID FatComputeMoveFileSplicePoints ( IN PIRP_CONTEXT IrpContext, IN PFCB FcbOrDcb, IN ULONG FileOffset, IN ULONG TargetCluster, IN ULONG BytesToReallocate, OUT PULONG FirstSpliceSourceCluster, OUT PULONG FirstSpliceTargetCluster, OUT PULONG SecondSpliceSourceCluster, OUT PULONG SecondSpliceTargetCluster, IN OUT PLARGE_MCB SourceMcb ) /*++ Routine Description: This is a helper routine for FatMoveFile that analyzes the range of file allocation we are moving and generates the splice points in the FAT table. Arguments: FcbOrDcb - Supplies the file and thus Mcb. FileOffset - Supplies the beginning Vbo of the reallocation zone. TargetCluster - Supplies the beginning cluster of the reallocation target. BytesToReallocate - Suppies the length of the reallocation zone. FirstSpliceSourceCluster - Receives the last cluster in previous allocation or zero if we are reallocating from VBO 0. FirstSpliceTargetCluster - Receives the target cluster (i.e. new allocation) SecondSpliceSourceCluster - Receives the final target cluster. SecondSpliceTargetCluster - Receives the first cluster of the remaining source allocation or FAT_CLUSTER_LAST if the reallocation zone extends to the end of the file. SourceMcb - This supplies an MCB that will be filled in with run information describing the file allocation being replaced. The Mcb must be initialized by the caller. Return Value: VOID --*/ { VBO SourceVbo; LBO SourceLbo; ULONG SourceIndex; ULONG SourceBytesInRun; ULONG SourceBytesRemaining; ULONG SourceMcbVbo; ULONG SourceMcbBytesInRun; PVCB Vcb; Vcb = FcbOrDcb->Vcb; // // Get information on the final cluster in the previous allocation and // prepare to enumerate it in the follow loop. // if (FileOffset == 0) { SourceIndex = 0; *FirstSpliceSourceCluster = 0; FatGetNextMcbEntry( Vcb, &FcbOrDcb->Mcb, 0, &SourceVbo, &SourceLbo, &SourceBytesInRun ); } else { FatLookupMcbEntry( Vcb, &FcbOrDcb->Mcb, FileOffset-1, &SourceLbo, &SourceBytesInRun, &SourceIndex); *FirstSpliceSourceCluster = FatGetIndexFromLbo( Vcb, SourceLbo ); if (SourceBytesInRun == 1) { SourceIndex += 1; FatGetNextMcbEntry( Vcb, &FcbOrDcb->Mcb, SourceIndex, &SourceVbo, &SourceLbo, &SourceBytesInRun); } else { SourceVbo = FileOffset; SourceLbo += 1; SourceBytesInRun -= 1; } } // // At this point the variables: // // - SourceIndex - SourceLbo - SourceBytesInRun - // // all correctly decribe the allocation to be removed. In the loop // below we will start here and continue enumerating the Mcb runs // until we are finished with the allocation to be relocated. // *FirstSpliceTargetCluster = TargetCluster; *SecondSpliceSourceCluster = *FirstSpliceTargetCluster + (BytesToReallocate >> Vcb->AllocationSupport.LogOfBytesPerCluster) - 1; for (SourceBytesRemaining = BytesToReallocate, SourceMcbVbo = 0; SourceBytesRemaining > 0; SourceIndex += 1, SourceBytesRemaining -= SourceMcbBytesInRun, SourceMcbVbo += SourceMcbBytesInRun) { if (SourceMcbVbo != 0) { FatGetNextMcbEntry( Vcb, &FcbOrDcb->Mcb, SourceIndex, &SourceVbo, &SourceLbo, &SourceBytesInRun ); } ASSERT( SourceVbo == SourceMcbVbo + FileOffset ); SourceMcbBytesInRun = SourceBytesInRun < SourceBytesRemaining ? SourceBytesInRun : SourceBytesRemaining; FatAddMcbEntry( Vcb, SourceMcb, SourceMcbVbo, SourceLbo, SourceMcbBytesInRun ); } // // Now compute the cluster of the target of the second // splice. If the final run in the above loop was // more than we needed, then we can just do arithmetic, // otherwise we have to look up the next run. // if (SourceMcbBytesInRun < SourceBytesInRun) { *SecondSpliceTargetCluster = FatGetIndexFromLbo( Vcb, SourceLbo + SourceMcbBytesInRun ); } else { if (FatGetNextMcbEntry( Vcb, &FcbOrDcb->Mcb, SourceIndex, &SourceVbo, &SourceLbo, &SourceBytesInRun )) { *SecondSpliceTargetCluster = FatGetIndexFromLbo( Vcb, SourceLbo ); } else { *SecondSpliceTargetCluster = FAT_CLUSTER_LAST; } } } NTSTATUS FatAllowExtendedDasdIo( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine marks the CCB to indicate that the handle may be used to read past the end of the volume file. The handle must be a dasd handle. Arguments: Irp - Supplies the Irp being processed. Return Value: NTSTATUS - The return status for the operation. --*/ { PIO_STACK_LOCATION IrpSp; PVCB Vcb; PFCB Fcb; PCCB Ccb; // // Get the current Irp stack location and save some references. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // Extract and decode the file object and check for type of open. // if (FatDecodeFileObject( IrpSp->FileObject, &Vcb, &Fcb, &Ccb ) != UserVolumeOpen) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) { FatCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); DebugTrace(-1, Dbg, "FatAllowExtendedDasdIo -> %08lx\n", STATUS_INVALID_PARAMETER); return STATUS_INVALID_PARAMETER; } SetFlag( Ccb->Flags, CCB_FLAG_ALLOW_EXTENDED_DASD_IO ); FatCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } VOID FatFlushAndCleanVolume( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN PVCB Vcb, IN FAT_FLUSH_TYPE FlushType ) /*++ Routine Description: This routine flushes and otherwise preparse a volume to be eligible for deletion. The dismount and PNP paths share the need for this common work. The Vcb will always be valid on return from this function. It is the caller's responsibility to attempt the dismount/deletion, and to setup allocation support again if the volume will be brought back from the brink. Arguments: Irp - Irp for the overlying request Vcb - the volume being operated on FlushType - specifies the kind of flushing desired Return Value: NTSTATUS - The return status for the operation. --*/ { // // The volume must be held exclusive. // ASSERT( FatVcbAcquiredExclusive( IrpContext, Vcb )); // // There is no fail, flush everything. If invalidating, it is important // that we invalidate as we flush (eventually, w/ paging io held) so that we // error out the maximum number of late writes. // if (FlushType != NoFlush) { (VOID) FatFlushVolume( IrpContext, Vcb, FlushType ); } FatCloseEaFile( IrpContext, Vcb, FALSE ); // // Now, tell the device to flush its buffers. // if (FlushType != NoFlush) { (VOID)FatHijackIrpAndFlushDevice( IrpContext, Irp, Vcb->TargetDeviceObject ); } // // Now purge everything in sight. We're trying to provoke as many closes as // soon as possible, this volume may be on its way out. // if (FlushType != FlushWithoutPurge) { (VOID) FatPurgeReferencedFileObjects( IrpContext, Vcb->RootDcb, NoFlush ); } // // If the volume was dirty and we were allowed to flush, do the processing that // the delayed callback would have done. // if (FlagOn(Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DIRTY)) { // // Cancel any pending clean volumes. // (VOID)KeCancelTimer( &Vcb->CleanVolumeTimer ); (VOID)KeRemoveQueueDpc( &Vcb->CleanVolumeDpc ); if (FlushType != NoFlush) { // // The volume is now clean, note it. // if (!FlagOn(Vcb->VcbState, VCB_STATE_FLAG_MOUNTED_DIRTY)) { FatMarkVolume( IrpContext, Vcb, VolumeClean ); ClearFlag( Vcb->VcbState, VCB_STATE_FLAG_VOLUME_DIRTY ); } // // Unlock the volume if it is removable. // if (FlagOn(Vcb->VcbState, VCB_STATE_FLAG_REMOVABLE_MEDIA) && !FlagOn(Vcb->VcbState, VCB_STATE_FLAG_BOOT_OR_PAGING_FILE)) { FatToggleMediaEjectDisable( IrpContext, Vcb, FALSE ); } } } // // Uninitialize the volume file cache map. Note that we cannot // do a "FatSyncUninit" because of deadlock problems. However, // since this FileObject is referenced by us, and thus included // in the Vpb residual count, it is OK to do a normal CcUninit. // if (FlushType != FlushWithoutPurge) { CcUninitializeCacheMap( Vcb->VirtualVolumeFile, &FatLargeZero, NULL ); FatTearDownAllocationSupport( IrpContext, Vcb ); } } NTSTATUS FatSearchBufferForLabel( IN PIRP_CONTEXT IrpContext, IN PVPB Vpb, IN PVOID Buffer, IN ULONG Size, OUT PBOOLEAN LabelFound ) /*++ Routine Description: Search a buffer (taken from the root directory) for a volume label matching the label in the Arguments: IrpContext - Supplies our irp context Vpb - Vpb supplying the volume label Buffer - Supplies the buffer we'll search Size - The size of the buffer in bytes. LabelFound - Returns whether a label was found. Return Value: There are four interesting cases: 1) Some random error occurred - that error returned as status, LabelFound is indeterminate. 2) No label was found - STATUS_SUCCESS returned, LabelFound is FALSE. 3) A matching label was found - STATUS_SUCCESS returned, LabelFound is TRUE. 4) A non-matching label found - STATUS_WRONG_VOLUME returned, LabelFound is indeterminate. --*/ { NTSTATUS Status; WCHAR UnicodeBuffer[11]; PDIRENT Dirent; PDIRENT TerminationDirent; ULONG VolumeLabelLength; OEM_STRING OemString; UNICODE_STRING UnicodeString; Dirent = Buffer; TerminationDirent = Dirent + Size / sizeof(DIRENT); while ( Dirent < TerminationDirent ) { if ( Dirent->FileName[0] == FAT_DIRENT_NEVER_USED ) { Dirent = TerminationDirent; break; } // // If the entry is the non-deleted volume label break from the loop. // // Note that all out parameters are already correctly set. // if (((Dirent->Attributes & ~FAT_DIRENT_ATTR_ARCHIVE) == FAT_DIRENT_ATTR_VOLUME_ID) && (Dirent->FileName[0] != FAT_DIRENT_DELETED)) { break; } Dirent += 1; } if (Dirent >= TerminationDirent) { // // We've run out of buffer. // *LabelFound = FALSE; return STATUS_SUCCESS; } // // Compute the length of the volume name // OemString.Buffer = &Dirent->FileName[0]; OemString.MaximumLength = 11; for ( OemString.Length = 11; OemString.Length > 0; OemString.Length -= 1) { if ( (Dirent->FileName[OemString.Length-1] != 0x00) && (Dirent->FileName[OemString.Length-1] != 0x20) ) { break; } } UnicodeString.MaximumLength = MAXIMUM_VOLUME_LABEL_LENGTH; UnicodeString.Buffer = &UnicodeBuffer[0]; Status = RtlOemStringToCountedUnicodeString( &UnicodeString, &OemString, FALSE ); if ( !NT_SUCCESS( Status ) ) { return Status; } VolumeLabelLength = UnicodeString.Length; if ( (VolumeLabelLength != (ULONG)Vpb->VolumeLabelLength) || (!RtlEqualMemory(&UnicodeBuffer[0], &Vpb->VolumeLabel[0], VolumeLabelLength)) ) { return STATUS_WRONG_VOLUME; } // // We found a matching label. // *LabelFound = TRUE; return STATUS_SUCCESS; } VOID FatVerifyLookupFatEntry ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN ULONG FatIndex, IN OUT PULONG FatEntry ) { ULONG PageEntryOffset; ULONG OffsetIntoVolumeFile; PVOID Buffer; ASSERT(Vcb->AllocationSupport.FatIndexBitSize == 32); FatVerifyIndexIsValid( IrpContext, Vcb, FatIndex); Buffer = FsRtlAllocatePoolWithTag( NonPagedPoolCacheAligned, PAGE_SIZE, TAG_ENTRY_LOOKUP_BUFFER ); OffsetIntoVolumeFile = FatReservedBytes(&Vcb->Bpb) + FatIndex * sizeof(ULONG); PageEntryOffset = (OffsetIntoVolumeFile % PAGE_SIZE) / sizeof(ULONG); try { FatPerformVerifyDiskRead( IrpContext, Vcb, Buffer, OffsetIntoVolumeFile & ~(PAGE_SIZE - 1), PAGE_SIZE, TRUE ); *FatEntry = ((PULONG)(Buffer))[PageEntryOffset]; } finally { ExFreePool( Buffer ); } }