/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS FS utility tool * FILE: modules/rosapps/drivers/vcdrom/vcdrom.c * PURPOSE: Virtual CD-ROM class driver * PROGRAMMERS: Pierre Schweitzer */ #include #include #include #include #include #define NDEBUG #include #include "vcdioctl.h" typedef struct _DEVICE_EXTENSION { PDEVICE_OBJECT DeviceObject; /* File object to the ISO when opened */ PFILE_OBJECT VolumeObject; /* Size of the ISO */ LARGE_INTEGER VolumeSize; /* Mandatory change count for verify */ ULONG ChangeCount; /* Will contain the device name or the drive letter */ UNICODE_STRING GlobalName; /* Will contain the image path */ UNICODE_STRING ImageName; /* Flags on mount (supp. of UDF/Joliet) */ ULONG Flags; /* Faking CD structure */ ULONG SectorSize; ULONG SectorShift; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; /* Taken from CDFS */ #define TOC_DATA_TRACK 0x04 #define TOC_LAST_TRACK 0xaa /* Should cover most of our usages */ #define DEFAULT_STRING_SIZE 50 /* Increment on device creation, protection by the mutex */ FAST_MUTEX ViMutex; ULONG ViDevicesCount; VOID ViInitializeDeviceExtension(PDEVICE_OBJECT DeviceObject) { PDEVICE_EXTENSION DeviceExtension; DeviceExtension = DeviceObject->DeviceExtension; /* Zero mandatory fields, the rest will be zeroed when needed */ DeviceExtension->DeviceObject = DeviceObject; DeviceExtension->VolumeObject = NULL; DeviceExtension->ChangeCount = 0; DeviceExtension->GlobalName.Buffer = NULL; DeviceExtension->GlobalName.MaximumLength = 0; DeviceExtension->GlobalName.Length = 0; DeviceExtension->ImageName.Buffer = NULL; DeviceExtension->ImageName.MaximumLength = 0; DeviceExtension->ImageName.Length = 0; } NTSTATUS ViAllocateUnicodeString(USHORT BufferLength, PUNICODE_STRING UnicodeString) { PVOID Buffer; /* Allocate the buffer */ Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferLength, ' dCV'); /* Initialize */ UnicodeString->Length = 0; UnicodeString->MaximumLength = BufferLength; UnicodeString->Buffer = Buffer; /* Return success if it went fine */ if (Buffer != NULL) { return STATUS_SUCCESS; } return STATUS_NO_MEMORY; } VOID ViFreeUnicodeString(PUNICODE_STRING UnicodeString) { /* Only free if allocate, that allows using this * on cleanup in short memory situations */ if (UnicodeString->Buffer != NULL) { ExFreePoolWithTag(UnicodeString->Buffer, 0); UnicodeString->Buffer = NULL; } /* Zero the rest */ UnicodeString->Length = 0; UnicodeString->MaximumLength = 0; } NTSTATUS ViSetIoStatus(NTSTATUS Status, ULONG_PTR Information, PIRP Irp) { /* Only set what we got */ Irp->IoStatus.Information = Information; Irp->IoStatus.Status = Status; /* And return the status, so that caller can return with us */ return Status; } NTSTATUS NTAPI VcdHandle(PDEVICE_OBJECT DeviceObject, PIRP Irp) { /* Stub for CREATE, CLEANUP, CLOSE, always a succes */ ViSetIoStatus(STATUS_SUCCESS, 0, Irp); IoCompleteRequest(Irp, IO_DISK_INCREMENT); return STATUS_SUCCESS; } NTSTATUS ViDeleteDevice(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS Status; PDEVICE_EXTENSION DeviceExtension; DeviceExtension = DeviceObject->DeviceExtension; /* Delete the drive letter */ Status = IoDeleteSymbolicLink(&DeviceExtension->GlobalName); ViFreeUnicodeString(&DeviceExtension->GlobalName); /* Close the ISO */ if (DeviceExtension->VolumeObject != NULL) { ObDereferenceObject(DeviceExtension->VolumeObject); DeviceExtension->VolumeObject = NULL; } /* Free the ISO name */ ViFreeUnicodeString(&DeviceExtension->ImageName); /* And delete the device */ IoDeleteDevice(DeviceObject); return ViSetIoStatus(Status, 0, Irp); } VOID NTAPI VcdUnload(PDRIVER_OBJECT DriverObject) { IRP FakeIrp; PDEVICE_OBJECT DeviceObject; /* No device, nothing to free */ DeviceObject = DriverObject->DeviceObject; if (DeviceObject == NULL) { return; } /* Delete any device we could have created */ do { PDEVICE_OBJECT NextDevice; NextDevice = DeviceObject->NextDevice; /* This is normally called on IoCtl, so fake * the IRP so that status can be dummily set */ ViDeleteDevice(DeviceObject, &FakeIrp); DeviceObject = NextDevice; } while (DeviceObject != NULL); } NTSTATUS ViCreateDriveLetter(PDRIVER_OBJECT DriverObject, WCHAR Letter, WCHAR *EffectiveLetter, PDEVICE_OBJECT *DeviceObject) { NTSTATUS Status; HANDLE LinkHandle; WCHAR DriveLetter[3], CurLetter; PDEVICE_OBJECT LocalDeviceObject; PDEVICE_EXTENSION DeviceExtension; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING DeviceCount, DeviceName; *DeviceObject = NULL; /* Allocate our buffers */ ViAllocateUnicodeString(DEFAULT_STRING_SIZE, &DeviceCount); ViAllocateUnicodeString(DEFAULT_STRING_SIZE, &DeviceName); /* For easier cleanup */ _SEH2_TRY { /* Get our device number */ ExAcquireFastMutex(&ViMutex); Status = RtlIntegerToUnicodeString(ViDevicesCount, 10, &DeviceCount); ++ViDevicesCount; ExReleaseFastMutex(&ViMutex); /* Conversion to string failed, bail out */ if (!NT_SUCCESS(Status)) { _SEH2_LEAVE; } /* Create our device name */ Status = RtlAppendUnicodeToString(&DeviceName, L"\\Device\\VirtualCdRom"); if (!NT_SUCCESS(Status)) { _SEH2_LEAVE; } Status = RtlAppendUnicodeStringToString(&DeviceName, &DeviceCount); if (!NT_SUCCESS(Status)) { _SEH2_LEAVE; } /* And create the device! */ Status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), &DeviceName, FILE_DEVICE_CD_ROM, FILE_READ_ONLY_DEVICE | FILE_FLOPPY_DISKETTE, FALSE, &LocalDeviceObject); if (!NT_SUCCESS(Status)) { _SEH2_LEAVE; } /* Initialize our DE */ ViInitializeDeviceExtension(LocalDeviceObject); DeviceExtension = LocalDeviceObject->DeviceExtension; ViAllocateUnicodeString(DEFAULT_STRING_SIZE, &DeviceExtension->GlobalName); /* Now, we'll try to find a free drive letter * We always start from Z and go backward */ for (CurLetter = Letter; CurLetter >= 'A'; CurLetter--) { /* By default, no flags. These will be set * on mount, by the caller */ DeviceExtension->Flags = 0; /* Create a drive letter name */ DeviceExtension->GlobalName.Length = 0; Status = RtlAppendUnicodeToString(&DeviceExtension->GlobalName, L"\\??\\"); if (!NT_SUCCESS(Status)) { _SEH2_LEAVE; } DriveLetter[0] = CurLetter; DriveLetter[1] = L':'; DriveLetter[2] = UNICODE_NULL; Status = RtlAppendUnicodeToString(&DeviceExtension->GlobalName, DriveLetter); if (!NT_SUCCESS(Status)) { _SEH2_LEAVE; } /* And try to open it */ InitializeObjectAttributes(&ObjectAttributes, &DeviceExtension->GlobalName, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, NULL); Status = ZwOpenSymbolicLinkObject(&LinkHandle, GENERIC_READ, &ObjectAttributes); /* It failed; good news, that letter is free, jump on it! */ if (!NT_SUCCESS(Status)) { /* Create a symbolic link to our device */ Status = IoCreateSymbolicLink(&DeviceExtension->GlobalName, &DeviceName); /* If created... */ if (NT_SUCCESS(Status)) { /* Return the drive letter to the caller */ *EffectiveLetter = CurLetter; ClearFlag(LocalDeviceObject->Flags, DO_DEVICE_INITIALIZING); /* No caching! */ SetFlag(LocalDeviceObject->Flags, DO_DIRECT_IO); /* And return the DO */ *DeviceObject = LocalDeviceObject; break; } } /* This letter is taken, try another one */ else { ZwClose(LinkHandle); Status = STATUS_OBJECT_NAME_EXISTS; } } /* We're out of drive letters, so fail :-( */ if (CurLetter == (L'A' - 1)) { Status = STATUS_INSUFFICIENT_RESOURCES; } } _SEH2_FINALLY { /* No longer need these */ ViFreeUnicodeString(&DeviceName); ViFreeUnicodeString(&DeviceCount); /* And delete in case of a failure */ if (!NT_SUCCESS(Status) && Status != STATUS_VERIFY_REQUIRED && LocalDeviceObject != NULL) { IoDeleteDevice(LocalDeviceObject); } } _SEH2_END; return Status; } NTSTATUS ViMountImage(PDEVICE_OBJECT DeviceObject, PUNICODE_STRING Image) { NTSTATUS Status; HANDLE ImgHandle; IO_STATUS_BLOCK IoStatus; ULONG Buffer[2], i, SectorShift; PDEVICE_EXTENSION DeviceExtension; OBJECT_ATTRIBUTES ObjectAttributes; FILE_STANDARD_INFORMATION FileStdInfo; /* Try to open the image */ InitializeObjectAttributes(&ObjectAttributes, Image, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = ZwCreateFile(&ImgHandle, FILE_READ_DATA | SYNCHRONIZE, &ObjectAttributes, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to open image: %wZ\n", Image); return Status; } /* Query its information */ Status = ZwQueryInformationFile(ImgHandle, &IoStatus, &FileStdInfo, sizeof(FileStdInfo), FileStandardInformation); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to query image information\n"); ZwClose(ImgHandle); return Status; } /* Set image size */ DeviceExtension = DeviceObject->DeviceExtension; DeviceExtension->VolumeSize.QuadPart = FileStdInfo.EndOfFile.QuadPart; /* Read the first 8-bytes to determine our sector size */ Status = ZwReadFile(ImgHandle, NULL, NULL, NULL, &IoStatus, Buffer, sizeof(Buffer), NULL, NULL); if (!NT_SUCCESS(Status) || Buffer[1] == 0 || (Buffer[1] & 0x1FF) != 0) { DeviceExtension->SectorSize = 2048; } else { DeviceExtension->SectorSize = Buffer[1]; } /* Now, compute the sector shift */ if (DeviceExtension->SectorSize == 512) { DeviceExtension->SectorShift = 9; } else { for (i = 512, SectorShift = 9; i < DeviceExtension->SectorSize; i = i * 2, ++SectorShift); if (i == DeviceExtension->SectorSize) { DeviceExtension->SectorShift = SectorShift; } else { DeviceExtension->SectorShift = 11; } } /* We're good, get the image file object to close the handle */ Status = ObReferenceObjectByHandle(ImgHandle, 0, NULL, KernelMode, (PVOID *)&DeviceExtension->VolumeObject, NULL); if (!NT_SUCCESS(Status)) { ZwClose(ImgHandle); DeviceExtension->VolumeObject = NULL; return Status; } ZwClose(ImgHandle); /* Increase change count to force a verify */ ++DeviceExtension->ChangeCount; /* And ask for a verify! */ SetFlag(DeviceObject->Flags, DO_VERIFY_VOLUME); /* Free old string (if any) */ ViFreeUnicodeString(&DeviceExtension->ImageName); /* And save our new image name */ ViAllocateUnicodeString(Image->MaximumLength, &DeviceExtension->ImageName); RtlCopyUnicodeString(&DeviceExtension->ImageName, Image); return STATUS_SUCCESS; } VOID ViCreateDriveAndMountImage(PDRIVER_OBJECT DriverObject, WCHAR Letter, LPCWSTR ImagePath) { UNICODE_STRING Image; WCHAR EffectiveLetter; PDEVICE_OBJECT DeviceObject; /* Create string from path */ DeviceObject = NULL; RtlInitUnicodeString(&Image, ImagePath); /* Create the drive letter (ignore output, it comes from registry, nothing to do */ ViCreateDriveLetter(DriverObject, Letter, &EffectiveLetter, &DeviceObject); /* And mount the image on the created drive */ ViMountImage(DeviceObject, &Image); } VOID ViLoadImagesFromRegistry(PDRIVER_OBJECT DriverObject, LPCWSTR RegistryPath) { WCHAR Letter[2]; WCHAR PathBuffer[100], ResBuffer[100]; /* We'll browse the registry for * DeviceX\IMAGE = {Path} * When found, we create (or at least, attempt to) * device X: and mount image Path */ Letter[0] = L'A'; Letter[1] = UNICODE_NULL; do { NTSTATUS Status; UNICODE_STRING Path, ResString; RTL_QUERY_REGISTRY_TABLE QueryTable[3]; /* Our path is always Device + drive letter */ RtlZeroMemory(PathBuffer, sizeof(PathBuffer)); Path.Buffer = PathBuffer; Path.Length = 0; Path.MaximumLength = sizeof(PathBuffer); RtlAppendUnicodeToString(&Path, L"Parameters\\Device"); RtlAppendUnicodeToString(&Path, Letter); ResString.Buffer = ResBuffer; ResString.Length = 0; ResString.MaximumLength = sizeof(ResBuffer); RtlZeroMemory(&QueryTable[0], sizeof(QueryTable)); QueryTable[0].Name = Path.Buffer; QueryTable[0].Flags = RTL_QUERY_REGISTRY_SUBKEY; QueryTable[1].Name = L"IMAGE"; QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT; QueryTable[1].EntryContext = &ResString; /* If query went fine, attempt to create and mount */ Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, RegistryPath, QueryTable, NULL, NULL); if (NT_SUCCESS(Status)) { ViCreateDriveAndMountImage(DriverObject, Letter[0], ResString.Buffer); } ++(Letter[0]); } while (Letter[0] <= L'Z'); } NTSTATUS ViVerifyVolume(PDEVICE_OBJECT DeviceObject, PIRP Irp) { /* Force verify */ SetFlag(DeviceObject->Flags, DO_VERIFY_VOLUME); Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED; IoSetHardErrorOrVerifyDevice(Irp, DeviceObject); return STATUS_VERIFY_REQUIRED; } NTSTATUS ViReadFile(PFILE_OBJECT File, PMDL Mdl, PLARGE_INTEGER Offset, PKEVENT Event, ULONG Length, PIO_STATUS_BLOCK IoStatusBlock) { PIRP LowerIrp; PIO_STACK_LOCATION Stack; PDEVICE_OBJECT DeviceObject; /* Get the lower DO */ DeviceObject = IoGetRelatedDeviceObject(File); /* Allocate an IRP to deal with it */ LowerIrp = IoAllocateIrp(DeviceObject->StackSize, 0); if (LowerIrp == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } /* Initialize it */ LowerIrp->RequestorMode = KernelMode; /* Our status block */ LowerIrp->UserIosb = IoStatusBlock; LowerIrp->MdlAddress = Mdl; /* We don't cache! */ LowerIrp->Flags = IRP_NOCACHE | IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO; /* Sync event for us to know when read is done */ LowerIrp->UserEvent = Event; LowerIrp->UserBuffer = (PVOID)((ULONG_PTR)Mdl->StartVa + Mdl->ByteOffset); /* The FO of our image */ LowerIrp->Tail.Overlay.OriginalFileObject = File; LowerIrp->Tail.Overlay.Thread = PsGetCurrentThread(); /* We basically perform a read operation, nothing complex here */ Stack = IoGetNextIrpStackLocation(LowerIrp); Stack->Parameters.Read.Length = Length; Stack->MajorFunction = IRP_MJ_READ; Stack->FileObject = File; Stack->Parameters.Read.ByteOffset.QuadPart = Offset->QuadPart; /* And call lower driver */ return IoCallDriver(DeviceObject, LowerIrp); } NTSTATUS NTAPI VcdRead(PDEVICE_OBJECT DeviceObject, PIRP Irp) { KEVENT Event; NTSTATUS Status; IO_STATUS_BLOCK IoStatus; PIO_STACK_LOCATION Stack; PDEVICE_EXTENSION DeviceExtension; /* Catch any exception that could happen during read attempt */ _SEH2_TRY { /* First of all, check if there's an image mounted */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject == NULL) { Status = ViSetIoStatus(STATUS_NO_MEDIA_IN_DEVICE, 0, Irp); _SEH2_LEAVE; } /* Check if volume has to be verified (or if we override, for instance * FSD performing initial reads for mouting FS) */ Stack = IoGetCurrentIrpStackLocation(Irp); if (BooleanFlagOn(DeviceObject->Flags, DO_VERIFY_VOLUME) && !BooleanFlagOn(Stack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) { Status = ViVerifyVolume(DeviceObject, Irp); _SEH2_LEAVE; } /* Check if we have enough room for output */ if (Stack->Parameters.Read.Length > Irp->MdlAddress->ByteCount) { Status = ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, 0, Irp); _SEH2_LEAVE; } /* Initialize our event */ KeInitializeEvent(&Event, NotificationEvent, FALSE); /* Perform actual read */ Status = ViReadFile(DeviceExtension->VolumeObject, Irp->MdlAddress, &Stack->Parameters.Read.ByteOffset, &Event, Stack->Parameters.Read.Length, &IoStatus); if (!NT_SUCCESS(Status)) { Status = ViSetIoStatus(Status, 0, Irp); _SEH2_LEAVE; } /* Make sure we wait until its done */ Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); if (!NT_SUCCESS(Status)) { Status = ViSetIoStatus(Status, 0, Irp); _SEH2_LEAVE; } /* Look whether we're reading first bytes of the volume, if so, our * "suppression" might be asked for */ if (Stack->Parameters.Read.ByteOffset.QuadPart / DeviceExtension->SectorSize < 0x20) { /* Check our output buffer is in a state where we can play with it */ if ((Irp->MdlAddress->MdlFlags & (MDL_ALLOCATED_FIXED_SIZE | MDL_PAGES_LOCKED | MDL_MAPPED_TO_SYSTEM_VA)) == (MDL_ALLOCATED_FIXED_SIZE | MDL_PAGES_LOCKED | MDL_MAPPED_TO_SYSTEM_VA)) { PUCHAR Buffer; /* Do we have to delete any UDF mark? */ if (BooleanFlagOn(DeviceExtension->Flags, MOUNT_FLAG_SUPP_UDF)) { /* Kill any NSR0X mark from UDF */ Buffer = (PUCHAR)((ULONG_PTR)Irp->MdlAddress->StartVa + Irp->MdlAddress->ByteOffset); if (Buffer != NULL) { if (Buffer[0] == 0 && Buffer[1] == 'N' && Buffer[2] == 'S' && Buffer[3] == 'R' && Buffer[4] == '0') { Buffer[5] = '0'; } } } /* Do we have to delete any Joliet mark? */ if (BooleanFlagOn(DeviceExtension->Flags, MOUNT_FLAG_SUPP_JOLIET)) { /* Kill the CD001 mark from Joliet */ Buffer = (PUCHAR)((ULONG_PTR)Irp->MdlAddress->StartVa + Irp->MdlAddress->ByteOffset); if (Buffer != NULL) { if (Buffer[0] == 2 && Buffer[1] == 'C' && Buffer[2] == 'D' && Buffer[3] == '0' && Buffer[4] == '0' && Buffer[5] == '1') { Buffer[5] = '0'; } } } } } /* Set status */ Status = ViSetIoStatus(Status, Stack->Parameters.Read.Length, Irp); } _SEH2_FINALLY { /* And complete! */ IoCompleteRequest(Irp, IO_DISK_INCREMENT); } _SEH2_END; return Status; } NTSTATUS ViCreateDevice(PDRIVER_OBJECT DriverObject, PIRP Irp) { NTSTATUS Status; PIO_STACK_LOCATION Stack; PDEVICE_OBJECT DeviceObject; Stack = IoGetCurrentIrpStackLocation(Irp); /* Make sure output buffer is big enough to receive the drive letter * when we create the drive */ if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(WCHAR)) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, sizeof(WCHAR), Irp); } /* And start creation. We always start from bottom */ Status = ViCreateDriveLetter(DriverObject, L'Z', Irp->AssociatedIrp.SystemBuffer, &DeviceObject); return ViSetIoStatus(Status, sizeof(WCHAR), Irp); } NTSTATUS ViGetDriveGeometry(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PDISK_GEOMETRY DiskGeo; PIO_STACK_LOCATION Stack; PDEVICE_EXTENSION DeviceExtension; /* No geometry if no image mounted */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject == NULL) { return ViSetIoStatus(STATUS_NO_MEDIA_IN_DEVICE, 0, Irp); } /* No geometry if volume is to be verified */ Stack = IoGetCurrentIrpStackLocation(Irp); if (BooleanFlagOn(DeviceObject->Flags, DO_VERIFY_VOLUME) && !BooleanFlagOn(Stack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) { return ViVerifyVolume(DeviceObject, Irp); } /* No geometry if too small output buffer */ if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY)) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, sizeof(DISK_GEOMETRY), Irp); } /* Finally, set what we're asked for */ DiskGeo = Irp->AssociatedIrp.SystemBuffer; DiskGeo->MediaType = RemovableMedia; DiskGeo->BytesPerSector = DeviceExtension->SectorSize; DiskGeo->SectorsPerTrack = DeviceExtension->VolumeSize.QuadPart >> DeviceExtension->SectorShift; DiskGeo->Cylinders.QuadPart = 1; DiskGeo->TracksPerCylinder = 1; return ViSetIoStatus(STATUS_SUCCESS, sizeof(DISK_GEOMETRY), Irp); } NTSTATUS ViCheckVerify(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PULONG Buffer; ULONG_PTR Information; PIO_STACK_LOCATION Stack; PDEVICE_EXTENSION DeviceExtension; /* Nothing to verify if no mounted */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject == NULL) { return ViSetIoStatus(STATUS_NO_MEDIA_IN_DEVICE, 0, Irp); } /* Do we have to verify? */ Stack = IoGetCurrentIrpStackLocation(Irp); if (BooleanFlagOn(DeviceObject->Flags, DO_VERIFY_VOLUME) && !BooleanFlagOn(Stack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) { return ViSetIoStatus(STATUS_VERIFY_REQUIRED, 0, Irp); } /* If caller provided a buffer, that's to get the change count */ Buffer = Irp->AssociatedIrp.SystemBuffer; if (Buffer != NULL) { *Buffer = DeviceExtension->ChangeCount; Information = sizeof(ULONG); } else { Information = 0; } /* Done */ return ViSetIoStatus(STATUS_SUCCESS, Information, Irp); } NTSTATUS ViIssueMountImage(PDEVICE_OBJECT DeviceObject, PUNICODE_STRING Image, PIRP Irp) { NTSTATUS Status; PDEVICE_EXTENSION DeviceExtension; /* We cannot mount an image if there's already one mounted */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject != NULL) { return ViSetIoStatus(STATUS_DEVICE_NOT_READY, 0, Irp); } /* Perform the mount */ Status = ViMountImage(DeviceObject, Image); return ViSetIoStatus(Status, 0, Irp); } ULONG ViComputeAddress(ULONG Address) { UCHAR Local[4]; /* Convert LBA to MSF */ Local[0] = 0; Local[1] = Address / 4500; Local[2] = Address % 4500 / 75; Local[3] = Address + 108 * Local[1] - 75 * Local[2]; return *(ULONG *)(&Local[0]); } VOID ViFillInTrackData(PTRACK_DATA TrackData, UCHAR Control, UCHAR Adr, UCHAR TrackNumber, ULONG Address) { /* Fill in our track data with provided information */ TrackData->Reserved = 0; TrackData->Reserved1 = 0; TrackData->Control = Control & 0xF; TrackData->Adr = Adr; TrackData->TrackNumber = TrackNumber; *(ULONG *)(&TrackData->Address[0]) = ViComputeAddress(Address); } NTSTATUS ViReadToc(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PCDROM_TOC Toc; PIO_STACK_LOCATION Stack; PDEVICE_EXTENSION DeviceExtension; /* No image mounted, no TOC */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject == NULL) { return ViSetIoStatus(STATUS_NO_MEDIA_IN_DEVICE, 0, Irp); } /* No TOC if we have to verify */ Stack = IoGetCurrentIrpStackLocation(Irp); if (BooleanFlagOn(DeviceObject->Flags, DO_VERIFY_VOLUME) && !BooleanFlagOn(Stack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) { return ViVerifyVolume(DeviceObject, Irp); } /* Check we have enough room for TOC */ if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(CDROM_TOC)) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, sizeof(CDROM_TOC), Irp); } /* Start filling the TOC */ Toc = Irp->AssociatedIrp.SystemBuffer; Toc->Length[0] = 0; Toc->Length[1] = 8; Toc->FirstTrack = 1; Toc->LastTrack = 1; /* And fill our single (an ISO file always have a single track) track with 2sec gap */ ViFillInTrackData(Toc->TrackData, TOC_DATA_TRACK, ADR_NO_MODE_INFORMATION, 1, 150); /* And add last track termination */ ViFillInTrackData(&Toc->TrackData[1], TOC_DATA_TRACK, ADR_NO_MODE_INFORMATION, TOC_LAST_TRACK, (DeviceExtension->VolumeSize.QuadPart >> DeviceExtension->SectorShift) + 150); return ViSetIoStatus(STATUS_SUCCESS, sizeof(CDROM_TOC), Irp); } NTSTATUS ViReadTocEx(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PCDROM_TOC Toc; PIO_STACK_LOCATION Stack; PCDROM_READ_TOC_EX TocEx; PDEVICE_EXTENSION DeviceExtension; /* No image mounted, no TOC */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject == NULL) { return ViSetIoStatus(STATUS_NO_MEDIA_IN_DEVICE, 0, Irp); } /* No TOC if we have to verify */ Stack = IoGetCurrentIrpStackLocation(Irp); if (BooleanFlagOn(DeviceObject->Flags, DO_VERIFY_VOLUME) && !BooleanFlagOn(Stack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) { return ViVerifyVolume(DeviceObject, Irp); } /* We need an input buffer */ if (Stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(CDROM_READ_TOC_EX)) { return ViSetIoStatus(STATUS_INFO_LENGTH_MISMATCH, sizeof(CDROM_READ_TOC_EX), Irp); } /* Validate output buffer is big enough */ if (Stack->Parameters.DeviceIoControl.OutputBufferLength < MAXIMUM_CDROM_SIZE) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, MAXIMUM_CDROM_SIZE, Irp); } /* Validate the input buffer - see cdrom_new */ TocEx = Irp->AssociatedIrp.SystemBuffer; if ((TocEx->Reserved1 != 0) || (TocEx->Reserved2 != 0) || (TocEx->Reserved3 != 0)) { return ViSetIoStatus(STATUS_INVALID_PARAMETER, 0, Irp); } if (((TocEx->Format == CDROM_READ_TOC_EX_FORMAT_SESSION) || (TocEx->Format == CDROM_READ_TOC_EX_FORMAT_PMA) || (TocEx->Format == CDROM_READ_TOC_EX_FORMAT_ATIP)) && TocEx->SessionTrack != 0) { return ViSetIoStatus(STATUS_INVALID_PARAMETER, 0, Irp); } if ((TocEx->Format != CDROM_READ_TOC_EX_FORMAT_TOC) && (TocEx->Format != CDROM_READ_TOC_EX_FORMAT_FULL_TOC) && (TocEx->Format != CDROM_READ_TOC_EX_FORMAT_CDTEXT) && (TocEx->Format != CDROM_READ_TOC_EX_FORMAT_SESSION) && (TocEx->Format != CDROM_READ_TOC_EX_FORMAT_PMA) && (TocEx->Format == CDROM_READ_TOC_EX_FORMAT_ATIP)) { return ViSetIoStatus(STATUS_INVALID_PARAMETER, 0, Irp); } /* Start filling the TOC */ Toc = Irp->AssociatedIrp.SystemBuffer; Toc->Length[0] = 0; Toc->Length[1] = 8; Toc->FirstTrack = 1; Toc->LastTrack = 1; /* And fill our single (an ISO file always have a single track) track with 2sec gap */ ViFillInTrackData(Toc->TrackData, TOC_DATA_TRACK, ADR_NO_MODE_INFORMATION, 1, 150); /* And add last track termination */ ViFillInTrackData(&Toc->TrackData[1], TOC_DATA_TRACK, ADR_NO_MODE_INFORMATION, TOC_LAST_TRACK, (DeviceExtension->VolumeSize.QuadPart >> DeviceExtension->SectorShift) + 150); return ViSetIoStatus(STATUS_SUCCESS, MAXIMUM_CDROM_SIZE, Irp); } NTSTATUS ViGetLastSession(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION Stack; PCDROM_TOC_SESSION_DATA Toc; PDEVICE_EXTENSION DeviceExtension; /* No image, no last session */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject == NULL) { return ViSetIoStatus(STATUS_NO_MEDIA_IN_DEVICE, 0, Irp); } /* No last session if we have to verify */ Stack = IoGetCurrentIrpStackLocation(Irp); if (BooleanFlagOn(DeviceObject->Flags, DO_VERIFY_VOLUME) && !BooleanFlagOn(Stack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) { return ViVerifyVolume(DeviceObject, Irp); } /* Check we have enough room for last session data */ if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(CDROM_TOC_SESSION_DATA)) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, sizeof(CDROM_TOC_SESSION_DATA), Irp); } /* Fill in data */ Toc = Irp->AssociatedIrp.SystemBuffer; Toc->Length[0] = 0; Toc->Length[1] = 8; Toc->FirstCompleteSession = 1; Toc->LastCompleteSession = 1; /* And return our track with 2sec gap (cf TOC function) */ ViFillInTrackData(Toc->TrackData, TOC_DATA_TRACK, ADR_NO_MODE_INFORMATION, 1, 150); return ViSetIoStatus(STATUS_SUCCESS, sizeof(CDROM_TOC_SESSION_DATA), Irp); } NTSTATUS ViEnumerateDrives(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PDRIVES_LIST DrivesList; PIO_STACK_LOCATION Stack; PDEVICE_OBJECT CurrentDO; PDEVICE_EXTENSION DeviceExtension; /* Check we have enough room for output */ Stack = IoGetCurrentIrpStackLocation(Irp); if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DRIVES_LIST)) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, sizeof(DRIVES_LIST), Irp); } /* Get the output buffer */ DrivesList = Irp->AssociatedIrp.SystemBuffer; DrivesList->Count = 0; /* And now, starting from our main DO, start browsing all the DO we created */ for (CurrentDO = DeviceObject->DriverObject->DeviceObject; CurrentDO != NULL; CurrentDO = CurrentDO->NextDevice) { /* Check we won't output our main DO */ DeviceExtension = CurrentDO->DeviceExtension; if (DeviceExtension->GlobalName.Length != RtlCompareMemory(DeviceExtension->GlobalName.Buffer, L"\\??\\VirtualCdRom", DeviceExtension->GlobalName.Length)) { /* When we return, we extract the drive letter * See ViCreateDriveLetter(), it's \??\Z: */ DrivesList->Drives[DrivesList->Count++] = DeviceExtension->GlobalName.Buffer[4]; } } return ViSetIoStatus(STATUS_SUCCESS, sizeof(DRIVES_LIST), Irp); } NTSTATUS ViGetImagePath(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIMAGE_PATH ImagePath; PIO_STACK_LOCATION Stack; PDEVICE_EXTENSION DeviceExtension; /* Check we have enough room for output */ Stack = IoGetCurrentIrpStackLocation(Irp); if (Stack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(IMAGE_PATH)) { return ViSetIoStatus(STATUS_BUFFER_TOO_SMALL, sizeof(IMAGE_PATH), Irp); } /* Get our image path from DO */ DeviceExtension = DeviceObject->DeviceExtension; ImagePath = Irp->AssociatedIrp.SystemBuffer; ImagePath->Mounted = (DeviceExtension->VolumeObject != NULL); ImagePath->Length = DeviceExtension->ImageName.Length; /* And if it's set, copy it back to the caller */ if (DeviceExtension->ImageName.Length != 0) { RtlCopyMemory(ImagePath->Path, DeviceExtension->ImageName.Buffer, DeviceExtension->ImageName.Length); } return ViSetIoStatus(STATUS_SUCCESS, sizeof(IMAGE_PATH), Irp); } NTSTATUS ViEjectMedia(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PDEVICE_EXTENSION DeviceExtension; /* Eject will force a verify */ SetFlag(DeviceObject->Flags, DO_VERIFY_VOLUME); /* If we have an image mounted, unmount it * But don't free anything related, so that * we can perform quick remount. * See: IOCTL_STORAGE_LOAD_MEDIA */ DeviceExtension = DeviceObject->DeviceExtension; if (DeviceExtension->VolumeObject != NULL) { ObDereferenceObject(DeviceExtension->VolumeObject); /* Device changed, so mandatory increment */ ++DeviceExtension->ChangeCount; DeviceExtension->VolumeObject = NULL; } return ViSetIoStatus(STATUS_SUCCESS, 0, Irp); } NTSTATUS ViRemountMedia(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS Status; UNICODE_STRING Image; PDEVICE_EXTENSION DeviceExtension; /* Get the device extension */ DeviceExtension = DeviceObject->DeviceExtension; /* Allocate a new string as mount parameter */ Status = ViAllocateUnicodeString(DeviceExtension->ImageName.MaximumLength, &Image); if (!NT_SUCCESS(Status)) { return ViSetIoStatus(Status, 0, Irp); } /* To allow cleanup in case of troubles */ _SEH2_TRY { /* Copy our current image name and mount */ RtlCopyUnicodeString(&Image, &DeviceExtension->ImageName); Status = ViIssueMountImage(DeviceObject, &Image, Irp); } _SEH2_FINALLY { ViFreeUnicodeString(&Image); } _SEH2_END; return ViSetIoStatus(Status, 0, Irp); } NTSTATUS NTAPI VcdDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS Status; UNICODE_STRING Image; PIO_STACK_LOCATION Stack; PMOUNT_PARAMETERS MountParameters; PDEVICE_EXTENSION DeviceExtension; DeviceExtension = DeviceObject->DeviceExtension; Stack = IoGetCurrentIrpStackLocation(Irp); _SEH2_TRY { switch (Stack->Parameters.DeviceIoControl.IoControlCode) { /* First of all, our private IOCTLs */ case IOCTL_VCDROM_CREATE_DRIVE: Status = ViCreateDevice(DeviceObject->DriverObject, Irp); break; case IOCTL_VCDROM_DELETE_DRIVE: Status = ViDeleteDevice(DeviceObject, Irp); break; case IOCTL_VCDROM_MOUNT_IMAGE: MountParameters = Irp->AssociatedIrp.SystemBuffer; Image.MaximumLength = 255 * sizeof(WCHAR); Image.Length = MountParameters->Length; Image.Buffer = MountParameters->Path; DeviceExtension->Flags = MountParameters->Flags; Status = ViIssueMountImage(DeviceObject, &Image, Irp); break; case IOCTL_VCDROM_ENUMERATE_DRIVES: Status = ViEnumerateDrives(DeviceObject, Irp); break; case IOCTL_VCDROM_GET_IMAGE_PATH: Status = ViGetImagePath(DeviceObject, Irp); break; /* Now, IOCTLs we have to handle as class driver */ case IOCTL_DISK_GET_DRIVE_GEOMETRY: case IOCTL_CDROM_GET_DRIVE_GEOMETRY: Status = ViGetDriveGeometry(DeviceObject, Irp); break; case IOCTL_DISK_CHECK_VERIFY: case IOCTL_CDROM_CHECK_VERIFY: Status = ViCheckVerify(DeviceObject, Irp); break; case IOCTL_CDROM_READ_TOC: Status = ViReadToc(DeviceObject, Irp); break; case IOCTL_CDROM_READ_TOC_EX: Status = ViReadTocEx(DeviceObject, Irp); break; case IOCTL_CDROM_GET_LAST_SESSION: Status = ViGetLastSession(DeviceObject, Irp); break; case IOCTL_STORAGE_EJECT_MEDIA: case IOCTL_CDROM_EJECT_MEDIA: Status = ViEjectMedia(DeviceObject, Irp); break; /* That one is a bit specific * It gets unmounted image mounted again */ case IOCTL_STORAGE_LOAD_MEDIA: /* That means it can only be performed if: * - We had an image previously * - It's no longer mounted * Otherwise, we just return success */ if (DeviceExtension->ImageName.Buffer == NULL || DeviceExtension->VolumeObject != NULL) { Status = ViSetIoStatus(STATUS_SUCCESS, 0, Irp); } else { Status = ViRemountMedia(DeviceObject, Irp); } break; default: Status = STATUS_INVALID_DEVICE_REQUEST; DPRINT1("IOCTL: %x not supported\n", Stack->Parameters.DeviceIoControl.IoControlCode); break; } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { Status = _SEH2_GetExceptionCode(); } _SEH2_END; /* Depending on the failure code, we may force a verify */ if (!NT_SUCCESS(Status)) { if (Status == STATUS_DEVICE_NOT_READY || Status == STATUS_IO_TIMEOUT || Status == STATUS_MEDIA_WRITE_PROTECTED || Status == STATUS_NO_MEDIA_IN_DEVICE || Status == STATUS_VERIFY_REQUIRED || Status == STATUS_UNRECOGNIZED_MEDIA || Status == STATUS_WRONG_VOLUME) { IoSetHardErrorOrVerifyDevice(Irp, DeviceObject); } } IoCompleteRequest(Irp, IO_DISK_INCREMENT); return Status; } NTSTATUS NTAPI DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS Status; UNICODE_STRING DeviceName; PDEVICE_OBJECT DeviceObject; PDEVICE_EXTENSION DeviceExtension; /* Set our entry points (rather limited :-)) */ DriverObject->DriverUnload = VcdUnload; DriverObject->MajorFunction[IRP_MJ_CREATE] = VcdHandle; DriverObject->MajorFunction[IRP_MJ_CLOSE] = VcdHandle; DriverObject->MajorFunction[IRP_MJ_CLEANUP] = VcdHandle; DriverObject->MajorFunction[IRP_MJ_READ] = VcdRead; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VcdDeviceControl; /* Create our main device to receive private IOCTLs */ RtlInitUnicodeString(&DeviceName, L"\\Device\\VirtualCdRom"); Status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), &DeviceName, FILE_DEVICE_CD_ROM, FILE_READ_ONLY_DEVICE | FILE_FLOPPY_DISKETTE, FALSE, &DeviceObject); if (!NT_SUCCESS(Status)) { return Status; } /* Initialize our device extension */ ViInitializeDeviceExtension(DeviceObject); DeviceExtension = DeviceObject->DeviceExtension; /* And create our accessible name from umode */ ViAllocateUnicodeString(DEFAULT_STRING_SIZE, &DeviceExtension->GlobalName); RtlAppendUnicodeToString(&DeviceExtension->GlobalName, L"\\??\\VirtualCdRom"); Status = IoCreateSymbolicLink(&DeviceExtension->GlobalName, &DeviceName); if (!NT_SUCCESS(Status)) { IoDeleteDevice(DeviceObject); return Status; } /* Initialize our mutex for device count */ ExInitializeFastMutex(&ViMutex); /* And try to load images that would have been stored in registry */ ViLoadImagesFromRegistry(DriverObject, RegistryPath->Buffer); return STATUS_SUCCESS; }