reactos/modules/rosapps/drivers/vcdrom/vcdrom.c
Hermès Bélusca-Maïto 9393fc320e
[FORMATTING] Remove trailing whitespace. Addendum to 34593d93.
Excluded: 3rd-party code (incl. wine) and most of the win32ss.
2021-09-13 03:52:22 +02:00

1244 lines
39 KiB
C

/*
* 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 <pierre@reactos.org>
*/
#include <ntddk.h>
#include <ntifs.h>
#include <ntdddisk.h>
#include <ntddcdrm.h>
#include <pseh/pseh2.h>
#define NDEBUG
#include <debug.h>
#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;
}