www.pudn.com > vdksrc.zip > vdkthread.c


/*
	vdkthread.c

	Virtual Disk kernel-mode driver for Windows NT platform
	Device thread routines
	Copyright (C) 2003 Ken Kato
*/

#include "vdkbase.h"
#include "vdkutil.h"
#include "vdkioctl.h"
#include "vdkfile.h"
#include "vdkaccess.h"

#include "imports.h"
#include "vdkdrv.h"

static NTSTATUS
VdkOpen(
	IN PDEVICE_OBJECT	DiskObject,
	IN PVDK_OPEN_FILE_INFO 	OpenFile);

static NTSTATUS
VdkClose(
	IN PDISK_EXTENSION	DiskExtension);

static NTSTATUS
VdkFormat(
	IN PVDK_DISK_INFO	DiskInfo,
	IN PFORMAT_PARAMETERS	FormatParam);

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, VdkOpen)
#pragma alloc_text(PAGE, VdkClose)
#pragma alloc_text(PAGE, VdkFormat)
#endif	// ALLOC_PRAGMA

//
// Substitute for MmGetSystemAddressForMdlSafe
// for NT 4.0 DDK does not provide its equivqlent.
// originally written by Bruce Engle for filedisk
//
static PVOID MmGetSystemAddressForMdlPrettySafe (
	IN PMDL 			Mdl,
	IN MM_PAGE_PRIORITY	Priority)
{
#if (VER_PRODUCTBUILD >= 2195)
	if (OsMajorVersion == 0) {
		StoreCurrentOsVersion();
	}

	if (OsMajorVersion >= 5) {
		return MmGetSystemAddressForMdlSafe(Mdl, Priority);
	}
	else {
#endif	// (VER_PRODUCTBUILD >= 2195)
		CSHORT	MdlMappingCanFail;
		PVOID	MappedSystemVa;

		MdlMappingCanFail = (CSHORT)(Mdl->MdlFlags & MDL_MAPPING_CAN_FAIL);

		Mdl->MdlFlags |= MDL_MAPPING_CAN_FAIL;

		MappedSystemVa = MmGetSystemAddressForMdl(Mdl);

		if (!MdlMappingCanFail) {
			Mdl->MdlFlags &= ~MDL_MAPPING_CAN_FAIL;
		}

		return MappedSystemVa;
#if (VER_PRODUCTBUILD >= 2195)
	}
#endif
}

