/*++ Copyright (c) 1996-2000 Microsoft Corporation Module Name: FsCtrl.c Abstract: This module implements the File System Control routines for Udfs called by the Fsd/Fsp dispatch drivers. // @@BEGIN_DDKSPLIT Author: Dan Lovinger [DanLo] 11-Jun-1996 Revision History: Tom Jolly [TomJolly] 1-March-2000 UDF 2.01 support // @@END_DDKSPLIT --*/ #include "UdfProcs.h" // // The Bug check file id for this module // #define BugCheckFileId (UDFS_BUG_CHECK_FSCTRL) // // The local debug trace level // #define Dbg (UDFS_DEBUG_LEVEL_FSCTRL) // // Local constants // BOOLEAN UdfDisable = FALSE; // // CRC of the PVD on Disney's Snow White title, so we can // ignore the volsetseqmax on that disc only. // #define UDF_SNOW_WHITE_PVD_CRC ((USHORT)0x1d05) #define UDF_SNOW_WHITE_PVD_CRC_VARIANT_2 ((USHORT)0x534e) // // Local macros // INLINE VOID UdfStoreFileSetDescriptorIfPrevailing ( IN OUT PNSR_FSD *StoredFSD, IN OUT PNSR_FSD *NewFSD ) { PNSR_FSD TempFSD; // // If we haven't stored a fileset descriptor or the fileset number // of the stored descriptor is less than the new descriptor, swap the // pointers around. // if (*StoredFSD == NULL || (*StoredFSD)->FileSet < (*NewFSD)->FileSet) { TempFSD = *StoredFSD; *StoredFSD = *NewFSD; *NewFSD = TempFSD; } } // // Local support routines // VOID UdfDetermineVolumeBounding ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject, IN PULONG S, IN PULONG N ); NTSTATUS UdfDismountVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfFindAnchorVolumeDescriptor ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN OUT PNSR_ANCHOR *AnchorVolumeDescriptor ); NTSTATUS UdfFindFileSetDescriptor ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PLONGAD LongAd, IN OUT PNSR_FSD *FileSetDescriptor ); NTSTATUS UdfFindVolumeDescriptors ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PEXTENTAD Extent, IN OUT PPCB *Pcb, IN OUT PNSR_PVD *PrimaryVolumeDescriptor, IN OUT PNSR_LVOL *LogicalVolumeDescriptor ); NTSTATUS UdfInvalidateVolumes ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfIsPathnameValid ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); BOOLEAN UdfIsRemount ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, OUT PVCB *OldVcb ); UdfIsVolumeDirty ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfIsVolumeMounted ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfLockVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); VOID UdfRemountOldVcb( IN PIRP_CONTEXT IrpContext, IN PVCB OldVcb, IN PVCB NewVcb, IN PDEVICE_OBJECT DeviceObjectWeTalkTo ); NTSTATUS UdfMountVolume( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfOplockRequest ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); BOOLEAN UdfRecognizeVolume ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT DeviceObject, IN ULONG SectorSize, IN PULONG BoundS, IN OUT PBOOLEAN Bridge, OUT PUSHORT NSRVerFound ); VOID UdfScanForDismountedVcb ( IN PIRP_CONTEXT IrpContext ); NTSTATUS UdfUnlockVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); VOID UdfUpdateVolumeLabel ( IN PIRP_CONTEXT IrpContext, IN PWCHAR VolumeLabel, IN OUT PUSHORT VolumeLabelLength, IN PUCHAR Dstring, IN UCHAR FieldLength ); VOID UdfUpdateVolumeSerialNumber ( IN PIRP_CONTEXT IrpContext, IN OUT PULONG VolumeSerialNumber, IN PNSR_FSD Fsd ); NTSTATUS UdfUserFsctl ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfVerifyVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); NTSTATUS UdfAllowExtendedDasdIo( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ); #ifdef EXPERIMENTAL_MOUNT_OPEN_R_MEDIA NTSTATUS UdfCheckForOpenRMedia( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject, IN PULONG S, IN PULONG N ); #pragma alloc_text(PAGE, UdfCheckForOpenRMedia) #endif #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, UdfCommonFsControl) #pragma alloc_text(PAGE, UdfDetermineVolumeBounding) #pragma alloc_text(PAGE, UdfDismountVolume) #pragma alloc_text(PAGE, UdfFindAnchorVolumeDescriptor) #pragma alloc_text(PAGE, UdfFindFileSetDescriptor) #pragma alloc_text(PAGE, UdfFindVolumeDescriptors) #pragma alloc_text(PAGE, UdfIsPathnameValid) #pragma alloc_text(PAGE, UdfIsRemount) #pragma alloc_text(PAGE, UdfIsVolumeDirty) #pragma alloc_text(PAGE, UdfIsVolumeMounted) #pragma alloc_text(PAGE, UdfLockVolume) #pragma alloc_text(PAGE, UdfMountVolume) #pragma alloc_text(PAGE, UdfOplockRequest) #pragma alloc_text(PAGE, UdfRecognizeVolume) #pragma alloc_text(PAGE, UdfScanForDismountedVcb) #pragma alloc_text(PAGE, UdfStoreVolumeDescriptorIfPrevailing) #pragma alloc_text(PAGE, UdfUnlockVolume) #pragma alloc_text(PAGE, UdfUpdateVolumeLabel) #pragma alloc_text(PAGE, UdfUpdateVolumeSerialNumber) #pragma alloc_text(PAGE, UdfUserFsctl) #pragma alloc_text(PAGE, UdfVerifyVolume) #pragma alloc_text(PAGE, UdfAllowExtendedDasdIo) #endif VOID UdfStoreVolumeDescriptorIfPrevailing ( IN OUT PNSR_VD_GENERIC *StoredVD, IN OUT PNSR_VD_GENERIC NewVD ) /*++ Routine Description: This routine updates Volume Descriptor if the new descriptor is more prevailing than the one currently stored. Arguments: StoredVD - pointer to a currently stored descriptor NewVD - pointer to a candidate descriptor Return Value: None. --*/ { PNSR_VD_GENERIC TempVD; // // If we haven't stored a volume descriptor or the sequence number // of the stored descriptor is less than the new descriptor, make a copy // of it and store it. // if ((NULL == *StoredVD) || ((*StoredVD)->Sequence < NewVD->Sequence)) { if ( NULL == *StoredVD) { *StoredVD = (PNSR_VD_GENERIC) FsRtlAllocatePoolWithTag( UdfNonPagedPool, sizeof(NSR_VD_GENERIC), TAG_NSR_VDSD ); } RtlCopyMemory( *StoredVD, NewVD, sizeof( NSR_VD_GENERIC)); } } NTSTATUS UdfCommonFsControl ( 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 = IoGetCurrentIrpStackLocation( Irp ); PAGED_CODE(); // // Check the input parameters // ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); // // Get a pointer to the current Irp stack location // IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // 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_MOUNT_VOLUME: Status = UdfMountVolume( IrpContext, Irp ); break; case IRP_MN_VERIFY_VOLUME: Status = UdfVerifyVolume( IrpContext, Irp ); break; case IRP_MN_USER_FS_REQUEST: Status = UdfUserFsctl( IrpContext, Irp ); break; default: UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); Status = STATUS_INVALID_DEVICE_REQUEST; break; } return Status; } // // Local support routine // NTSTATUS UdfUserFsctl ( 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; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PAGED_CODE(); // // Case on the control code. // switch ( IrpSp->Parameters.FileSystemControl.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 = UdfOplockRequest( IrpContext, Irp ); break; case FSCTL_LOCK_VOLUME : Status = UdfLockVolume( IrpContext, Irp ); break; case FSCTL_UNLOCK_VOLUME : Status = UdfUnlockVolume( IrpContext, Irp ); break; case FSCTL_DISMOUNT_VOLUME : Status = UdfDismountVolume( IrpContext, Irp ); break; case FSCTL_IS_VOLUME_DIRTY : Status = UdfIsVolumeDirty( IrpContext, Irp ); break; case FSCTL_IS_VOLUME_MOUNTED : Status = UdfIsVolumeMounted( IrpContext, Irp ); break; case FSCTL_IS_PATHNAME_VALID : Status = UdfIsPathnameValid( IrpContext, Irp ); break; case FSCTL_INVALIDATE_VOLUMES : Status = UdfInvalidateVolumes( IrpContext, Irp ); break; case FSCTL_ALLOW_EXTENDED_DASD_IO: Status = UdfAllowExtendedDasdIo( IrpContext, Irp ); break; // // We don't support any of the known or unknown requests. // default: UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); Status = STATUS_INVALID_DEVICE_REQUEST; break; } return Status; } // // Local support routine // NTSTATUS UdfOplockRequest ( 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; PFCB Fcb; PCCB Ccb; ULONG OplockCount = 0; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PAGED_CODE(); // // We only permit oplock requests on files. // if (UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb ) != UserFileOpen ) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); 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 ); ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST ); // // Switch on the function control code. We grab the Fcb exclusively // for oplock requests, shared for oplock break acknowledgement. // switch (IrpSp->Parameters.FileSystemControl.FsControlCode) { case FSCTL_REQUEST_OPLOCK_LEVEL_1 : case FSCTL_REQUEST_OPLOCK_LEVEL_2 : case FSCTL_REQUEST_BATCH_OPLOCK : case FSCTL_REQUEST_FILTER_OPLOCK : UdfAcquireFcbExclusive( IrpContext, Fcb, FALSE ); if (IrpSp->Parameters.FileSystemControl.FsControlCode == FSCTL_REQUEST_OPLOCK_LEVEL_2) { if (Fcb->FileLock != NULL) { OplockCount = (ULONG) FsRtlAreThereCurrentFileLocks( Fcb->FileLock ); } } else { OplockCount = Fcb->FcbCleanup; } break; case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE: case FSCTL_OPBATCH_ACK_CLOSE_PENDING: case FSCTL_OPLOCK_BREAK_NOTIFY: case FSCTL_OPLOCK_BREAK_ACK_NO_2: UdfAcquireFcbShared( IrpContext, Fcb, FALSE ); break; default: UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } // // Use a try finally to free the Fcb. // try { // // Verify the Fcb. // UdfVerifyFcbOperation( IrpContext, Fcb ); // // Call the FsRtl routine to grant/acknowledge oplock. // Status = FsRtlOplockFsctrl( &Fcb->Oplock, Irp, OplockCount ); // // Set the flag indicating if Fast I/O is possible // UdfLockFcb( IrpContext, Fcb ); Fcb->IsFastIoPossible = UdfIsFastIoPossible( Fcb ); UdfUnlockFcb( IrpContext, Fcb ); // // The oplock package will complete the Irp. // Irp = NULL; } finally { // // Release all of our resources // UdfReleaseFcb( IrpContext, Fcb ); } // // Complete the request if there was no exception. // UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Local support routine // NTSTATUS UdfLockVolumeInternal ( 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; NTSTATUS FinalStatus = (FileObject? STATUS_ACCESS_DENIED: STATUS_DEVICE_BUSY); ULONG RemainingUserReferences = (FileObject? 1: 0); KIRQL SavedIrql; ASSERT_EXCLUSIVE_VCB( Vcb ); // // The cleanup count for the volume only reflects the fileobject that // will lock the volume. Otherwise, we must fail the request. // // Since the only cleanup is for the provided fileobject, we will try // to get rid of all of the other user references. If there is only one // remaining after the purge then we can allow the volume to be locked. // UdfPurgeVolume( IrpContext, Vcb, FALSE ); // // 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 purged, 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. // UdfReleaseVcb( IrpContext, Vcb ); Status = CcWaitForCurrentLazyWriterActivity(); // // This is intentional. If we were able to get the Vcb before, just // wait for it and take advantage of knowing that it is OK to leave // the flag up. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE ); if (!NT_SUCCESS( Status )) { return Status; } UdfFspClose( Vcb ); // // If the volume is already explicitly locked then fail. We use the // Vpb locked flag as an 'explicit lock' flag in the same way as Fat. // IoAcquireVpbSpinLock( &SavedIrql ); if (!FlagOn( Vcb->Vpb->Flags, VPB_LOCKED ) && (Vcb->VcbCleanup == RemainingUserReferences) && (Vcb->VcbUserReference == Vcb->VcbResidualUserReference + RemainingUserReferences)) { SetFlag( Vcb->VcbState, VCB_STATE_LOCKED ); SetFlag( Vcb->Vpb->Flags, VPB_LOCKED ); Vcb->VolumeLockFileObject = FileObject; FinalStatus = STATUS_SUCCESS; } IoReleaseVpbSpinLock( SavedIrql ); return FinalStatus; } NTSTATUS UdfUnlockVolumeInternal ( 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. --*/ { NTSTATUS Status = STATUS_NOT_LOCKED; KIRQL SavedIrql; IoAcquireVpbSpinLock( &SavedIrql ); if (FlagOn(Vcb->Vpb->Flags, VPB_LOCKED) && (FileObject == Vcb->VolumeLockFileObject)) { ClearFlag( Vcb->VcbState, VCB_STATE_LOCKED ); ClearFlag( Vcb->Vpb->Flags, VPB_LOCKED); Vcb->VolumeLockFileObject = NULL; Status = STATUS_SUCCESS; } IoReleaseVpbSpinLock( SavedIrql ); return Status; } // // Local support routine // NTSTATUS UdfLockVolume ( 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 = IoGetCurrentIrpStackLocation( Irp ); PVCB Vcb; PFCB Fcb; PCCB Ccb; PAGED_CODE(); // // Decode the file object, the only type of opens we accept are // user volume opens. // if (UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb ) != UserVolumeOpen) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } DebugTrace(( +1, Dbg, "UdfLockVolume()\n")); // // 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. // Vcb = Fcb->Vcb; UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE ); try { // // Verify the Vcb. // UdfVerifyVcb( IrpContext, Vcb ); Status = UdfLockVolumeInternal( IrpContext, Vcb, IrpSp->FileObject ); } finally { // // Release the Vcb. // UdfReleaseVcb( IrpContext, Vcb ); if (AbnormalTermination() || !NT_SUCCESS( Status )) { FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_LOCK_FAILED ); } DebugTrace(( -1, Dbg, "UdfLockVolume() -> 0x%X\n", Status)); } // // Complete the request if there haven't been any exceptions. // UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Local support routine // NTSTATUS UdfUnlockVolume ( 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 = IoGetCurrentIrpStackLocation( Irp ); PVCB Vcb; PFCB Fcb; PCCB Ccb; PAGED_CODE(); // // Decode the file object, the only type of opens we accept are // user volume opens. // if (UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb ) != UserVolumeOpen ) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } // // Acquire exclusive access to the Vcb. // Vcb = Fcb->Vcb; UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE ); // // We won't check for a valid Vcb for this request. An unlock will always // succeed on a locked volume. // Status = UdfUnlockVolumeInternal( IrpContext, Vcb, IrpSp->FileObject ); // // Release all of our resources // UdfReleaseVcb( IrpContext, Vcb ); // // Send notification that the volume is avaliable. // if (NT_SUCCESS( Status )) { FsRtlNotifyVolumeEvent( IrpSp->FileObject, FSRTL_VOLUME_UNLOCK ); } // // Complete the request if there haven't been any exceptions. // UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Local support routine // NTSTATUS UdfDismountVolume ( 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. We only dismount a volume which has been locked. The intent here is that someone has locked the volume (they are the only remaining handle). We set the volume state to invalid so that it will be torn down quickly. Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PVCB Vcb; PFCB Fcb; PCCB Ccb; PAGED_CODE(); if (UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb ) != UserVolumeOpen ) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } DebugTrace(( +1, Dbg, "UdfDismountVolume()\n")); Vcb = Fcb->Vcb; // // Make this request waitable. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT); // // Acquire exclusive access to the Vcb, and take the global resource // to sync. against mounts, verifies etc. // UdfAcquireUdfData( IrpContext); UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE ); // // Mark the volume as invalid, but only do it if the vcb is locked // by this handle and the volume is currently mounted. No more // operations will occur on this vcb except cleanup/close. // if (Vcb->VcbCondition != VcbMounted) { Status = STATUS_VOLUME_DISMOUNTED; } else { // // Invalidate the volume right now. // // The intent here is to make every subsequent operation // on the volume fail and grease the rails toward dismount. // UdfLockVcb( IrpContext, Vcb ); if (Vcb->VcbCondition != VcbDismountInProgress) { Vcb->VcbCondition = VcbInvalid; } UdfUnlockVcb( IrpContext, Vcb ); // // Set flag to tell the close path that we want to force dismount // the volume when this handle is closed. // SetFlag( Ccb->Flags, CCB_FLAG_DISMOUNT_ON_CLOSE); Status = STATUS_SUCCESS; } // // Release all of our resources // UdfReleaseVcb( IrpContext, Vcb ); UdfReleaseUdfData( IrpContext); DebugTrace(( -1, Dbg, "UdfDismountVolume() -> 0x%x\n", Status)); // // Complete the request if there haven't been any exceptions. // UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } NTSTATUS UdfAllowExtendedDasdIo( 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; NTSTATUS Status = STATUS_SUCCESS; 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 (UserVolumeOpen != UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb )) { Status = STATUS_INVALID_PARAMETER; } else { SetFlag( Ccb->Flags, CCB_FLAG_ALLOW_EXTENDED_DASD_IO ); } UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Local support routine // UdfIsVolumeDirty ( 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. // if (Irp->AssociatedIrp.SystemBuffer != NULL) { VolumeState = Irp->AssociatedIrp.SystemBuffer; } else { UdfCompleteRequest( 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)) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } *VolumeState = 0; // // Decode the file object // TypeOfOpen = UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb ); if (TypeOfOpen != UserVolumeOpen) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } if (Fcb->Vcb->VcbCondition != VcbMounted) { UdfCompleteRequest( IrpContext, Irp, STATUS_VOLUME_DISMOUNTED ); return STATUS_VOLUME_DISMOUNTED; } // // Now set up to return the clean state. If we paid attention to the dirty // state of the media we could be more accurate, but since this is a readonly // implementation at the moment we think it is clean all of the time. // Irp->IoStatus.Information = sizeof( ULONG ); UdfCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } // // Local support routine // NTSTATUS UdfIsVolumeMounted ( 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 --*/ { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PFCB Fcb; PCCB Ccb; PAGED_CODE(); // // Decode the file object. // UdfDecodeFileObject( IrpSp->FileObject, &Fcb, &Ccb ); if (Fcb != NULL) { // // Disable PopUps, we want to return any error. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DISABLE_POPUPS ); // // Verify the Vcb. This will raise in the error condition. // UdfVerifyVcb( IrpContext, Fcb->Vcb ); } UdfCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } // // Local support routine // NTSTATUS UdfIsPathnameValid ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine determines if pathname is a valid UDFS pathname. We always succeed this request. Arguments: Irp - Supplies the Irp to process. Return Value: None --*/ { PAGED_CODE(); UdfCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } // // Local support routine // NTSTATUS UdfInvalidateVolumes ( IN PIRP_CONTEXT IrpContext, 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; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); KIRQL SavedIrql; BOOLEAN UnlockVcb = FALSE; LUID TcbPrivilege = {SE_TCB_PRIVILEGE, 0}; HANDLE Handle; PVCB Vcb; PLIST_ENTRY Links; PFILE_OBJECT FileToMarkBad; PDEVICE_OBJECT DeviceToMarkBad; // // We only allow this operation to be sent to our file system devices. // if (!UdfDeviceIsFsDo( IrpSp->DeviceObject)) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); return STATUS_INVALID_DEVICE_REQUEST; } // // Check for the correct security access. // The caller must have the SeTcbPrivilege. // if (!SeSinglePrivilegeCheck( TcbPrivilege, Irp->RequestorMode )) { UdfCompleteRequest( IrpContext, Irp, 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 )) { UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } Handle = (HANDLE) LongToHandle( *((PUINT32) Irp->AssociatedIrp.SystemBuffer) ); } else { #endif if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof( HANDLE )) { UdfCompleteRequest( IrpContext, Irp, 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)) { UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Grab the DeviceObject from the FileObject. // DeviceToMarkBad = FileToMarkBad->DeviceObject; // // We only needed the device object involved, not a reference to the file. // ObDereferenceObject( FileToMarkBad ); // // Make sure this request can wait. // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST ); UdfAcquireUdfData( IrpContext ); // // Nothing can go wrong now. // // // Now walk through all the mounted Vcb's looking for candidates to // mark invalid. // // On volumes we mark invalid, check for dismount possibility (which is // why we have to get the next link so early). // Links = UdfData.VcbQueue.Flink; while (Links != &UdfData.VcbQueue) { Vcb = 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. // UdfLockVcb( IrpContext, Vcb ); if (Vcb->Vpb->RealDevice == DeviceToMarkBad) { // // Take the VPB spinlock, and look to see if this volume is the // one currently mounted on the actual device. If it is, pull it // off immediately. // IoAcquireVpbSpinLock( &SavedIrql ); if (DeviceToMarkBad->Vpb == Vcb->Vpb) { PVPB NewVpb = Vcb->SwapVpb; ASSERT( FlagOn( Vcb->Vpb->Flags, VPB_MOUNTED)); ASSERT( NULL != NewVpb); 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; Vcb->SwapVpb = NULL; } IoReleaseVpbSpinLock( SavedIrql ); if (Vcb->VcbCondition != VcbDismountInProgress) { UdfSetVcbCondition( Vcb, VcbInvalid); } UdfUnlockVcb( IrpContext, Vcb ); UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE); UdfPurgeVolume( IrpContext, Vcb, FALSE ); UnlockVcb = UdfCheckForDismount( IrpContext, Vcb, FALSE ); if (UnlockVcb) { UdfReleaseVcb( IrpContext, Vcb); } } else { UdfUnlockVcb( IrpContext, Vcb ); } } UdfReleaseUdfData( IrpContext ); UdfCompleteRequest( IrpContext, Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } VOID UdfRemountOldVcb( IN PIRP_CONTEXT IrpContext, IN PVCB OldVcb, IN PVCB NewVcb, IN PDEVICE_OBJECT DeviceObjectWeTalkTo ) { KIRQL SavedIrql; ObDereferenceObject( OldVcb->TargetDeviceObject ); IoAcquireVpbSpinLock( &SavedIrql); NewVcb->Vpb->RealDevice->Vpb = OldVcb->Vpb; OldVcb->Vpb->RealDevice = NewVcb->Vpb->RealDevice; OldVcb->TargetDeviceObject = DeviceObjectWeTalkTo; UdfSetVcbCondition( OldVcb, VcbMounted); UdfSetMediaChangeCount( OldVcb, NewVcb->MediaChangeCount); ClearFlag( OldVcb->VcbState, VCB_STATE_VPB_NOT_ON_DEVICE); IoReleaseVpbSpinLock( SavedIrql); } // // Local support routine // NTSTATUS UdfMountVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ 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 UDF volume, and create the VCB and root directory FCB structures. The algorithm it uses is essentially as follows: 1. Create a new Vcb Structure, and initialize it enough to do I/O through the on-disk volume descriptors. 2. Read the disk and check if it is a UDF volume. 3. If it is not a UDF volume then 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 deleting the VCB, hook in the old VCB, and complete the IRP. 5. Otherwise create a Vcb and root directory FCB Arguments: Irp - Supplies the Irp to process Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PVOLUME_DEVICE_OBJECT VolDo = NULL; PVCB Vcb = NULL; PVCB OldVcb = NULL; PPCB Pcb = NULL; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PDEVICE_OBJECT DeviceObjectWeTalkTo = IrpSp->Parameters.MountVolume.DeviceObject; PVPB Vpb = IrpSp->Parameters.MountVolume.Vpb; PFILE_OBJECT FileObjectToNotify = NULL; ULONG MediaChangeCount = 0; DISK_GEOMETRY DiskGeometry; PNSR_ANCHOR AnchorVolumeDescriptor = NULL; PNSR_PVD PrimaryVolumeDescriptor = NULL; PNSR_LVOL LogicalVolumeDescriptor = NULL; PNSR_FSD FileSetDescriptor = NULL; BOOLEAN BridgeMedia; BOOLEAN SetDoVerifyOnFail; USHORT NSRVerFound = UDF_NSR_NO_VRS_FOUND; ULONG BoundS; ULONG BoundN; PAGED_CODE(); // // Check the input parameters // ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); // // Check that we are talking to a Cdrom or Disk device. This request should // always be waitable. // ASSERT( Vpb->RealDevice->DeviceType == FILE_DEVICE_CD_ROM || Vpb->RealDevice->DeviceType == FILE_DEVICE_DISK || Vpb->RealDevice->DeviceType == FILE_DEVICE_VIRTUAL_DISK ); ASSERT( FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )); DebugTrace(( +1, Dbg | UDFS_DEBUG_LEVEL_VERFYSUP, "UdfMountVolume (Vpb %p, Dev %p)\n", Vpb, Vpb->RealDevice)); // // Update the real device in the IrpContext from the Vpb. There was no available // file object when the IrpContext was created. // IrpContext->RealDevice = Vpb->RealDevice; SetDoVerifyOnFail = UdfRealDevNeedsVerify( IrpContext->RealDevice); // // Check if we have disabled the mount process. // if (UdfDisable) { UdfCompleteRequest( IrpContext, Irp, STATUS_UNRECOGNIZED_VOLUME ); DebugTrace(( 0, Dbg, "UdfMountVolume, disabled\n" )); DebugTrace(( -1, Dbg, "UdfMountVolume -> STATUS_UNRECOGNIZED_VOLUME\n" )); return STATUS_UNRECOGNIZED_VOLUME; } // // Don't even attempt to mount floppy discs // if (FlagOn( Vpb->RealDevice->Characteristics, FILE_FLOPPY_DISKETTE)) { UdfCompleteRequest( IrpContext, Irp, STATUS_UNRECOGNIZED_VOLUME ); return STATUS_UNRECOGNIZED_VOLUME; } // // Do a CheckVerify here to lift the MediaChange ticker from the driver // Status = UdfPerformDevIoCtrl( IrpContext, ( Vpb->RealDevice->DeviceType == FILE_DEVICE_CD_ROM ? IOCTL_CDROM_CHECK_VERIFY : IOCTL_DISK_CHECK_VERIFY ), DeviceObjectWeTalkTo, NULL, 0, &MediaChangeCount, sizeof(ULONG), FALSE, TRUE, NULL ); if (!NT_SUCCESS( Status )) { UdfCompleteRequest( IrpContext, Irp, Status ); DebugTrace(( 0, Dbg, "UdfMountVolume, CHECK_VERIFY handed back status %08x (so don't continue)\n", Status )); DebugTrace(( -1, Dbg, "UdfMountVolume -> %08x\n", Status )); return Status; } // // Now let's make Jeff delirious and call to get the disk geometry. This // will fix the case where the first change line is swallowed. // // This IOCTL does not have a generic STORAGE equivalent, so we must figure // our which variant to pass down from the real underlying device object (as // opposed to the top of the driver filter stack we will really be attaching // on top of). // Status = UdfPerformDevIoCtrl( IrpContext, ( Vpb->RealDevice->DeviceType == FILE_DEVICE_CD_ROM ? IOCTL_CDROM_GET_DRIVE_GEOMETRY : IOCTL_DISK_GET_DRIVE_GEOMETRY ), DeviceObjectWeTalkTo, NULL, 0, &DiskGeometry, sizeof( DISK_GEOMETRY ), FALSE, TRUE, NULL ); // // If this call failed, we might be able to get away with a heuristic guess as to // what the sector size is (per CDFS), but that is playing with fire. Nearly every // failure here will be a permanent problem of some form. // if (!NT_SUCCESS( Status )) { UdfCompleteRequest( IrpContext, Irp, Status ); DebugTrace(( 0, Dbg, "UdfMountVolume, GET_DRIVE_GEOMETRY failed\n" )); DebugTrace(( -1, Dbg, "UdfMountVolume -> %08x\n", Status )); return Status; } // // Acquire the global resource to do mount operations. // UdfAcquireUdfData( IrpContext ); // // Use a try-finally to facilitate cleanup. // try { // // Do a quick check to see if there any Vcb's which can be removed. // UdfScanForDismountedVcb( IrpContext ); // // Make sure that the driver/drive is not screwing up underneath of us by // feeding us garbage for the sector size. // if (DiskGeometry.BytesPerSector == 0 || (DiskGeometry.BytesPerSector & ~( 1 << UdfHighBit( DiskGeometry.BytesPerSector ))) != 0) { DebugTrace(( 0, 0, "UdfMountVolume, bad DiskGeometry (%08x) .BytesPerSector == %08x\n", &DiskGeometry, DiskGeometry.BytesPerSector )); ASSERT( FALSE ); try_leave( Status = STATUS_DRIVER_INTERNAL_ERROR ); } // // Now find the multi-session bounds on this media. // UdfDetermineVolumeBounding( IrpContext, DeviceObjectWeTalkTo, &BoundS, &BoundN ); // // Now go confirm that this volume may be a UDF image by looking for a // valid ISO 13346 Volume Recognition Sequence in the last and first // sessions. // if (!UdfRecognizeVolume( IrpContext, DeviceObjectWeTalkTo, DiskGeometry.BytesPerSector, &BoundS, &BridgeMedia, &NSRVerFound)) { #ifdef EXPERIMENTAL_MOUNT_OPEN_R_MEDIA if (DeviceObjectWeTalkTo->DeviceType == FILE_DEVICE_CD_ROM) { DebugTrace(( 0, Dbg, "UdfMountVolume, recognition failed but continuing to look for open R volume\n")); } else #endif { DebugTrace(( 0, Dbg, "UdfMountVolume, recognition failed so not mounting\n" )); try_leave( Status = STATUS_UNRECOGNIZED_VOLUME ); } } // // Create the DeviceObject for this mount attempt // Status = IoCreateDevice( UdfData.DriverObject, sizeof( VOLUME_DEVICE_OBJECT ) - sizeof( DEVICE_OBJECT ), NULL, FILE_DEVICE_CD_ROM_FILE_SYSTEM, 0, FALSE, (PDEVICE_OBJECT *) &VolDo ); if (!NT_SUCCESS( Status )) { DebugTrace(( 0, Dbg, "UdfMountVolume, couldn't get voldo! (%08x)\n", Status )); try_leave( Status ); } // // Our alignment requirement is the larger of the processor alignment requirement // already in the volume device object and that in the DeviceObjectWeTalkTo // if (DeviceObjectWeTalkTo->AlignmentRequirement > VolDo->DeviceObject.AlignmentRequirement) { VolDo->DeviceObject.AlignmentRequirement = DeviceObjectWeTalkTo->AlignmentRequirement; } // // Initialize the overflow queue for the volume // VolDo->OverflowQueueCount = 0; InitializeListHead( &VolDo->OverflowQueue ); VolDo->PostedRequestCount = 0; KeInitializeSpinLock( &VolDo->OverflowQueueSpinLock ); // // 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. // Vpb->DeviceObject = (PDEVICE_OBJECT) VolDo; // // Initialize the Vcb. This routine will raise on an allocation // failure. // UdfInitializeVcb( IrpContext, &VolDo->Vcb, DeviceObjectWeTalkTo, Vpb, &DiskGeometry, MediaChangeCount ); // // We must initialize the stack size in our device object before // the following reads, because the I/O system has not done it yet. // ((PDEVICE_OBJECT) VolDo)->StackSize = (CCHAR) (DeviceObjectWeTalkTo->StackSize + 1); // // Set the correct sector size. IO defaults to 512b for DISK_FS and 2k for // CDROM_FS.... // ((PDEVICE_OBJECT) VolDo)->SectorSize = (USHORT) DiskGeometry.BytesPerSector; ClearFlag( VolDo->DeviceObject.Flags, DO_DEVICE_INITIALIZING ); // // Pick up a local pointer to the new Vcb. Here is where we start // thinking about cleanup of structures if the mount is failed. // Vcb = &VolDo->Vcb; Vpb = NULL; VolDo = NULL; // // Store the session bounds we determined earlier. // Vcb->BoundS = BoundS; Vcb->BoundN = BoundN; // // Store the Vcb in the IrpContext as we didn't have one before. // IrpContext->Vcb = Vcb; UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE ); // // Store the NSR version that we found // Vcb->NsrVersion = NSRVerFound; // // Let's reference the Vpb to make sure we are the one to // have the last dereference. // Vcb->Vpb->ReferenceCount += 1; // // Clear the verify bit for the start of mount. // UdfMarkRealDevVerifyOk( Vcb->Vpb->RealDevice); // // Now find the Anchor Volume Descriptor so we can discover the Volume Set // Descriptor Sequence extent. // Status = UdfFindAnchorVolumeDescriptor( IrpContext, Vcb, &AnchorVolumeDescriptor ); if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfMountVolume, couldn't find anchor descriptors\n" )); try_leave( Status ); } // // Now search for the prevailing copies of the PVD, LVD, and related PD in the // extents indicated by the AVD. // Status = UdfFindVolumeDescriptors( IrpContext, Vcb, &AnchorVolumeDescriptor->Main, &Pcb, &PrimaryVolumeDescriptor, &LogicalVolumeDescriptor ); // // If we discovered invalid structures on the main extent, we may still // be able to use the reserve extent. By definition the two extents // must be logically equal, so just plow into it on any error. // if (!NT_SUCCESS( Status )) { Status = UdfFindVolumeDescriptors( IrpContext, Vcb, &AnchorVolumeDescriptor->Reserve, &Pcb, &PrimaryVolumeDescriptor, &LogicalVolumeDescriptor ); } if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfMountVolume, couldn't find good VSD descriptors (PVD/LVD/PD) status %X\n", Status )); try_leave( Status ); } // // Now go complete initialization of the Pcb. After this point, we can perform // physical partition mappings and know that the partition table is good. // Status = UdfCompletePcb( IrpContext, Vcb, Pcb ); if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfMountVolume, Pcb completion failed\n" )); try_leave( Status ); } Vcb->Pcb = Pcb; Pcb = NULL; // // Set up all the support we need to do reads into the volume. // UdfUpdateVcbPhase0( IrpContext, Vcb ); // // Now go get the fileset descriptor that will finally reveal the location // of the root directory on this volume. // Status = UdfFindFileSetDescriptor( IrpContext, Vcb, &LogicalVolumeDescriptor->FSD, &FileSetDescriptor ); if (!NT_SUCCESS(Status)) { try_leave( NOTHING ); } // // Now that we have everything together, update the Vpb with identification // of this volume. // UdfUpdateVolumeLabel( IrpContext, Vcb->Vpb->VolumeLabel, &Vcb->Vpb->VolumeLabelLength, LogicalVolumeDescriptor->VolumeID, sizeof( LogicalVolumeDescriptor->VolumeID )); UdfUpdateVolumeSerialNumber( IrpContext, &Vcb->Vpb->SerialNumber, FileSetDescriptor ); // // Check if this is a remount operation. If so then clean up // the data structures passed in and created here. // if (UdfIsRemount( IrpContext, Vcb, &OldVcb )) { KIRQL SavedIrql; DebugTrace((0, Dbg | UDFS_DEBUG_LEVEL_VERFYSUP, "Remounting Vcb %p (Vpb %p)\n", OldVcb , OldVcb->Vpb)); // // Link the old Vcb to point to the new device object that we // should be talking to, dereferencing the previous. Call a nonpaged // routine to do this since we take the Vpb spinlock. // UdfRemountOldVcb( IrpContext, OldVcb, Vcb, DeviceObjectWeTalkTo); // // Push the state of the method 2 bit across. In changing the device, // we may now be on one with a different requirement. // ClearFlag( OldVcb->VcbState, VCB_STATE_METHOD_2_FIXUP ); SetFlag( OldVcb->VcbState, FlagOn( Vcb->VcbState, VCB_STATE_METHOD_2_FIXUP )); // // See if we will need to provide notification of the remount. This is the readonly // filesystem's form of dismount/mount notification - we promise that whenever a // volume is "dismounted", that a mount notification will occur when it is revalidated. // Note that we do not send mount on normal remounts - that would duplicate the media // arrival notification of the device driver. // if (FlagOn( OldVcb->VcbState, VCB_STATE_NOTIFY_REMOUNT )) { ClearFlag( OldVcb->VcbState, VCB_STATE_NOTIFY_REMOUNT ); FileObjectToNotify = OldVcb->RootIndexFcb->FileObject; ObReferenceObject( FileObjectToNotify ); } DebugTrace(( 0, Dbg, "UdfMountVolume, remounted old Vcb %08x\n", OldVcb )); try_leave( Status = STATUS_SUCCESS ); } // // Initialize the Vcb and associated structures from our volume descriptors // UdfUpdateVcbPhase1( IrpContext, Vcb, FileSetDescriptor ); // // Drop an extra reference on the root dir file so we'll be able to send // notification. // if (Vcb->RootIndexFcb) { FileObjectToNotify = Vcb->RootIndexFcb->FileObject; ObReferenceObject( FileObjectToNotify ); } // // The new mount is complete. Remove the additional references on this // Vcb since, at this point, we have added the real references this volume // will have during its lifetime. We also need to drop the additional // reference on the device we mounted. // Vcb->VcbReference -= Vcb->VcbResidualReference; ASSERT( Vcb->VcbReference == Vcb->VcbResidualReference ); ObDereferenceObject( Vcb->TargetDeviceObject ); UdfSetVcbCondition( Vcb, VcbMounted); UdfReleaseVcb( IrpContext, Vcb ); Vcb = NULL; Status = STATUS_SUCCESS; } finally { DebugUnwind( "UdfMountVolume" ); // // If we are not mounting the device, then set the verify bit again. // if ((AbnormalTermination() || (Status != STATUS_SUCCESS)) && SetDoVerifyOnFail) { UdfMarkRealDevForVerify( IrpContext->RealDevice); } // // If we didn't complete the mount then cleanup any remaining structures. // if (Vpb != NULL) { Vpb->DeviceObject = NULL; } if (Pcb != NULL) { UdfDeletePcb( Pcb ); } if (Vcb != NULL) { // // Make sure there is no Vcb in the IrpContext since it could go away // IrpContext->Vcb = NULL; Vcb->VcbReference -= Vcb->VcbResidualReference; if (UdfDismountVcb( IrpContext, Vcb )) { UdfReleaseVcb( IrpContext, Vcb ); } } else if (VolDo != NULL) { IoDeleteDevice( (PDEVICE_OBJECT)VolDo ); Vpb->DeviceObject = NULL; } // // Release the global resource. // UdfReleaseUdfData( IrpContext ); // // Free any structures we may have been allocated // UdfFreePool( &AnchorVolumeDescriptor ); UdfFreePool( &PrimaryVolumeDescriptor ); UdfFreePool( &LogicalVolumeDescriptor ); UdfFreePool( &FileSetDescriptor ); } // // Now send mount notification. // if (FileObjectToNotify) { FsRtlNotifyVolumeEvent( FileObjectToNotify, FSRTL_VOLUME_MOUNT ); ObDereferenceObject( FileObjectToNotify ); } // // Complete the request if no exception. // UdfCompleteRequest( IrpContext, Irp, Status ); DebugTrace(( -1, Dbg, "UdfMountVolume -> %08x\n", Status )); return Status; } // // Local support routine // NTSTATUS UdfVerifyVolume ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp ) /*++ Routine Description: This routine performs the verify 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 = IoGetCurrentIrpStackLocation( Irp ); PVPB Vpb = IrpSp->Parameters.VerifyVolume.Vpb; PVCB Vcb = &((PVOLUME_DEVICE_OBJECT) IrpSp->Parameters.VerifyVolume.DeviceObject)->Vcb; PPCB Pcb = NULL; PNSR_ANCHOR AnchorVolumeDescriptor = NULL; PNSR_PVD PrimaryVolumeDescriptor = NULL; PNSR_LVOL LogicalVolumeDescriptor = NULL; PNSR_FSD FileSetDescriptor = NULL; ULONG MediaChangeCount = Vcb->MediaChangeCount; ULONG Index; PFILE_OBJECT FileObjectToNotify = NULL; BOOLEAN ReturnError; BOOLEAN ReleaseVcb = FALSE; IO_STATUS_BLOCK Iosb; WCHAR VolumeLabel[ MAXIMUM_VOLUME_LABEL_LENGTH / sizeof( WCHAR )]; USHORT VolumeLabelLength; ULONG VolumeSerialNumber; NTSTATUS Status; PAGED_CODE(); // // Check input. // ASSERT_IRP_CONTEXT( IrpContext ); // // Check that we are talking to a Cdrom or Disk device. This request should // always be waitable. // ASSERT( Vpb->RealDevice->DeviceType == FILE_DEVICE_CD_ROM || Vpb->RealDevice->DeviceType == FILE_DEVICE_DISK ); ASSERT_VCB( Vcb ); ASSERT( FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )); // // Update the real device in the IrpContext from the Vpb. There was no available // file object when the IrpContext was created. // IrpContext->RealDevice = Vpb->RealDevice; DebugTrace(( +1, Dbg, "UdfVerifyVolume, Vcb %08x\n", Vcb )); // // Acquire the global to synchronise against mounts and teardown. // UdfAcquireUdfData( IrpContext ); try { UdfAcquireVcbExclusive( IrpContext, Vcb, FALSE ); ReleaseVcb = TRUE; // // Verify that there is a disk here. // Status = UdfPerformDevIoCtrl( IrpContext, ( Vpb->RealDevice->DeviceType == FILE_DEVICE_CD_ROM ? IOCTL_CDROM_CHECK_VERIFY : IOCTL_DISK_CHECK_VERIFY ), Vcb->TargetDeviceObject, NULL, 0, &MediaChangeCount, sizeof(ULONG), FALSE, TRUE, &Iosb ); if (!NT_SUCCESS( Status )) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, CHECK_VERIFY failed\n" )); // // If we will allow a raw mount then return WRONG_VOLUME to // allow the volume to be mounted by raw. // if (FlagOn( IrpSp->Flags, SL_ALLOW_RAW_MOUNT )) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, ... allowing raw mount\n" )); Status = STATUS_WRONG_VOLUME; } try_leave( Status ); } if (Iosb.Information != sizeof(ULONG)) { // // Be safe about the count in case the driver didn't fill it in // MediaChangeCount = 0; } // // Verify that the device actually saw a change. If the driver does not // support the MCC, then we must verify the volume in any case. // if (MediaChangeCount == 0 || (Vcb->MediaChangeCount != MediaChangeCount)) { // // Now we need to navigate the disc to find the relavent decriptors. This is // much the same as the mount process. // // // Find the AVD. // Status = UdfFindAnchorVolumeDescriptor( IrpContext, Vcb, &AnchorVolumeDescriptor ); if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, No AVD visible\n" )); try_leave( Status = STATUS_WRONG_VOLUME ); } // // Get the prevailing descriptors out of the VDS, building a fresh Pcb. // Status = UdfFindVolumeDescriptors( IrpContext, Vcb, &AnchorVolumeDescriptor->Main, &Pcb, &PrimaryVolumeDescriptor, &LogicalVolumeDescriptor ); // // Try the reserve sequence in case of error. // if (Status == STATUS_DISK_CORRUPT_ERROR) { Status = UdfFindVolumeDescriptors( IrpContext, Vcb, &AnchorVolumeDescriptor->Reserve, &Pcb, &PrimaryVolumeDescriptor, &LogicalVolumeDescriptor ); } // // If we're totally unable to find a VDS, give up. // if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, PVD/LVD/PD pickup failed\n" )); try_leave( Status = STATUS_WRONG_VOLUME ); } // // Now go complete initialization of the Pcb so we can compare it. // Status = UdfCompletePcb( IrpContext, Vcb, Pcb ); if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, Pcb completion failed\n" )); try_leave( Status = STATUS_WRONG_VOLUME ); } // // Now let's compare this new Pcb to the previous Vcb's Pcb to see if they // appear to be equivalent. // if (!UdfEquivalentPcb( IrpContext, Pcb, Vcb->Pcb)) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, Pcbs are not equivalent\n" )); try_leave( Status = STATUS_WRONG_VOLUME ); } // // At this point we know that the Vcb's Pcb is OK for mapping to find the fileset // descriptor, so we can drop the new one we built for comparison purposes. // UdfDeletePcb( Pcb ); Pcb = NULL; // // Go pick up the fileset descriptor. // Status = UdfFindFileSetDescriptor( IrpContext, Vcb, &LogicalVolumeDescriptor->FSD, &FileSetDescriptor ); if (!NT_SUCCESS(Status)) { try_leave( Status = STATUS_WRONG_VOLUME ); } // // Now that everything is in place, build a volume label and serial number from these // descriptors and perform the final check that this Vcb is (or is not) the right one // for the media now in the drive. // UdfUpdateVolumeLabel( IrpContext, VolumeLabel, &VolumeLabelLength, LogicalVolumeDescriptor->VolumeID, sizeof( LogicalVolumeDescriptor->VolumeID )); UdfUpdateVolumeSerialNumber( IrpContext, &VolumeSerialNumber, FileSetDescriptor ); if ((Vcb->Vpb->SerialNumber != VolumeSerialNumber) || (Vcb->Vpb->VolumeLabelLength != VolumeLabelLength) || (VolumeLabelLength != RtlCompareMemory( Vcb->Vpb->VolumeLabel, VolumeLabel, VolumeLabelLength))) { DebugTrace(( 0, Dbg, "UdfVerifyVolume, volume label/sn mismatch\n" )); try_leave( Status = STATUS_WRONG_VOLUME ); } } // // The volume is OK, clear the verify bit. // DebugTrace(( 0, Dbg, "UdfVerifyVolume, looks like the same volume\n" )); UdfSetVcbCondition( Vcb, VcbMounted); UdfMarkRealDevVerifyOk( Vpb->RealDevice); // // See if we will need to provide notification of the remount. This is the readonly // filesystem's form of dismount/mount notification. // if (FlagOn( Vcb->VcbState, VCB_STATE_NOTIFY_REMOUNT )) { ClearFlag( Vcb->VcbState, VCB_STATE_NOTIFY_REMOUNT ); FileObjectToNotify = Vcb->RootIndexFcb->FileObject; ObReferenceObject( FileObjectToNotify ); } } finally { // // If we did not raise an exception, update the current Vcb. // if (!AbnormalTermination()) { // // Update the media change count to note that we have verified the volume // at this value // UdfSetMediaChangeCount( Vcb, MediaChangeCount); // // Mark the Vcb as not mounted. // if (Status == STATUS_WRONG_VOLUME) { UdfSetVcbCondition( Vcb, VcbNotMounted); // // Now, if there are no user handles to the volume, try to spark // teardown by purging the volume. // if (Vcb->VcbCleanup == 0) { if (NT_SUCCESS( UdfPurgeVolume( IrpContext, Vcb, FALSE ))) { ReleaseVcb = UdfCheckForDismount( IrpContext, Vcb, FALSE ); } } } } DebugTrace(( -1, Dbg, "UdfVerifyVolume -> %08x\n", Status )); if (ReleaseVcb) { UdfReleaseVcb( IrpContext, Vcb ); } UdfReleaseUdfData( IrpContext ); // // Delete the Pcb if built. // if (Pcb != NULL) { UdfDeletePcb( Pcb ); } UdfFreePool( &AnchorVolumeDescriptor ); UdfFreePool( &PrimaryVolumeDescriptor ); UdfFreePool( &LogicalVolumeDescriptor ); UdfFreePool( &FileSetDescriptor ); } // // Now send mount notification. // if (FileObjectToNotify) { FsRtlNotifyVolumeEvent( FileObjectToNotify, FSRTL_VOLUME_MOUNT ); ObDereferenceObject( FileObjectToNotify ); } // // Complete the request if no exception. // UdfCompleteRequest( IrpContext, Irp, Status ); return Status; } // // Local support routine // BOOLEAN UdfIsRemount ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, OUT PVCB *OldVcb ) /*++ Routine Description: This routine walks through the links of the Vcb chain in the global data structure. The remount condition is met when the following conditions are all met: 1 - The 32 serial for this VPB matches that in a previous VPB. 2 - The volume label for this VPB matches that in the previous VPB. 3 - The system pointer to the real device object in the current VPB matches that in the same previous VPB. 4 - Finally the previous Vcb cannot be invalid or have a dismount underway. If a VPB is found which matches these conditions, then the address of the Vcb for that VPB is returned via the pointer OldVcb. Skip over the current Vcb. Arguments: Vcb - This is the Vcb we are checking for a remount. OldVcb - A pointer to the address to store the address for the Vcb for the volume if this is a remount. (This is a pointer to a pointer) Return Value: BOOLEAN - TRUE if this is in fact a remount, FALSE otherwise. --*/ { PLIST_ENTRY Link; PVPB Vpb = Vcb->Vpb; PVPB OldVpb; BOOLEAN Remount = FALSE; PAGED_CODE(); // // Check input. // ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_VCB( Vcb ); DebugTrace(( +1, Dbg, "UdfIsRemount, Vcb %08x\n", Vcb )); for (Link = UdfData.VcbQueue.Flink; Link != &UdfData.VcbQueue; Link = Link->Flink) { *OldVcb = CONTAINING_RECORD( Link, VCB, VcbLinks ); // // Skip ourselves. // if (Vcb == *OldVcb) { continue; } // // Look at the Vpb and state of the previous Vcb. // OldVpb = (*OldVcb)->Vpb; if ((OldVpb != Vpb) && (OldVpb->RealDevice == Vpb->RealDevice) && ((*OldVcb)->VcbCondition == VcbNotMounted)) { // // Go ahead and compare serial numbers and volume label. // if ((OldVpb->SerialNumber == Vpb->SerialNumber) && (Vpb->VolumeLabelLength == OldVpb->VolumeLabelLength) && (RtlEqualMemory( OldVpb->VolumeLabel, Vpb->VolumeLabel, Vpb->VolumeLabelLength ))) { // // Got it. // DebugTrace(( 0, Dbg, "UdfIsRemount, matched OldVcb %08x\n", *OldVcb )); Remount = TRUE; break; } } } DebugTrace(( -1, Dbg, "UdfIsRemount -> %c\n", (Remount? 'T' : 'F' ))); return Remount; } // // Local support routine // NTSTATUS UdfFindFileSetDescriptor ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PLONGAD LongAd, IN OUT PNSR_FSD *FileSetDescriptor ) /*++ Routine Description: This routine walks a Fileset Descriptor Sequence looking for the default descriptor. This will reveal the location of the root directory on the volume. Arguments: Vcb - Vcb of volume to search LongAd - Long allocation descriptor describing the start of the sequence FileSetDescriptor - Address of caller's pointer to an FSD Return Value: STATUS_SUCCESS if all descriptors are found, read, and are valid. STATUS_DISK_CORRUPT_ERROR if corrupt/bad descriptors are found (may be raised) --*/ { PNSR_FSD FSD = NULL; ULONGLONG Offset; ULONG Lbn, Len; NTSTATUS Status = STATUS_SUCCESS; PAGED_CODE(); // // Check inputs // ASSERT_IRP_CONTEXT( IrpContext ); ASSERT( *FileSetDescriptor == NULL ); DebugTrace(( +1, Dbg, "UdfFindFileSetDescriptor, Vcb %08x, LongAd %08x %x/%08x +%08x (type %x)\n", Vcb, LongAd, LongAd->Start.Partition, LongAd->Start.Lbn, LongAd->Length.Length, LongAd->Length.Type )); // // If the extent we begin from is not a whole number of recorded logical blocks, // we can't continue. // if (LongAd->Length.Length == 0 || LongAd->Length.Type != NSRLENGTH_TYPE_RECORDED || BlockOffset( Vcb, LongAd->Length.Length )) { DebugTrace(( +0, Dbg, "UdfFindFileSetDescriptor, bad longad length\n" )); DebugTrace(( -1, Dbg, "UdfFindFileSetDescriptor -> STATUS_DISK_CORRUPT_ERROR\n" )); return STATUS_DISK_CORRUPT_ERROR; } // // Use a try-finally for cleanup // try { try { for ( // // Home ourselves in the search and make a pass through the sequence. // Len = LongAd->Length.Length, Lbn = LongAd->Start.Lbn; Len; // // Advance to the next descriptor offset in the sequence. // Len -= BlockSize( Vcb ), Lbn++) { // // Allocate a buffer to read fileset descriptors. // if (FSD == NULL) { FSD = FsRtlAllocatePoolWithTag( UdfNonPagedPool, UdfRawBufferSize( Vcb, sizeof(NSR_FSD) ), TAG_NSR_FSD ); } // // Lookup the physical offset for this block. We could be mapping // through a VAT here so we can't just assume that all the // blocks in the extent are physically contiguous. The FSD seems to // be the exception here - there's nothing that says it must be in // physical partition, and it can have a terminator, => 2 blocks // minumum. There is no single block virtual extent limitation in UDF 1.50. // Offset = LlBytesFromSectors( Vcb, UdfLookupPsnOfExtent( IrpContext, Vcb, LongAd->Start.Partition, Lbn, BlockSize( Vcb))); Status = UdfReadSectors( IrpContext, Offset, UdfRawReadSize( Vcb, sizeof(NSR_FSD) ), TRUE, FSD, Vcb->TargetDeviceObject ); if (!NT_SUCCESS( Status ) || FSD->Destag.Ident == DESTAG_ID_NOTSPEC) { // // These are both an excellent sign that this is an unrecorded sector, which // is defined to terminate the sequence. (3/8.4.2) // break; } if ((FSD->Destag.Ident != DESTAG_ID_NSR_FSD && FSD->Destag.Ident != DESTAG_ID_NSR_TERM) || !UdfVerifyDescriptor( IrpContext, &FSD->Destag, FSD->Destag.Ident, sizeof(NSR_FSD), Lbn, TRUE)) { // // If we spot an illegal descriptor type in the stream, there is no reasonable // way to guess that we can continue (the disc may be trash beyond this point). // Clearly, we also cannot trust the next extent pointed to by a corrupt // descriptor. // try_leave( Status = STATUS_DISK_CORRUPT_ERROR ); } if (FSD->Destag.Ident == DESTAG_ID_NSR_TERM) { // // This is a way to terminate the sequence. // break; } // // Reset the pointers to the possible next extent // LongAd = &FSD->NextExtent; if (LongAd->Length.Length) { // // A fileset descriptor containing a nonzero next extent pointer also // terminates this extent of the FSD sequence. (4/8.3.1) // // If the extent referred to is not fully recorded, this will // terminate the sequence. // if (LongAd->Length.Type != NSRLENGTH_TYPE_RECORDED) { break; } Len = LongAd->Length.Length; // // The extent must be a multiple of a block size. // if (BlockOffset( Vcb, Len )) { DebugTrace(( +0, Dbg, "UdfFindFileSetDescriptor, interior extent not blocksize in length\n" )); try_leave ( Status = STATUS_DISK_CORRUPT_ERROR ); } Lbn = LongAd->Start.Lbn; // // Note that we must correct the values to take into account // the changes that will be made next time through the for loop. // Len += BlockSize( Vcb ); Lbn -= 1; } UdfStoreFileSetDescriptorIfPrevailing( FileSetDescriptor, &FSD ); } } finally { DebugUnwind( "UdfFindFileSetDescriptor"); // // Free up the buffer space we may have allocated // UdfFreePool( &FSD ); } } except( UdfExceptionFilter( IrpContext, GetExceptionInformation() )) { // // Transmute raised apparent file corruption to disk corruption - we are not // yet touching the visible filesystem. // Status = IrpContext->ExceptionStatus; DebugTrace(( +0, Dbg, "UdfFindFileSetDescriptor, exception %08x thrown\n", Status )); if (Status == STATUS_FILE_CORRUPT_ERROR) { DebugTrace(( +0, Dbg, "UdfFindFileSetDescriptor, translating file corrupt to disk corrupt\n" )); Status = STATUS_DISK_CORRUPT_ERROR; } } // // Success is when we've really found something. If we failed to find the // descriptor, commute whatever intermediate status was involved and clean up. // if (*FileSetDescriptor == NULL) { Status = STATUS_UNRECOGNIZED_VOLUME; } if (!NT_SUCCESS( Status )) { UdfFreePool( FileSetDescriptor ); } DebugTrace(( -1, Dbg, "UdfFindFileSetDescriptor -> %08x\n", Status )); return Status; } // // Local support routine // NTSTATUS UdfFindVolumeDescriptors ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PEXTENTAD Extent, IN OUT PPCB *Pcb, IN OUT PNSR_PVD *PrimaryVolumeDescriptor, IN OUT PNSR_LVOL *LogicalVolumeDescriptor ) /*++ Routine Description: This routine walks the indicated Volume Descriptor Sequence searching for the active descriptors for this volume and generates an initializing Pcb from the referenced partitions. No updating of the Vcb occurs. Arguments: Vcb - Vcb of volume to search Extent - Extent to search Pcb - Address of a caller's pointer to a Pcb PrimaryVolumeDescriptor - Address of caller's pointer to a PVD LogicalVolumeDescriptor - Address of caller's pointer to an LVD Return Value: STATUS_SUCCESS if all descriptors are found, read, and are valid. STATUS_DISK_CORRUPT_ERROR if corrupt descriptors are found. STATUS_UNRECOGNIZED_VOLUME if noncompliant descriptors are found. Descriptors are only returned on success. --*/ { PNSR_VD_GENERIC GenericVD = NULL; ULONGLONG Offset; ULONG Len; ULONG MaxSize; ULONG UnitSize = UdfRawReadSize( Vcb, sizeof(NSR_VD_GENERIC) ); NTSTATUS Status = STATUS_SUCCESS; ULONG ThisPass = 1; ULONG MaxVdpExtents; PAGED_CODE(); // // Check the input parameters // ASSERT_IRP_CONTEXT( IrpContext); ASSERT_VCB( Vcb ); ASSERT_OPTIONAL_PCB( *Pcb ); DebugTrace(( +1, Dbg, "UdfFindVolumeDescriptors, Vcb %08x, Extent %08x +%08x\n", Vcb, Extent->Lsn, Extent->Len )); // // If the extent we begin from is not at least the size of an aligned descriptor // or is sized in base units other than aligned descriptors, we can't continue. // if (Extent->Len < UnitSize || Extent->Len % UnitSize) { DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, Base extent length %08x is mismatched with read size %08x\n", Extent->Len, UnitSize )); DebugTrace(( -1, Dbg, "UdfFindVolumeDescriptors -> STATUS_DISK_CORRUPT_ERROR\n" )); return STATUS_DISK_CORRUPT_ERROR; } // // Use a try-finally to facilitate cleanup. // try { DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, starting pass 1, find LVD/PVD\n" )); // // We will make at least one pass through the Volume Descriptor Sequence to find // the prevailing versions of the two controlling descriptors - the PVD and LVD. // In order to avoid picking up partition descriptors that aren't actually going // to be referenced by the LVD, we will pick them up in a second pass if we find // a PVD and LVD that look reasonable and then stick them in a Pcb. // for (ThisPass = 1; ThisPass <= 2; ThisPass++) { MaxVdpExtents = 16; for ( // // Home ourselves in the search and make a pass through the sequence. // Offset = LlBytesFromSectors( Vcb, Extent->Lsn ), Len = Extent->Len; // // If we have reached the end of the extent's indicated valid // length, we are done. This usually will not happen. // Len; // // Advance to the next descriptor offset in the sequence. // Offset += UnitSize, Len -= UnitSize ) { // // Allocate a buffer to read generic volume descriptors. // if (GenericVD == NULL) { GenericVD = (PNSR_VD_GENERIC) FsRtlAllocatePoolWithTag( UdfNonPagedPool, UdfRawBufferSize( Vcb, sizeof(NSR_VD_GENERIC) ), TAG_NSR_VDSD ); } Status = UdfReadSectors( IrpContext, Offset, UnitSize, TRUE, GenericVD, Vcb->TargetDeviceObject ); // // Thise is a decent sign that this is an unrecorded sector and is // defined to terminate the sequence. // if (!NT_SUCCESS( Status )) { break; } // // Calculate the maximum size we expect this descriptor to be. For LVDs // the descriptor can be followed by upto 2 partition maps, pushing it // over the 512 byte ECMA desc. limit which we were assuming was the max. // MaxSize = sizeof( NSR_VD_GENERIC); if (DESTAG_ID_NSR_LVOL == GenericVD->Destag.Ident) { MaxSize += 2 * sizeof( PARTMAP_UDF_GENERIC); ASSERT( BlockSize( Vcb) >= 1024); } if (GenericVD->Destag.Ident > DESTAG_ID_MAXIMUM_PART3 || !UdfVerifyDescriptor( IrpContext, &GenericVD->Destag, GenericVD->Destag.Ident, MaxSize, (ULONG) SectorsFromLlBytes( Vcb, Offset ), TRUE)) { // // If we spot an illegal descriptor type in the stream, there is no reasonable // way to guess that we can continue (the disc may be trash beyond this point). // Likewise, even if we have a single corrupt descriptor we cannot continue because // this may be corruption of a descriptor we may have otherwise required for operation // (i.e., one of the prevailing descriptors). // DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, descriptor didn't verify\n" )); try_leave( Status = STATUS_DISK_CORRUPT_ERROR ); } if (GenericVD->Destag.Ident == DESTAG_ID_NSR_TERM) { // // The Terminating Descriptor (3/10.9) is the usual way to stop a search. // break; } if (GenericVD->Destag.Ident == DESTAG_ID_NSR_VDP) { // // Follow a Volume Desciptor Pointer (3/10.3) to the next extent of the sequence. // We will only follow a maximum of 16 extents, to guard against loops. // if (0 == --MaxVdpExtents) { try_leave( Status = STATUS_DISK_CORRUPT_ERROR ); } // // Bias the values by UnitSize, so that the next loop iteration will change them // to the correct values. // Offset = LlBytesFromSectors( Vcb, ((PNSR_VDP) GenericVD)->Next.Lsn ) - UnitSize; Len = ((PNSR_VDP) GenericVD)->Next.Len; // // We cannot do anything if the extent is invalid // if (Len < UnitSize || Len % UnitSize) { DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, following extent length %08x is mismatched with read size %08x\n", Extent->Len, UnitSize )); try_leave( Status = STATUS_DISK_CORRUPT_ERROR ); } Len += UnitSize; continue; } DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, descriptor tag %08x\n", GenericVD->Destag.Ident )); if (ThisPass == 1) { // // Our first pass is to find prevailing LVD and PVD. // switch (GenericVD->Destag.Ident) { case DESTAG_ID_NSR_PVD: UdfStoreVolumeDescriptorIfPrevailing( (PNSR_VD_GENERIC *) PrimaryVolumeDescriptor, GenericVD ); break; case DESTAG_ID_NSR_LVOL: UdfStoreVolumeDescriptorIfPrevailing( (PNSR_VD_GENERIC *) LogicalVolumeDescriptor, GenericVD ); break; default: break; } } else { PNSR_PART PartitionDescriptor = (PNSR_PART) GenericVD; USHORT ExpectedNsrVer; // // Our second pass is to pick up all relevant NSR02/3 PD // if (PartitionDescriptor->Destag.Ident != DESTAG_ID_NSR_PART) { continue; } // // Look at the NSR standard revision // if (UdfEqualEntityId( &PartitionDescriptor->ContentsID, &UdfNSR02Identifier, NULL )) { ExpectedNsrVer = VsdIdentNSR02; } else if (UdfEqualEntityId( &PartitionDescriptor->ContentsID, &UdfNSR03Identifier, NULL )) { ExpectedNsrVer = VsdIdentNSR03; } else { // // Unknown NSR revision // ExpectedNsrVer = VsdIdentBad; } // // Check that the NSR version in this PD matches what we found in the VRS earlier. // if (ExpectedNsrVer != Vcb->NsrVersion) { #ifdef EXPERIMENTAL_MOUNT_OPEN_R_MEDIA // // If we didn't find a VRS (i.e. open CD/DVD-R media) then we ignore this, since // we didn't have a VRS to infer the NSR version from. Just store what we // found here. // if (Vcb->NsrVersion == UDF_NSR_NO_VRS_FOUND) { Vcb->NsrVersion = ExpectedNsrVer; } else #endif { DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors: NSR version in PartitionDescriptor (%d) != NSR found in VRS (%d)\n", ExpectedNsrVer, Vcb->NsrVersion)); try_leave( Status = STATUS_UNRECOGNIZED_VOLUME ); } } UdfAddToPcb( *Pcb, (PNSR_PART) GenericVD ); } } // inner descriptor loop. // // Now that a pass through the VDS has been completed, analyze the results. // if (ThisPass == 1) { PNSR_PVD PVD; PNSR_LVOL LVD; USHORT MaxVerBasedOnNSR; // // Reference the descriptors for ease of use // PVD = *PrimaryVolumeDescriptor; LVD = *LogicalVolumeDescriptor; // // Check that the descriptors indicate a logical volume which appears to // be a valid UDF volume. // if ((PVD == NULL && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, don't have a PVD\n" ))) || (LVD == NULL && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, don't have an LVD\n" )))) { try_leave( Status = STATUS_UNRECOGNIZED_VOLUME ); } // // Store away the UDF revision in the VCB for future reference, and clamp the // maximum acceptable revision based on the previously encountered NSR version. // Vcb->UdfRevision = ((PUDF_SUFFIX_DOMAIN)&(LVD->DomainID.Suffix))->UdfRevision; MaxVerBasedOnNSR = (VsdIdentNSR03 > Vcb->NsrVersion) ? UDF_VERSION_150 : UDF_VERSION_RECOGNIZED; DebugTrace((0,Dbg,"UdfFindVolumeDescriptors() Pass 1: Found LVD specifying DomainID %x\n", ((PUDF_SUFFIX_DOMAIN)&(LVD->DomainID.Suffix))->UdfRevision)); if ( // // Now check the PVD // // // The Volume Set Sequence fields indicates how many volumes form // the volume set and what number this volume is in that sequence. // We are a level 2 implementation, meaning that the volumes we read // consist of a single volume. (3/11) // (PVD->VolSetSeq > 1 && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, PVD VolSetSeq %08x - not volume 1 of a volume set\n", PVD->VolSetSeq ))) || (((PVD->VolSetSeqMax > 1) && (PVD->Destag.CRC != UDF_SNOW_WHITE_PVD_CRC) && (PVD->Destag.CRC != UDF_SNOW_WHITE_PVD_CRC_VARIANT_2)) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, PVD VolSetSeqMax %08x - volume in a non-unit volume set\n", PVD->VolSetSeqMax ))) || (PVD->CharSetList != UDF_CHARSETLIST && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, PVD CharSetList %08x != CS0 only\n", PVD->CharSetList ))) || (PVD->CharSetListMax != UDF_CHARSETLIST && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, PVD CharSetListMax %08x != CS0 only\n", PVD->CharSetListMax ))) || // // The two character sets must be UDF CS0. CS0 is a "by convention" // character set in ISO 13346, which UDF specifies for our domain. // (!UdfEqualCharspec( &PVD->CharsetDesc, &UdfCS0Identifier, CHARSPEC_T_CS0 ) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, PVD CharsetDesc != CS0 only\n" ))) || (!UdfEqualCharspec( &PVD->CharsetExplan, &UdfCS0Identifier, CHARSPEC_T_CS0 ) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, PVD CharsetExplan != CS0 only\n" ))) || // // Now check the LVD // // // The LVD is a variant sized structure. Check that the claimed size fits in a single // logical sector. Although an LVD may legally exceed a single sector, we will never // want to deal with such a volume. // (ISONsrLvolSize( LVD ) > SectorSize( Vcb ) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, LVD is bigger than a sector\n" ))) || // // The character set used in the LVD must be UDF CS0 as well. // (!UdfEqualCharspec( &LVD->Charset, &UdfCS0Identifier, CHARSPEC_T_CS0 ) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, LVD Charset != CS0 only\n" ))) || // // The specified block size must equal the physical sector size. // (LVD->BlockSize != SectorSize( Vcb ) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, LVD BlockSize %08x != SectorSize %08x\n" ))) || // // The domain must be within the version we read // (!UdfDomainIdentifierContained( &LVD->DomainID, &UdfDomainIdentifier, UDF_VERSION_MINIMUM, MaxVerBasedOnNSR ) && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, domain ID indicates unreadable volume\n" ))) || // // Although we can handle any number of partitions, UDF only specifies // a single partition or special dual partition formats. // (LVD->MapTableCount > 2 && DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, LVD MapTableCount %08x greater than allowed (2)\n", LVD->MapTableCount ))) ) { DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, ... so returning STATUS_UNRECOGNIZED_VOLUME\n" )); try_leave( Status = STATUS_UNRECOGNIZED_VOLUME ); } // // Now that we have performed the simple field checks, build a Pcb. // Status = UdfInitializePcb( IrpContext, Vcb, Pcb, LVD ); if (!NT_SUCCESS(Status)) { DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, Pcb intialization failed (!)\n" )); try_leave( Status ); } } // // Go onto Pass 2 to find the Partition Descriptors // DebugTrace(( 0, Dbg, "UdfFindVolumeDescriptors, starting pass 2, find associated PD\n" )); } } finally { DebugUnwind( "UdfFindVolumeDescriptors" ); // // Free up the buffer space we may have allocated // UdfFreePool( &GenericVD ); } DebugTrace(( -1, Dbg, "UdfFindVolumeDescriptors -> %08x\n", Status )); // // Success is when we've really found something. If we failed to find both // descriptors, commute whatever intermediate status was involved and clean up. // if (*PrimaryVolumeDescriptor == NULL || *LogicalVolumeDescriptor == NULL) { Status = STATUS_UNRECOGNIZED_VOLUME; } if (!NT_SUCCESS( Status )) { UdfFreePool(PrimaryVolumeDescriptor); UdfFreePool(LogicalVolumeDescriptor); } return Status; } // // Local support routine // NTSTATUS UdfFindAnchorVolumeDescriptor ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN OUT PNSR_ANCHOR *AnchorVolumeDescriptor ) /*++ Routine Description: This routine will find the Anchor Volume Descriptor for a piece of media Arguments: Vcb - Vcb of volume to search AnchorVolumeDescriptor - Caller's pointer to an AVD Return Value: Boolean TRUE if AVD is discovered, FALSE otherwise. --*/ { ULONG ThisPass; ULONG ReadLsn; ULONG Lsn; BOOLEAN Found = FALSE; NTSTATUS Status; PAGED_CODE(); // // Check the input parameters // ASSERT_IRP_CONTEXT( IrpContext); ASSERT_VCB( Vcb ); ASSERT(*AnchorVolumeDescriptor == NULL); DebugTrace(( +1, Dbg, "UdfFindAnchorVolumeDescriptors()\n")); // // Discover the Anchor Volume Descriptor, which will point towards the // Volume Set Descriptor Sequence. The AVD may exist at sector 256 or // in the last sector of the volume. // *AnchorVolumeDescriptor = (PNSR_ANCHOR) FsRtlAllocatePoolWithTag( UdfNonPagedPool, UdfRawBufferSize( Vcb, sizeof(NSR_ANCHOR) ), TAG_NSR_VDSD ); // // Search the three possible locations for an AVD to exist on the volume, // plus check for the possibility of a method 2 fixup requirement. // for ( ThisPass = 0; ThisPass < 11; ThisPass++ ) { if (ThisPass == 0) { ReadLsn = Lsn = ANCHOR_SECTOR + Vcb->BoundS; #ifdef EXPERIMENTAL_MOUNT_OPEN_R_MEDIA } else if (ThisPass == 1) { // // Open CD-R media typically only has a single AVDP at 512. Only // consider this if we failed to find a VRS on the media. // if (Vcb->NsrVersion == UDF_NSR_NO_VRS_FOUND) { ReadLsn = Lsn = 2*ANCHOR_SECTOR + Vcb->BoundS; } else { continue; } #else } else if (ThisPass == 1) { continue; #endif } else if (ThisPass == 2) { // // It is so unlikely that we will get a disk that doesn't have // an anchor at 256 that this is a pretty good indication we // have a CD-RW here and the drive is method 2 goofy. Take // a shot. // ReadLsn = UdfMethod2TransformSector( Vcb, ANCHOR_SECTOR ); Lsn = ANCHOR_SECTOR; } else if (ThisPass >= 3) { ULONG SubPass = (ThisPass > 6) ? (ThisPass - 4) : ThisPass; // // Our remaining two chances depend on being able to determine // the last recorded sector for the volume. If we were unable // to do this, stop. // if (!Vcb->BoundN) { break; } // // Note that although we're only looking at 2 sectors (N, N-256), // because of the fuzziness of N on CD media (can include runout // of 2 sectors) and method 2 addressing bugs in some drives, we // potentially have to look at 8 locations... We work fowards to // try and avoid reading invalid sectors (which can take some time). // ReadLsn = Lsn = Vcb->BoundN - ( SubPass == 3? (ANCHOR_SECTOR + 2): // 3,7 ( SubPass == 4? ANCHOR_SECTOR: // 4,8 ( SubPass == 5? 2 : 0 ))); // 5,9 6,10 // // Also try the method 2 transformed version of each address (pass 7..10) // If we get this far, it might take a while... // if (6 < ThisPass) { ReadLsn = UdfMethod2TransformSector( Vcb, Lsn); } } DebugTrace(( 0, Dbg, "Pass: %d Trying Lsn/ReadLsn %X / %X\n", ThisPass, Lsn, ReadLsn)); // // We may have more chances to succeed if failure occurs. // Status = UdfReadSectors( IrpContext, LlBytesFromSectors( Vcb, ReadLsn ), UdfRawReadSize( Vcb, sizeof(NSR_ANCHOR) ), TRUE, *AnchorVolumeDescriptor, Vcb->TargetDeviceObject ); if ( NT_SUCCESS( Status ) && UdfVerifyDescriptor( IrpContext, &(*AnchorVolumeDescriptor)->Destag, DESTAG_ID_NSR_ANCHOR, sizeof(NSR_ANCHOR), Lsn, TRUE) ) { // // Got one! Set the method 2 fixup appropriately. // if (ReadLsn != Lsn) { DebugTrace(( 0, Dbg, "************************************************\n")); DebugTrace(( 0, Dbg, "METHOD 2 FIXUPS ACTIVATED FOR Vcb @ %08x\n", Vcb )); DebugTrace(( 0, Dbg, "************************************************\n")); SetFlag( Vcb->VcbState, VCB_STATE_METHOD_2_FIXUP ); } else { ClearFlag( Vcb->VcbState, VCB_STATE_METHOD_2_FIXUP ); } Status = STATUS_SUCCESS; break; } } if (11 == ThisPass) { Status = STATUS_UNRECOGNIZED_VOLUME; } DebugTrace(( -1, Dbg, "UdfFindAnchorVolumeDescriptors() -> %X\n", Status)); return Status; } // // Local support routine // BOOLEAN UdfRecognizeVolume ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT DeviceObject, IN ULONG SectorSize, IN OUT PULONG BoundS, IN OUT PBOOLEAN Bridge, OUT PUSHORT NSRVerFound ) /*++ Routine Description: This routine walks the Volume Recognition Sequence to determine whether this volume contains an NSR02 (ISO 13346 Section 4) image. Arguments: DeviceObject - device we are checking SectorSize - size of a physical sector on this device Bridge - will return whether there appear to be ISO 9660 structures on the media NSRVerFound - returns either VsdIdentNSR02 or VsdIdentNSR03 if successful Return Value: Boolean TRUE if we found NSR02/3, FALSE otherwise. --*/ { NTSTATUS Status; BOOLEAN FoundBEA; BOOLEAN FoundNSR; BOOLEAN Resolved; ULONG AssumedDescriptorSize = sizeof(VSD_GENERIC); USHORT ThisRecordType; PVSD_GENERIC VolumeStructureDescriptor; PVSD_GENERIC VolumeStructureDescriptorBuffer; ULONGLONG Offset; ULONGLONG StartOffset; PAGED_CODE(); // // Check the input parameters // ASSERT_IRP_CONTEXT( IrpContext); VolumeStructureDescriptorBuffer = (PVSD_GENERIC) FsRtlAllocatePoolWithTag( UdfNonPagedPool, UdfRawBufferSizeN( SectorSize, sizeof(VSD_GENERIC) ), TAG_NSR_VSD ); DebugTrace(( +1, Dbg, "UdfRecognizeVolume, DevObj %08x SectorSize %08x\n", DeviceObject, SectorSize )); // // Use try-finally to facilitate cleanup // try { Retry: FoundBEA = FoundNSR = Resolved = FALSE; StartOffset = Offset = (SectorSize * (ULONGLONG)(*BoundS)) + VRA_BOUNDARY_LOCATION; while (!Resolved) { // // It's possible that the sector size is > 2k which is the descriptor // size. Only read if we've processed all 2k blocks in the prev. sector // if (0 == (Offset & (SectorSize - 1))) { VolumeStructureDescriptor = VolumeStructureDescriptorBuffer; Status = UdfReadSectors( IrpContext, Offset, UdfRawReadSizeN( SectorSize, sizeof(VSD_GENERIC) ), TRUE, VolumeStructureDescriptor, DeviceObject ); if (!NT_SUCCESS( Status )) { break; } } // // Now check the type of the descriptor. All ISO 13346 VSDs are // of Type 0, 9660 PVDs are Type 1, 9660 SVDs are Type 2, and 9660 // terminating descriptors are Type 255. // if (VolumeStructureDescriptor->Type == 0) { // // In order to properly recognize the volume, we must know all of the // Structure identifiers in ISO 13346 so that we can terminate if a // badly formatted (or, shockingly, non 13346) volume is presented to us. // ThisRecordType = (USHORT)UdfFindInParseTable( VsdIdentParseTable, VolumeStructureDescriptor->Ident, VSD_LENGTH_IDENT ); switch ( ThisRecordType ) { case VsdIdentBEA01: // // Only one BEA may exist and its version must be 1 (2/9.2.3) // DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got a BEA01\n" )); if ((FoundBEA && DebugTrace(( 0, Dbg, "UdfRecognizeVolume, ... but it is a duplicate!\n" ))) || (VolumeStructureDescriptor->Version != 1 && DebugTrace(( 0, Dbg, "UdfRecognizeVolume, ... but it has a wacky version number %02x != 1!\n", VolumeStructureDescriptor->Version )))) { Resolved = TRUE; break; } FoundBEA = TRUE; break; case VsdIdentTEA01: // // If we reach the TEA it must be the case that we don't recognize // DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got a TEA01\n" )); Resolved = TRUE; break; case VsdIdentNSR02: case VsdIdentNSR03: // // We recognize NSR02/3 version 1 embedded after a BEA (3/9.1.3). For // simplicity we will not bother being a complete nitpick and check // for a bounding TEA, although we will be optimistic in the case where // we fail to match the version. // DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got an NSR0%c\n", ((VsdIdentNSR02 == ThisRecordType) ? '2' : '3'))); if ((FoundBEA || !DebugTrace(( 0, Dbg, "UdfRecognizeVolume, ... but we haven't seen a BEA01 yet!\n" ))) && (VolumeStructureDescriptor->Version == 1 || !DebugTrace(( 0, Dbg, "UdfRecognizeVolume, ... but it has a wacky version number %02x != 1\n", VolumeStructureDescriptor->Version ))) ) { FoundNSR = Resolved = TRUE; *NSRVerFound = ThisRecordType; // Report the NSR version we found here break; } break; case VsdIdentCD001: case VsdIdentCDW01: case VsdIdentNSR01: case VsdIdentCDW02: case VsdIdentBOOT2: DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got a valid but uninteresting 13346 descriptor (%d)\n", ThisRecordType )); // // Valid but uninteresting (to us) descriptors // break; default: DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got an invalid 13346 descriptor (%d)\n", ThisRecordType )); // // This probably was a false alert, but in any case there is nothing // on this volume for us. Exception is if this media sector size // is >= 4k, and this was the second descriptor. We'll allow // a failure here, and switch to reading in whole sector increments. // if ((Offset == (StartOffset + sizeof(VSD_GENERIC))) && (SectorSize > sizeof( VSD_GENERIC))) { Offset -= AssumedDescriptorSize; AssumedDescriptorSize = SectorSize; } else { Resolved = TRUE; } break; } } else if (!FoundBEA && (VolumeStructureDescriptor->Type < 3 || VolumeStructureDescriptor->Type == 255)) { DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got a 9660 descriptor\n" )); // // Only HSG (CDROM) and 9660 (CD001) are possible, and they are only legal // before the ISO 13346 BEA/TEA extent. By design, an ISO 13346 VSD precisely // overlaps a 9660 PVD/SVD in the appropriate fields. // // Note that we aren't being strict about the structure of the 9660 descriptors // since that really isn't very interesting. We care more about the 13346. // // switch (UdfFindInParseTable( VsdIdentParseTable, VolumeStructureDescriptor->Ident, VSD_LENGTH_IDENT )) { case VsdIdentCDROM: case VsdIdentCD001: DebugTrace(( 0, Dbg, "UdfRecognizeVolume, ... seems we have 9660 here\n" )); // // Note to our caller that we seem to have ISO 9660 here // *Bridge = TRUE; break; default: DebugTrace(( 0, Dbg, "UdfRecognizeVolume, ... but it looks wacky\n" )); // // This probably was a false alert, but in any case there is nothing // on this volume for us. Exception is if this media sector size // is >= 4k, and this was the second descriptor. We'll allow // a failure here, and switch to reading in whole sector increments. // if ((Offset == (StartOffset + sizeof(VSD_GENERIC))) && (SectorSize > sizeof( VSD_GENERIC))) { Offset -= AssumedDescriptorSize; AssumedDescriptorSize = SectorSize; } else { Resolved = TRUE; } break; } } else { // // Something else must be recorded on this volume. // DebugTrace(( 0, Dbg, "UdfRecognizeVolume, got an unrecognizeable descriptor, probably not 13346/9660\n" )); break; } Offset += AssumedDescriptorSize; VolumeStructureDescriptor = Add2Ptr( VolumeStructureDescriptor, sizeof( VSD_GENERIC), PVSD_GENERIC); } // // If this was the first pass, and we weren't looking at the start // of the disc (i.e. later session), and we didn't find anything, // then try the first track in the first session. // if (!FoundNSR && (0 != *BoundS)) { DebugTrace(( 0, Dbg, "UdfRecognizeVolume, failed to find VRS in last session, trying first\n" )); *BoundS = 0; goto Retry; } } finally { DebugUnwind( "UdfRecognizeVolume" ); // // Free up our temporary buffer // UdfFreePool( &VolumeStructureDescriptorBuffer ); if (AbnormalTermination()) { // // Commute a status we raised for empty devices so that other filesystems // can have a crack at this. // if (UdfIsRawDevice(IrpContext, IrpContext->ExceptionStatus)) { IrpContext->ExceptionStatus = STATUS_UNRECOGNIZED_VOLUME; } } } DebugTrace(( -1, Dbg, "UdfRecognizeVolume -> %u\n", FoundNSR )); return FoundNSR; } // // Local support routine // VOID UdfScanForDismountedVcb ( IN PIRP_CONTEXT IrpContext ) /*++ Routine Description: This routine walks through the list of Vcb's looking for any which may now be deleted. They may have been left on the list because there were outstanding references. Arguments: Return Value: None --*/ { PVCB Vcb; PLIST_ENTRY Links; PAGED_CODE(); // // Check input. // ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_EXCLUSIVE_UDFDATA; // // Walk through all of the Vcb's attached to the global data. // Links = UdfData.VcbQueue.Flink; while (Links != &UdfData.VcbQueue) { Vcb = CONTAINING_RECORD( Links, VCB, VcbLinks ); // // Move to the next link now since the current Vcb may be deleted. // Links = Links->Flink; // // If dismount is already underway then check if this Vcb can // go away. // if ((Vcb->VcbCondition == VcbDismountInProgress) || (Vcb->VcbCondition == VcbInvalid) || ((Vcb->VcbCondition == VcbNotMounted) && (Vcb->VcbReference <= Vcb->VcbResidualReference))) { UdfCheckForDismount( IrpContext, Vcb, FALSE ); } } return; } #ifdef EXPERIMENTAL_MOUNT_OPEN_R_MEDIA NTSTATUS UdfCheckForOpenRMedia( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject, IN PULONG S, IN PULONG N ) /*++ Routine Description: Examines the media in the target device to determine whether or not it is C/DVD-R media in an open state (i.e. current open track is packet mode... etc) that could be UDF. If so, updates S and N. Arguments: S - an address to store the start of the volume for the purposes of finding descriptors N - an address to store the end of the volume for the purposes of finding descriptors Return Value: NTSTATUS from underlying drivers. --*/ { CDB Cdb; NTSTATUS Status; UCHAR Buffer[256]; ULONG BufferSize = sizeof( Buffer); ULONG FixedPacketSize; ULONG Feature; PTRACK_INFORMATION TrackInfo = (PTRACK_INFORMATION)Buffer; DISK_INFORMATION DiscInfo; // // Issue a read disc information. // RtlZeroMemory( &Cdb, sizeof( Cdb)); Cdb.READ_DISK_INFORMATION.OperationCode = SCSIOP_READ_DISK_INFORMATION; Cdb.READ_DISK_INFORMATION.AllocationLength[0] = (UCHAR)(BufferSize >> 8); Cdb.READ_DISK_INFORMATION.AllocationLength[1] = (UCHAR)(BufferSize & 0xff); Status = UdfSendSptCdb( TargetDeviceObject, &Cdb, Buffer, &BufferSize, TRUE, 5, NULL, 0, NULL, NULL); if (!NT_SUCCESS(Status)) { // // Wouldn't expect this to fail except on really old CD-ROM drives. // DebugTrace((0, Dbg, "READ_DISC_INFORMATION failed 0x%x\n", Status)); return Status; } // // Preserve the disc info. // RtlCopyMemory( &DiscInfo, Buffer, sizeof( DiscInfo)); // // Fail if disc state and last session/border aren't both incomplete. // if (!(DiscInfo.LastSessionStatus == 1) && (DiscInfo.DiskStatus == 1)) { return STATUS_INVALID_DEVICE_REQUEST; } // // Read track information for the last track on the media. // RtlZeroMemory( &Cdb, sizeof( Cdb)); BufferSize = sizeof( Buffer); Cdb.READ_TRACK_INFORMATION.OperationCode = SCSIOP_READ_TRACK_INFORMATION; Cdb.READ_TRACK_INFORMATION.AllocationLength[0] = (UCHAR)(BufferSize >> 8); Cdb.READ_TRACK_INFORMATION.AllocationLength[1] = (UCHAR)(BufferSize & 0xff); Cdb.READ_TRACK_INFORMATION.Track = 1; // blockaddress field => track/rzone number Cdb.READ_TRACK_INFORMATION.BlockAddress[3] = 0xff; // = invisible / open track Status = UdfSendSptCdb( TargetDeviceObject, &Cdb, Buffer, &BufferSize, TRUE, 5, NULL, 0, NULL, NULL); if (!NT_SUCCESS(Status)) { // // Wouldn't expect this to fail except on really old CD-ROM drives. // DebugTrace((0, Dbg, "READ_TRACK_INFORMATION failed 0x%x\n", Status)); return Status; } // // Check that the last track is... // - variable packet mode // - not reserved / damaged / FP / blank and that either the NWA or LRA is valid. // if (TrackInfo->Damage || TrackInfo->FP || TrackInfo->Blank || !TrackInfo->Packet || TrackInfo->RT || !(TrackInfo->NWA_V || (TrackInfo->Reserved3 & 1))) { return STATUS_INVALID_DEVICE_REQUEST; } // // If the last recorded block address is valid, use that. // if (TrackInfo->Reserved3 & 1) { SwapCopyUchar4( N, TrackInfo->FixedPacketSize + 8); } else { // // Pull out the NWA. // SwapCopyUchar4( N, TrackInfo->NextWritableAddress); // // Skip back past the runout blocks. We expect that DVD-R drives // will return the LRA, so we assume CD-R here (7 blocks runout). // *N -= 8; } // // Set S to zero, since we expect an AVDP at sector 512. // *S = 0; DebugTrace(( 0, Dbg, "UdfCheckForOpenRMedia -> N = 0x%x\n", *N)); return STATUS_SUCCESS; } #endif VOID UdfDetermineVolumeBounding ( IN PIRP_CONTEXT IrpContext, IN PDEVICE_OBJECT TargetDeviceObject, IN PULONG S, IN PULONG N ) /*++ Routine Description: This routine will figure out where the base offset to discover volume descriptors lies and where the end of the disc is. In the case where this is a non-CD media, this will tend to not to set the end bound since there is no uniform way to figure that piece of information out. The bounding information is used to start the hunt for CD-UDF (UDF 1.5) volumes. Anyone who puts CD-UDF on non-CD media deserves what they get. Arguments: Vcb - the volume we are operating on S - an address to store the start of the volume for the purposes of finding descriptors N - an address to store the end of the volume for the purposes of finding descriptors Return Value: None. Benign inability find the S/N information will result in 0/0 being returned. --*/ { NTSTATUS Status; PCDROM_TOC CdromToc; PTRACK_DATA TrackData; CDROM_READ_TOC_EX Command; PAGED_CODE(); // // Check input. // ASSERT_IRP_CONTEXT( IrpContext ); // // Whack the inputs to the benign state. // *S = *N = 0; // // Currently we do nothing here for non CD class devices. This does // mean that we can't mount (e.g.) WORM/RAM/MO media which has been // recorded with sequential UDF and a VAT. // if (TargetDeviceObject->DeviceType != FILE_DEVICE_CD_ROM) { DebugTrace(( 0, Dbg, "Not determining volume bounds / session info - not CDROM class device\n")); return; } // // Allocate a buffer for the last session information. // CdromToc = FsRtlAllocatePoolWithTag( UdfPagedPool, sizeof( CDROM_TOC ), TAG_CDROM_TOC ); RtlZeroMemory( CdromToc, sizeof( CDROM_TOC )); DebugTrace(( +1, Dbg, "UdfDetermineVolumeBounding, S %08x N %08x\n", S, N )); // // Zero the command block. This conveniently corresponds to an // LBA mode READ_TOC request. // RtlZeroMemory( &Command, sizeof( Command)); // // Try to retrieve the CDROM last session information. // try { // // Pull up the TOC. The information for track AA (start of leadout) // will get us the end of disc within some tolerance dependent on how // much the device manufacturer paid attention to specifications. // (-152, -150, -2, and 0 are possible offsets to the real end). // Status = UdfPerformDevIoCtrl( IrpContext, IOCTL_CDROM_READ_TOC_EX, TargetDeviceObject, &Command, sizeof( Command), CdromToc, sizeof( CDROM_TOC ), FALSE, TRUE, NULL ); // // If this failed, try again with the MSF variant of the command // if (!NT_SUCCESS(Status) && (STATUS_INSUFFICIENT_RESOURCES != Status)) { Command.Msf = 1; Status = UdfPerformDevIoCtrl( IrpContext, IOCTL_CDROM_READ_TOC_EX, TargetDeviceObject, &Command, sizeof( Command), CdromToc, sizeof( CDROM_TOC ), FALSE, TRUE, NULL ); } // // Raise an exception if there was an allocation failure. // if (Status == STATUS_INSUFFICIENT_RESOURCES) { DebugTrace(( 0, Dbg, "UdfDetermineVolumeBounding, READ_TOC failed INSUFFICIENT_RESOURCES\n" )); UdfRaiseStatus( IrpContext, Status ); } // // For other errors, just fail. Perhaps this will turn out to be benign, in any case // the mount will rapidly and correctly fail if it really was dependant on this work. // if (!NT_SUCCESS( Status )) { #ifdef EXPERIMENTAL_MOUNT_OPEN_R_MEDIA // // On 'open' CD/DVD-R media the TOC is not available. So look for media // in this state which might be packet written UDF+VAT. This will only // succeed on CD/DVD-R drives. Regardless of success/failure here, we're // done. // Status = UdfCheckForOpenRMedia( IrpContext, TargetDeviceObject, S, N); #endif try_leave( NOTHING ); } // // Sanity chck that the TOC is well-bounded. // if (CdromToc->LastTrack - CdromToc->FirstTrack >= MAXIMUM_NUMBER_TRACKS) { DebugTrace(( 0, Dbg, "UdfDetermineVolumeBounding, TOC malf (too many tracks)\n" )); try_leave( NOTHING ); } #if DBG { ULONG Track; for ( Track = 0; Track <= (ULONG)(CdromToc->LastTrack - CdromToc->FirstTrack + 1); Track++) { DebugTrace(( 0, Dbg, " TOC[%02x]: Num: %x Ctrl/Adr: %x/%x Addr: %08x\n", Track, CdromToc->TrackData[Track].TrackNumber, CdromToc->TrackData[Track].Control, CdromToc->TrackData[Track].Adr, *(PULONG)(CdromToc->TrackData[Track].Address))); } } #endif TrackData = &CdromToc->TrackData[(CdromToc->LastTrack - CdromToc->FirstTrack + 1)]; // // Last track better have number 0xAA ... // if (TrackData->TrackNumber != 0xaa) { DebugTrace(( 0, Dbg, "UdfDetermineVolumeBounding, TOC malf (aa not last)\n" )); // // Some drives do this wrong, apparently, so we won't enforce it. // // try_leave( NOTHING ); } // // Now, find the AA (leadout 'track') info // if (Command.Msf) { // // Convert MSF to a logical block address. 75 frames/sectors // per second, 60 seconds per minute. The MSF address is stored LSB (the F byte) high // in the word. // // NOTE: MSF is only capable of representing 256*(256+256*60)*75 = 0x11ce20 sectors. // This is 2.3gb, much less than the size of DVD media, which will respond to CDROM_TOC. // Caveat user. And actually the maximum 'legal' value is 63/59/74. // *N = (TrackData->Address[3] + (TrackData->Address[2] + TrackData->Address[1] * 60) * 75) - 1; // // We must bias back by 0/2/0 MSF since that is the defined location of sector 0. This // works out to 150 sectors. // if (*N <= 150) { *N = 0; try_leave( NOTHING ); } *N -= 150; } else { // // The non-MSF (LBA) request succeeded, so just fix the endianness. // SwapCopyUchar4( N, &TrackData->Address); if (0 != *N) { *N -= 1; } } // // Seems that some DVD drives always return AA start 0x6dd39 (which is the max legally // representable MSF value 99/59/74) to TOC queries, even in LBA mode. If this // is what we have for the leadout address, then lets see what READ_CAPACITY says. // We'll also issue read capacity if the address is > than this, since we must // be dealing with DVD or DDCD media, so the drive must support the command and // it should give a definitive answer. // if (0x6dd38 <= *N) { PDISK_GEOMETRY_EX Geometry = (PVOID)CdromToc; ULONG Blocks; Status = UdfPerformDevIoCtrl( IrpContext, IOCTL_CDROM_GET_DRIVE_GEOMETRY_EX, TargetDeviceObject, NULL, 0, CdromToc, sizeof( CDROM_TOC ), FALSE, TRUE, NULL ); if (NT_SUCCESS( Status)) { Blocks = (ULONG)(Geometry->DiskSize.QuadPart / Geometry->Geometry.BytesPerSector) - 1; if (Blocks > *N) { DebugTrace((0, Dbg, "Using READ_CAPACITY media size of 0x%X in place of maxed out READ_TOC value\n", Blocks)); *N = Blocks; } } else { DebugTrace(( 0, Dbg, "GET_DRIVE_GEO failed, %x\n", Status)); } } // // Query the last session information from the driver. Not that this // actually issues an LBA mode READ_TOC_EX and pulls the address from // there. // Status = UdfPerformDevIoCtrl( IrpContext, IOCTL_CDROM_GET_LAST_SESSION, TargetDeviceObject, NULL, 0, CdromToc, sizeof( CDROM_TOC ), FALSE, TRUE, NULL ); // // Raise an exception if there was an allocation failure. // if (Status == STATUS_INSUFFICIENT_RESOURCES) { DebugTrace(( 0, Dbg, "UdfDetermineVolumeBounding, GET_LAST_SESSION failed INSUFFICIENT_RESOURCES\n" )); UdfRaiseStatus( IrpContext, Status ); } // // Now, if we got anything interesting out of this try, return it. If this // failed for any other reason, we don't really care - it just means that // if this was CDUDF media, we're gonna fail to figure it out pretty quickly. // // Life is tough. // if (NT_SUCCESS( Status ) && CdromToc->FirstTrack != CdromToc->LastTrack) { // // The 0 entry in TrackData tells us about the first track in the last // session as a logical block address. // SwapCopyUchar4( S, &CdromToc->TrackData[0].Address ); // // Save grief if the session info is messed up. // if (*N <= *S) { DebugTrace(( 0, Dbg, "UdfDetermineVolumeBounding, N (0x%x) before S (0x%x), whacking both back!\n", *N, *S )); *S = *N = 0; } } DebugTrace(( 0, Dbg, "UdfDetermineVolumeBounding, S 0x%08x, N (== AA start - 150) 0x%08x\n", *S, *N)); } finally { DebugUnwind( "UdfDetermineVolumeBounding" ); if (CdromToc != NULL) { UdfFreePool( &CdromToc ); } } DebugTrace(( -1, Dbg, "UdfDetermineVolumeBounding -> VOID\n" )); } // // Local support routine // VOID UdfUpdateVolumeLabel ( IN PIRP_CONTEXT IrpContext, IN PWCHAR VolumeLabel, IN OUT PUSHORT VolumeLabelLength, IN PUCHAR Dstring, IN UCHAR FieldLength ) /*++ Routine Description: This routine will retrieve an NT volume label from a logical volume descriptor. Arguments: VolumeLabel - a volume label to fill in. VolumeLabelLength - returns the length of the returned volume label. Dstring - the dstring field containing the volume id. FieldLength - the length of the dstring field. Return Value: None. --*/ { BOOLEAN Result; PAGED_CODE(); // // Check inputs. // ASSERT_IRP_CONTEXT( IrpContext ); DebugTrace(( +1, Dbg, "UdfUpdateVolumeLabel, Label %08x, Dstring %08x FieldLength %02x\n", VolumeLabel, Dstring, FieldLength )); // // Check that the dstring is usable as a volume identification. // Result = UdfCheckLegalCS0Dstring( IrpContext, Dstring, 0, FieldLength, TRUE ); // // Update the label directly if the dstring is good. // if (Result) { UNICODE_STRING TemporaryUnicodeString; TemporaryUnicodeString.Buffer = VolumeLabel; TemporaryUnicodeString.MaximumLength = MAXIMUM_VOLUME_LABEL_LENGTH; TemporaryUnicodeString.Length = 0; UdfConvertCS0DstringToUnicode( IrpContext, Dstring, 0, FieldLength, &TemporaryUnicodeString ); // // Now retrieve the name for return to the caller. // *VolumeLabelLength = TemporaryUnicodeString.Length; DebugTrace(( 0, Dbg, "UdfUpdateVolumeLabel, Labeled as \"%wZ\"\n", &TemporaryUnicodeString )); // // Treat as label. // } else { *VolumeLabelLength = 0; DebugTrace(( 0, Dbg, "UdfUpdateVolumeLabel, invalid label.\n" )); } DebugTrace(( -1, Dbg, "UdfUpdateVolumeLabel -> VOID\n" )); } // // Local support routine // VOID UdfUpdateVolumeSerialNumber ( IN PIRP_CONTEXT IrpContext, IN OUT PULONG VolumeSerialNumber, IN PNSR_FSD Fsd ) /*++ Routine Description: This routine will compute the volume serial number for a set of descriptors. Arguments: VolumeSerialNumber - returns the volume serial number corresponding to these descriptors. Fsd - the fileset descriptor to examine. Return Value: None. --*/ { ULONG VsnLe; PAGED_CODE(); // // Check input. // ASSERT_IRP_CONTEXT( IrpContext ); // // The serial number is just off of the FSD. This matches Win9x. // VsnLe = UdfSerial32( (PCHAR) Fsd, sizeof( NSR_FSD )); SwapCopyUchar4( VolumeSerialNumber, &VsnLe ); }