//
// Device dedicated thread routine
// Handles read, write, open, close operation
//
VOID
VdkThread (
	IN PVOID Context)	// Disk device object
{
	PDEVICE_OBJECT		disk_object;
	PDISK_EXTENSION		disk_extension;
	PLIST_ENTRY 		request;
	PIRP				irp;
	PIO_STACK_LOCATION	io_stack;

	disk_object = (PDEVICE_OBJECT)Context;
	disk_extension = (PDISK_EXTENSION)disk_object->DeviceExtension;

	KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);

	for (;;) {
		//
		// wait for the request event to be signalled
		//
		KeWaitForSingleObject(
			&disk_extension->RequestEvent,
			Executive,
			KernelMode,
			FALSE,
			NULL);

		//
		// terminate request ?
		//
		if (disk_extension->TerminateThread) {
			VDKTRACE(VDKINFO,
				("[VDK] Exitting device thread\n"));

			PsTerminateSystemThread(STATUS_SUCCESS);
		}

		//
		// perform requested task
		//
		while ((request = ExInterlockedRemoveHeadList(
			&disk_extension->ListHead, &disk_extension->ListLock)) != NULL) {

			irp = CONTAINING_RECORD(request, IRP, Tail.Overlay.ListEntry);

			io_stack = IoGetCurrentIrpStackLocation(irp);

			irp->IoStatus.Information = 0;

			switch (io_stack->MajorFunction) {
			case IRP_MJ_READ:
				if (!disk_extension->DiskInfo.DiskType) {
					irp->IoStatus.Status = STATUS_DEVICE_NOT_READY;

					VDKTRACE(VDKWARN | VDKREAD,
						("[VDK] IRP_MJ_READ: STATUS_DEVICE_NOT_READY\n"));

					break;
				}

				irp->IoStatus.Status = VdkReadSector(
					&disk_extension->DiskInfo,
					(ULONG)(io_stack->Parameters.Read.ByteOffset.QuadPart >>
						VDK_BYTE_SHIFT_TO_SECTOR),
					io_stack->Parameters.Read.Length >>
						VDK_BYTE_SHIFT_TO_SECTOR,
					MmGetSystemAddressForMdlPrettySafe(
						irp->MdlAddress, NormalPagePriority));

				if (NT_SUCCESS(irp->IoStatus.Status)) {
					irp->IoStatus.Information =
						io_stack->Parameters.Read.Length;
				}
				break;

			case IRP_MJ_WRITE:
				if (!disk_extension->DiskInfo.DiskType) {
					irp->IoStatus.Status = STATUS_DEVICE_NOT_READY;

					VDKTRACE(VDKWARN | VDKWRITE,
						("[VDK] IRP_MJ_READ: STATUS_DEVICE_NOT_READY\n"));

					break;
				}

				irp->IoStatus.Status = VdkWriteSector(
					&disk_extension->DiskInfo,
					(ULONG)(io_stack->Parameters.Write.ByteOffset.QuadPart >>
						VDK_BYTE_SHIFT_TO_SECTOR),
					io_stack->Parameters.Write.Length >>
						VDK_BYTE_SHIFT_TO_SECTOR,
					MmGetSystemAddressForMdlPrettySafe(
						irp->MdlAddress, NormalPagePriority));

				if (NT_SUCCESS(irp->IoStatus.Status)) {
					irp->IoStatus.Information =
						io_stack->Parameters.Read.Length;
				}
				break;

			case IRP_MJ_DEVICE_CONTROL:
				switch (io_stack->Parameters.DeviceIoControl.IoControlCode) {
				case IOCTL_VDK_OPEN_FILE:

#ifdef VDK_SUPPORT_NETWORK
					SeImpersonateClient(disk_extension->SecurityContext, NULL);
#endif	// VDK_SUPPORT_NETWORK

					irp->IoStatus.Status = VdkOpen(
						disk_object,
						(PVDK_OPEN_FILE_INFO)irp->AssociatedIrp.SystemBuffer);

#ifdef VDK_SUPPORT_NETWORK
					PsRevertToSelf();
#endif	// VDK_SUPPORT_NETWORK

					break;

				case IOCTL_VDK_CLOSE_FILE:
					//
					// Mark this device as a zombie if the volume is not dismounted
					//
					if ((io_stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG) ||
						!*(PULONG)irp->AssociatedIrp.SystemBuffer) &&
						disk_object->Vpb &&
						disk_object->Vpb->ReferenceCount > 1) {

						VDKTRACE(VDKIOCTL|VDKWARN,
							("[VDK] %ws becomes a zombie\n",
							disk_extension->DeviceName.Buffer));

						disk_extension->PartitionOrdinal = VDK_ZOMBIE;
					}

					irp->IoStatus.Status = VdkClose(disk_extension);
					break;

				case IOCTL_VDK_CREATE_DISK:
					{
						PDEVICE_OBJECT	device_obj;
						ULONG	disk_num = 0;

						device_obj = disk_object->DriverObject->DeviceObject;

						//
						//	count number of existing virtual disk devices
						//
						while (device_obj) {
							if (((PPART_EXTENSION)device_obj->DeviceExtension)->FirstPartition
								== (PDISK_EXTENSION)device_obj->DeviceExtension) {
								disk_num++;
							}
							device_obj = device_obj->NextDevice;
						}

						if (disk_num >= VDK_MAXIMUM_DISK_NUM) {
							VDKTRACE(VDKCREATE|VDKWARN,
								("[VDK] No more disk devies.\n"));

							irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
							break;
						}

						//
						//	create new virtual disk device
						//
						irp->IoStatus.Status = VdkCreateDisk(disk_object->DriverObject, disk_num);
						break;
					}

				case IOCTL_VDK_DELETE_DISK:
					{
						PDEVICE_OBJECT device_obj;
						PDISK_EXTENSION disk_ext = NULL;

						device_obj = disk_object->DriverObject->DeviceObject;

						//
						//	Look for the last created disk device (first in the chain)
						//
						while (device_obj) {
							disk_ext = (PDISK_EXTENSION)device_obj->DeviceExtension;

							if (disk_ext == disk_ext->FirstPartition) {
								break;
							}

							device_obj = device_obj->NextDevice;
						}

						if (!device_obj) {
							VDKTRACE(VDKDELETE,
								("[VDK] Disk device not found\n"));

							irp->IoStatus.Status = STATUS_DRIVER_INTERNAL_ERROR;
							break;
						}

						//
						//	Check if the device can be deleted
						//

						if (disk_ext->VirtualNumber == 0) {
							VDKTRACE(VDKDELETE,
								("[VDK] Cannot delete disk 0\n"));

							irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
							break;
						}

						if (disk_ext->DiskInfo.DiskType) {
							VDKTRACE(VDKDELETE,
								("[VDK] Device %ws in use.\n",
								disk_ext->DeviceName.Buffer));

							irp->IoStatus.Status = STATUS_DEVICE_BUSY;
							break;
						}

						if (device_obj->Vpb && device_obj->Vpb->ReferenceCount) {
							VDKTRACE(VDKDELETE,
								("[VDK] Device %ws reference count %lu\n",
								disk_ext->DeviceName.Buffer,
								device_obj->Vpb->ReferenceCount));

							irp->IoStatus.Status = STATUS_DEVICE_BUSY;
							break;
						}

						//
						//	Delete the device
						//
						VdkDeleteDevice(device_obj);

						irp->IoStatus.Status = STATUS_SUCCESS;
						break;
					}

				default:
					// This shouldn't happen...
					irp->IoStatus.Status = STATUS_DRIVER_INTERNAL_ERROR;
				}

				VDKTRACE((VDKINFO | VDKIOCTL),
					("[VDK] IRP_MJ_DEVICE_CONTROL\n"));

#if DBG
				if ((TraceFlags & VDKIOCTL)	&&
					(!NT_SUCCESS(irp->IoStatus.Status) || (TraceFlags & VDKINFO) == VDKINFO)) {

					PrintIoCtrlStatus(
						disk_extension->DeviceName.Buffer,
						io_stack->Parameters.DeviceIoControl.IoControlCode,
						irp->IoStatus.Status);
				}
#endif	// DBG
				break;

			default:
				// This shouldn't happen...
				irp->IoStatus.Status = STATUS_DRIVER_INTERNAL_ERROR;

				VDKTRACE(0,
					("[VDK] Unknown major function 0x%08lu\n",
					io_stack->MajorFunction));
			}

			if (irp->IoStatus.Status == STATUS_FILE_FORCED_CLOSED) {
				//
				// File has been forced to close e.g. due to network condition
				// Must update driver status
				//
				VDKTRACE(VDKIOCTL|VDKWARN,
					("[VDK] %ws becomes a zombie due to forced close.\n",
					disk_extension->DeviceName.Buffer));

				disk_extension->DiskInfo.DiskType = VDK_DISKTYPE_NONE;
				disk_extension->PartitionOrdinal = VDK_ZOMBIE;

				VdkClose(disk_extension);
			}

			//
			// complete the request
			//
			if (NT_SUCCESS(irp->IoStatus.Status)) {
				IoCompleteRequest(irp, IO_DISK_INCREMENT);
			}
			else {
				IoCompleteRequest(irp, IO_NO_INCREMENT);
			}

		} // while

	} // for (;;)

}

//
// Open image file(s)
//
NTSTATUS
VdkOpen(
	IN PDEVICE_OBJECT		DiskObject,
	IN PVDK_OPEN_FILE_INFO 	OpenFile)
{
	PDISK_EXTENSION	disk_extension;
	ULONG			idx;
	NTSTATUS		status = STATUS_SUCCESS;

	VDKTRACE(VDKOPEN | VDKINFO,("[VDK] VdkOpen - IN\n"));

	//
	// set necessary pointers
	//
	disk_extension = (PDISK_EXTENSION)DiskObject->DeviceExtension;

	//
	// open files in the OPEN_FILE list
	//
	status = VdkOpenDisk(OpenFile, &disk_extension->DiskInfo);

	if (!NT_SUCCESS(status)) {
		goto exit_func;
	}

	//
	// Retrieve alignment requirement for each file and use the
	// largest value as this device's alignment requirement.
	//
	for (idx = 0; idx < disk_extension->DiskInfo.FilesTotal; idx++) {

		if (disk_extension->DiskInfo.Files[idx].FileHandle) {

			IO_STATUS_BLOCK	io_status;
			FILE_ALIGNMENT_INFORMATION	file_alignment;

			status = ZwQueryInformationFile(
				disk_extension->DiskInfo.Files[idx].FileHandle,
				&io_status,
				&file_alignment,
				sizeof(FILE_ALIGNMENT_INFORMATION),
				FileAlignmentInformation);

			if (!NT_SUCCESS(status)) {
				VDKTRACE(VDKOPEN,
					("[VDK] ZwQueryInformationFile - FILE_ALIGNMENT_INFORMATION %s\n",
					VdkStatusStr(status)));

				goto exit_func;
			}

			if (DiskObject->AlignmentRequirement <
				file_alignment.AlignmentRequirement) {

				DiskObject->AlignmentRequirement =
					file_alignment.AlignmentRequirement;
			}
		}
	}

	//
	// store disk disk capacity also in this device's partition info
	//
	disk_extension->PartitionInfo.PartitionLength.QuadPart =
		(LONGLONG)disk_extension->DiskInfo.Capacity << VDK_BYTE_SHIFT_TO_SECTOR;

	//
	// Adjust device characteristics
	//
	if (disk_extension->DiskInfo.DiskType == VDK_DISKTYPE_READONLY) {
		DiskObject->Characteristics |= FILE_READ_ONLY_DEVICE;
	}
	else {
		DiskObject->Characteristics &= ~FILE_READ_ONLY_DEVICE;
	}

exit_func:

	VDKTRACE(VDKOPEN | VDKINFO, ("[VDK] VdkOpen - OUT\n"));

	return status;
}

//
// Close current image file(s)
//
NTSTATUS
VdkClose(
	IN PDISK_EXTENSION		DiskExtension)
{
	PDEVICE_OBJECT	next_partition;
	NTSTATUS 		status = STATUS_SUCCESS;

	VDKTRACE(VDKCLOSE | VDKINFO, ("[VDK] VdkClose - IN\n"));

	//
	// Detach all partition devices from the disk device
	//
	RtlZeroMemory(
		&DiskExtension->PartitionInfo,
		sizeof(DiskExtension->PartitionInfo));

	next_partition = DiskExtension->NextPartition;
	DiskExtension->NextPartition = NULL;

	while (next_partition) {
		PDEVICE_OBJECT current_part = next_partition;
		PPART_EXTENSION part_extension = current_part->DeviceExtension;
		next_partition = part_extension->NextPartition;

		if (!current_part->Vpb || !current_part->Vpb->ReferenceCount) {
			VDKTRACE(VDKCLOSE | VDKINFO,
				("[VDK] Deleting %ws from device chain.\n",
				part_extension->DeviceName.Buffer));

			VdkDeleteDevice(current_part);
		}
		else {
			//
			//	the device cannot be deleted yet
			//
			VDKTRACE(VDKCLOSE | VDKWARN,
				("[VDK] %ws becomes a zombie.\n",
				part_extension->DeviceName.Buffer));

			RtlZeroMemory(
				&part_extension->PartitionInfo,
				sizeof(part_extension->PartitionInfo));

			part_extension->PartitionOrdinal	= VDK_ZOMBIE;
			part_extension->FirstPartition 		= NULL;
			part_extension->NextPartition 		= NULL;

			if (part_extension->SymbolicLink.Buffer) {

				VDKTRACE(VDKCLOSE | VDKINFO,
					("[VDK] Deleting a symbolic link %ws\n",
					part_extension->SymbolicLink.Buffer));

				IoDeleteSymbolicLink(&part_extension->SymbolicLink);
				ExFreePool(part_extension->SymbolicLink.Buffer);

				RtlZeroMemory(
					&part_extension->SymbolicLink,
					sizeof(part_extension->SymbolicLink));
			}
		}
	}

	//
	// Close all files and release resources
	//

	VdkCloseDisk(&DiskExtension->DiskInfo);

	VDKTRACE(VDKCLOSE | VDKINFO, ("[VDK] VdkClose - OUT\n"));

	return status;
}

//
// Default fill character for format operation
//
#define MEDIA_FORMAT_FILL_DATA	0xf6

//
// Format tracks
// Actually, just fills specified range of tracks with fill characters
//
NTSTATUS
VdkFormat(
	IN PVDK_DISK_INFO		DiskInfo,
	IN PFORMAT_PARAMETERS	FormatParam)
{
	PUCHAR				format_buffer;
	ULONG				start_offset;
	ULONG				end_offset;
	NTSTATUS			status;

	VDKTRACE(VDKFORMAT | VDKINFO,
		("[VDK] VdkFormat\n"));

	//
	// prepare format buffer
	//
	{
		ULONG buf_length =
			DiskInfo->Sectors << VDK_BYTE_SHIFT_TO_SECTOR;

		format_buffer = ExAllocatePool(PagedPool, buf_length);

		if (format_buffer == NULL) {

			VDKTRACE(VDKFORMAT,
				("[VDK] cannot allocate format buffer\n"));

			return STATUS_INSUFFICIENT_RESOURCES;
		}

		RtlFillMemory(format_buffer, buf_length, MEDIA_FORMAT_FILL_DATA);
	}

	//
	// Prepare format parameters
	//

	start_offset =
		(FormatParam->StartCylinderNumber * DiskInfo->Tracks +
		FormatParam->StartHeadNumber) *
		DiskInfo->Sectors;

	end_offset =
		(FormatParam->EndCylinderNumber * DiskInfo->Tracks +
		FormatParam->EndHeadNumber) *
		DiskInfo->Sectors;

	VDKTRACE(VDKFORMAT | VDKINFO,
		("[VDK] Formatting sectors %lu - %lu\n",
		start_offset, end_offset));

	do {
		status = VdkWriteSector(
			DiskInfo,
			start_offset,
			DiskInfo->Sectors,
			format_buffer);

		if (!NT_SUCCESS(status)) {
			break;
		}

		start_offset += DiskInfo->Sectors;
	}
	while (start_offset <= end_offset);

	ExFreePool(format_buffer);

	return status;
}

// End Of File