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


/*
	vdkctl.c

	Virtual Disk driver control routines
	Copyright (C) 2003 Ken Kato
*/

#include "vdkbase.h"
#include "vdkutil.h"
#include "vdkfile.h"
#include "vdkioctl.h"
#include "vdkver.h"
#include "vdkctl.h"

#include "VDiskUtil.h"

#pragma warning(push,3)
#include 
#pragma warning(pop)

//
//	Version info translation
//
#define VERSIONINFO_PATH	"\\StringFileInfo\\" VDK_VERSIONINFO_LANG "\\OriginalFileName"

//
//	DOS device name (\\.\VirtualDK\Partition)
//
#define VDK_DOS_TEMPLATE	VDK_DEVICE_BASENAME "%u" "\\Partition%u"
#define VDK_DEV_TEMPLATE	"\\\\.\\" VDK_DOS_TEMPLATE
#define VDK_REG_CONFIG_KEY	"System\\CurrentControlSet\\Services\\" VDK_DEVICE_BASENAME

//
//	Get Virtual Disk driver configuration
//
DWORD VdkGetDriverConfig(
	LPTSTR		driver_path,
	LPDWORD		start_type,
	PULONG		device_num)
{
	SC_HANDLE	hScManager;				// Service Control Manager
	SC_HANDLE	hService;				// Service (= Driver)
	LPQUERY_SERVICE_CONFIG config = NULL;
	DWORD		return_len;
	DWORD		ret = ERROR_SUCCESS;

	if (driver_path) {
		ZeroMemory(driver_path, MAX_PATH);
	}
	if (start_type) {
		*start_type = (DWORD)-1;
	}
	if (device_num) {
		*device_num = 0;
	}

	//	Connect to the Service Control Manager

	hScManager = OpenSCManager(NULL, NULL, 0);

	if (hScManager == NULL) {
		ret = GetLastError();

		VDKTRACE(0,
			("VdkGetDriverConfig: OpenSCManager() - %s\n",
			VdkStatusStr(ret)));

		return ret;
	}

	//	Open Existing Service Object

	hService = OpenService(
		hScManager,						// Service control manager
		VDK_DEVICE_BASENAME,			// service name
		SERVICE_QUERY_CONFIG);			// service access mode

	if (hService == NULL) {
		ret = GetLastError();

		VDKTRACE(0,
			("VdkGetDriverConfig: OpenService(SERVICE_QUERY_CONFIG) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	//	Get the return_len of config information

	if (!QueryServiceConfig(hService, NULL, 0, &return_len)) {
		ret = GetLastError();

		if (ret == ERROR_INSUFFICIENT_BUFFER) {
			ret = ERROR_SUCCESS;
		}
		else {
			VDKTRACE(0, ("VdkGetDriverConfig: QueryServiceConfig() - %s\n",
				VdkStatusStr(ret)));

			goto cleanup;
		}
	}

	//	allocate a required buffer

	config = (QUERY_SERVICE_CONFIG *)VdkAllocMem(return_len);

	if (config == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDriverConfig: VdkAllocMem(%lu) - %s\n",
			return_len, VdkStatusStr(ret)));

		goto cleanup;
	}

	//	get the config information

	if (!QueryServiceConfig(hService, config, return_len, &return_len)) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDriverConfig: QueryServiceConfig() - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	//	copy information to output buffer

	if (driver_path) {
		if (strncmp(config->lpBinaryPathName, "\\??\\", 4) == 0) {
			strncpy(
				driver_path,
				config->lpBinaryPathName + 4,
				MAX_PATH);
		}
		else {
			strncpy(
				driver_path,
				config->lpBinaryPathName,
				MAX_PATH);
		}
	}

	if (start_type) {
		*start_type = config->dwStartType;
	}

	if (device_num) {
		//
		//	Get number of initial devices from registry
		//
		HKEY hKey;

		ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
			VDK_REG_CONFIG_KEY, 0, KEY_READ, &hKey);

		if (ret == ERROR_SUCCESS) {
			return_len = sizeof(DWORD);

			ret = RegQueryValueEx(
				hKey, VDK_REG_DISKNUM_VALUE, 0, NULL,
				(LPBYTE)device_num, &return_len);

			if (ret == ERROR_PATH_NOT_FOUND ||
				ret == ERROR_FILE_NOT_FOUND) {
				ret = ERROR_SUCCESS;
				*device_num = VDK_DEFAULT_DISK_NUM;
			}

			RegCloseKey(hKey);
		}

		if (ret != ERROR_SUCCESS) {
			VDKTRACE(0, ("VdkGetDriverConfig: RegOpenKeyEx - %s\n",
				VdkStatusStr(ret)));

			*device_num = 0;
		}
	}

cleanup:
	//	Free service config buffer

	if (config) {
		VdkFreeMem(config);
	}

	//	Close the service object handle

	if (hService) {
		CloseServiceHandle(hService);
	}

	//	Close handle to the service control manager.

	if (hScManager) {
		CloseServiceHandle(hScManager);
	}

	return ret;
}

//
//	Get Virtual Disk driver state
//
DWORD VdkGetDriverState(
	LPDWORD current_state)
{
	SC_HANDLE		hScManager = NULL;	// Service Control Manager
	SC_HANDLE		hService = NULL;	// Service (= Driver)
	SERVICE_STATUS	status;
	DWORD			ret = ERROR_SUCCESS;

	if (current_state) {
		*current_state = 0;
	}

	//	Connect to the Service Control Manager

	hScManager = OpenSCManager(NULL, NULL, 0);

	if (hScManager == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDriverState: OpenSCManager() - %s\n",
			VdkStatusStr(ret)));

		return ret;
	}

	//	Open Existing Service Object

	hService = OpenService(
		hScManager,						// Service control manager
		VDK_DEVICE_BASENAME,			// service name
		SERVICE_QUERY_STATUS);			// service access mode

	if (hService == NULL) {
		ret = GetLastError();

		if (ret == ERROR_SERVICE_DOES_NOT_EXIST) {
			if (current_state) {
				*current_state = VDK_NOT_INSTALLED;
			}
			ret = ERROR_SUCCESS;
		}
		else {
			VDKTRACE(0, (
				"VdkGetDriverState: OpenService(SERVICE_QUERY_STATUS) - %s\n",
				VdkStatusStr(ret)));
		}

		goto cleanup;
	}

	//	Get current driver status

	memset(&status, 0, sizeof(status));

	if (!QueryServiceStatus(hService, &status)) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDriverState: QueryServiceStatus() - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	if (current_state) {
		*current_state = status.dwCurrentState;
	}

cleanup:
	//	Close the service object handle

	if (hService) {
		CloseServiceHandle(hService);
	}

	//	Close handle to the service control manager.

	if (hScManager) {
		CloseServiceHandle(hScManager);
	}

	return ret;
}

//
//	Install Virtual Disk Driver dynamically
//
DWORD VdkInstall(
	LPCTSTR		driver_path,
	BOOL		auto_start)
{
	SC_HANDLE	hScManager;				// Service Control Manager
	SC_HANDLE	hService;				// Service (= Driver)
	TCHAR		full_path[MAX_PATH];
	DWORD		ret = ERROR_SUCCESS;

	//	Prepare driver binary's full path

	if (driver_path == NULL || *driver_path == '\0') {

		// default driver file is vdk.sys in the same directory as executable

		DWORD len = GetModuleFileName(
			NULL, full_path, sizeof(full_path));

		if (len == 0) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkInstall: GetModuleFileName - %s\n",
				VdkStatusStr(ret)));

			return ret;
		}

		//	search the last '\' char

		while (len > 0 && full_path[len - 1] != '\\') {
			len --;
		}

		strcpy(&full_path[len], VDK_DRIVER_FILENAME);
	}
	else {
		//	ensure that tha path is absolute full path

		LPTSTR file_part;

		if (GetFullPathName(
			driver_path,
			sizeof(full_path),
			full_path,
			&file_part) == 0)
		{
			ret = GetLastError();

			VDKTRACE(0, ("VdkInstall: GetFullPathName(%s) - %s\n",
				driver_path, VdkStatusStr(ret)));

			return ret;
		}

		//	only directory is specified?

		if (GetFileAttributes(full_path) & FILE_ATTRIBUTE_DIRECTORY) {
			strcat(full_path, "\\");
			strcat(full_path, VDK_DRIVER_FILENAME);
		}
	}

	//	Check if the file is a valid Virtual Disk driver

	ret = VdkCheckFileVersion(full_path, NULL);

	if (ret != ERROR_SUCCESS) {
		VDKTRACE(0, ("VdkInstall: VdkCheckDriverFile(%s)\n", full_path));

		return ret;
	}

	//	If the path is under %SystemRoot% make it relative to %SystemRoot%
	{
		TCHAR windir[MAX_PATH];
		int len;

		len = GetEnvironmentVariable("SystemRoot", windir, sizeof(windir));

		if (len > sizeof(windir)) {
			VDKTRACE(0, (
				"VdkInstall: %%SystemRoot%% contains too long text\n"));

			return ERROR_BAD_ENVIRONMENT;
		}
		else if (len && _strnicmp(windir, full_path, len) == 0) {
			memmove(full_path, full_path + len + 1, strlen(full_path) - len);
		}
	}

	//	Connect to the Service Control Manager

	hScManager = OpenSCManager(
		NULL,							// local machine
		NULL,							// local database
		SC_MANAGER_CREATE_SERVICE);		// access required

	if (hScManager == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkInstall: OpenSCManager() - %s\n", VdkStatusStr(ret)));

		return ret;
	}

	//	Create a new service object

	hService = CreateService(
		hScManager,						// service control manager
		VDK_DEVICE_BASENAME,			// internal service name
		VDK_DEVICE_BASENAME,			// display name
		SERVICE_ALL_ACCESS,				// access mode
		SERVICE_KERNEL_DRIVER,			// service type
		auto_start
			? SERVICE_AUTO_START
			: SERVICE_DEMAND_START,		// service start type
		SERVICE_ERROR_NORMAL,			// start error sevirity
		full_path,						// service image file path
		NULL,							// service group
		NULL,							// service tag
		NULL,							// service dependency
		NULL,							// use LocalSystem account
		NULL							// password for the account
	);

	if (!hService) {
		// Failed to create a service object
		ret = GetLastError();

		VDKTRACE(0, ("VdkInstall: CreateService() - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

cleanup:
	//	Close the service object handle

	if (hService) {
		CloseServiceHandle(hService);
	}

	//	Close handle to the service control manager.

	if (hScManager) {
		CloseServiceHandle(hScManager);
	}

	return ret;
}

//
//	Remove Virtual Disk Driver entry from system registry
//
DWORD VdkRemove()
{
	SC_HANDLE	hScManager;				// Service Control Manager
	SC_HANDLE	hService;				// Service (= Driver)
	DWORD		ret = ERROR_SUCCESS;

	//	Connect to the Service Control Manager

	hScManager = OpenSCManager(NULL, NULL, 0);

	if (hScManager == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkRemove: OpenSCManager() - %s\n", VdkStatusStr(ret)));

		return ret;
	}

	//	Open Existing Service Object

	hService = OpenService(
		hScManager,						// Service control manager
		VDK_DEVICE_BASENAME,			// service name
		DELETE);						// service access mode

	if (hService == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkRemove: OpenService(DELETE) - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

	//	Remove driver entry from registry

	if (!DeleteService(hService)) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkRemove: DeleteService() - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

cleanup:
	//	Close the service object handle

	if (hService) {
		CloseServiceHandle(hService);
	}

	//	Close handle to the service control manager.

	if (hScManager) {
		CloseServiceHandle(hScManager);
	}

	return ret;
}

//
//	Start Virtual Disk Driver
//
DWORD VdkStart(DWORD *state)
{
	SC_HANDLE hScManager;				// Service Control Manager
	SC_HANDLE hService;					// Service (= Driver)
	SERVICE_STATUS	stat;
	DWORD ret = ERROR_SUCCESS;
	int i;

	//	Connect to the Service Control Manager

	hScManager = OpenSCManager(NULL, NULL, 0);

	if (hScManager == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkStart: OpenSCManager() - %s\n", VdkStatusStr(ret)));

		return ret;
	}

	//	Open Existing Service Object

	hService = OpenService(
		hScManager,						// Service control manager
		VDK_DEVICE_BASENAME,			// service name
		SERVICE_START
		| SERVICE_QUERY_STATUS);		// service access mode

	if (hService == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkStart: OpenService(SERVICE_START) - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

	//	Start the driver

	if (!StartService(hService, 0, NULL)) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkStart: StartService() - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

	//	Ensure the driver is started
	for (i = 0;;) {
		if (!QueryServiceStatus(hService, &stat)) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkStart: QueryServiceStatus() - %s\n",
				VdkStatusStr(ret)));

			break;
		}

		if (stat.dwCurrentState == SERVICE_RUNNING || ++i == 5) {
			break;
		}

		Sleep(1000);
	}

	if (state) {
		*state = stat.dwCurrentState;
	}

cleanup:
	//	Close the service object handle

	if (hService) {
		CloseServiceHandle(hService);
	}

	//	Close handle to the service control manager.

	if (hScManager) {
		CloseServiceHandle(hScManager);
	}

	return ret;
}

//
//	Stop Virtual Disk Driver
//
DWORD VdkStop(DWORD *state)
{
	SC_HANDLE		hScManager;			// Service Control Manager
	SC_HANDLE		hService;			// Service (= Driver)
	SERVICE_STATUS	stat;
	DWORD			ret = ERROR_SUCCESS;
	int				i = 0;

	//	Connect to the Service Control Manager

	hScManager = OpenSCManager(NULL, NULL, 0);

	if (hScManager == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkStop: OpenSCManager() - %s\n", VdkStatusStr(ret)));

		return ret;
	}

	//	Open Existing Service Object

	hService = OpenService(
		hScManager,						// Service control manager
		VDK_DEVICE_BASENAME,			// service name
		SERVICE_STOP
		| SERVICE_QUERY_STATUS);		// service access mode

	if (hService == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkStop: OpenService(SERVICE_STOP) - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

	//	Stop the driver

	if (!ControlService(hService, SERVICE_CONTROL_STOP, &stat)) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkStop: ControlService(SERVICE_CONTROL_STOP) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	//	Ensure the driver is stopped
	while (stat.dwCurrentState != SERVICE_STOPPED && ++i < 5) {
		Sleep(1000);

		if (!QueryServiceStatus(hService, &stat)) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkStop: QueryServiceStatus() - %s\n",
				VdkStatusStr(ret)));

			break;
		}
	}

	if (state) {
		*state = stat.dwCurrentState;
	}

cleanup:
	//	Close the service object handle

	if (hService) {
		CloseServiceHandle(hService);
	}

	//	Close handle to the service control manager.

	if (hScManager) {
		CloseServiceHandle(hScManager);
	}

	return ret;
}

//
//	Set number of initial device
//
DWORD VdkSetDeviceNum(
	ULONG	device_num)
{
	DWORD ret;
	HKEY hKey;

	if (device_num == 0 || device_num > VDK_MAXIMUM_DISK_NUM) {
		return ERROR_INVALID_PARAMETER;
	}

	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		VDK_REG_CONFIG_KEY, 0, KEY_WRITE, &hKey);

	if (ret != ERROR_SUCCESS) {
		VDKTRACE(0,
			("VdkSetDeviceNum: RegOpenKeyEx - %s\n",
			VdkStatusStr(ret)));

		return ret;
	}

	ret = RegSetValueEx(
		hKey, VDK_REG_DISKNUM_VALUE, 0, REG_DWORD,
		(PBYTE)&device_num, sizeof(device_num));

	if (ret != ERROR_SUCCESS) {
		VDKTRACE(0,
			("VdkSetDeviceNum: RegSetValueEx - %s\n",
			VdkStatusStr(ret)));
	}

	RegCloseKey(hKey);

	return ret;
}

//
//	Check VDK driver file version
//
DWORD VdkCheckFileVersion(
	LPCTSTR	driver_path,
	PULONG	version)
{
	DWORD	return_len;
	DWORD	dummy;
	LPVOID	info;
	VS_FIXEDFILEINFO	*fixedinfo;
	DWORD	ret = ERROR_SUCCESS;
	LPTSTR	str;

	//	Check parameter

	if (!driver_path || !*driver_path) {
		return ERROR_INVALID_PARAMETER;
	}

	if (version) {
		*version = 0;
	}

	//	Check if the driver file is accessible?
	{
		HANDLE hFile = CreateFile(
			driver_path,
			GENERIC_READ,
			FILE_SHARE_READ | FILE_SHARE_WRITE,
			NULL,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			NULL);

		if (hFile == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			return ret;
		}

		CloseHandle(hFile);
	}

	//	Ensure that the driver binary is located on a local drive
	//	because device driver cannot be started on network drives.

	if (*driver_path == '\\' && *(driver_path + 1) == '\\') {
		//	full path is a UNC path -- \\server\dir\...

		VDKTRACE(0, ( "VdkCheckDriverFile: Driver is located on a network drive\n"));

		return ERROR_NETWORK_ACCESS_DENIED;
	}
	else {
		//	ensure that the drive letter is not a network drive

		char root[] = " :\\";

		root[0] = *driver_path;

		if (GetDriveType(root) == DRIVE_REMOTE) {
			// the drive is a network drive

			VDKTRACE(0, ( "VdkCheckDriverFile: Driver is located on a network drive\n"));

			return ERROR_NETWORK_ACCESS_DENIED;
		}
	}

	//	check file version

	return_len = GetFileVersionInfoSize((LPTSTR)driver_path, &dummy);

	if (return_len == 0) {
		ret = ERROR_BAD_FORMAT;

		VDKTRACE(0, (
			"VdkCheckDriverFile: GetFileVersionInfoSize == 0\n"));

		return ret;
	}

	if ((info = VdkAllocMem(return_len)) == NULL) {
		ret = GetLastError();

		VDKTRACE(0, (
			"VdkCheckDriverFile: VdkAllocMem(%lu) - %s\n", return_len, VdkStatusStr(ret)));

		return ret;
	}

	if (!GetFileVersionInfo((LPTSTR)driver_path, 0, return_len, info)) {
		ret = GetLastError();

		VDKTRACE(0, (
			"VdkCheckDriverFile: GetFileVersionInfo - %s\n", VdkStatusStr(ret)));

		goto cleanup;
	}

	return_len = sizeof(fixedinfo);

	if (!VerQueryValue(info, "\\", (PVOID *)&fixedinfo, (PUINT)&return_len)) {

		VDKTRACE(0, (
			"VdkCheckDriverFile: Failed to get fixed version info\n"));

		ret = ERROR_BAD_FORMAT;

		goto cleanup;
	}

	if (version) {
		*version = fixedinfo->dwFileVersionMS;

		if (fixedinfo->dwFileFlags & VS_FF_DEBUG) {
			*version |= 0x00008000;
		}
	}

	if (fixedinfo->dwFileOS				!= VOS_NT_WINDOWS32 ||
		fixedinfo->dwFileType			!= VFT_DRV			||
		fixedinfo->dwFileSubtype		!= VFT2_DRV_SYSTEM) {

		VDKTRACE(0, (
			"VdkCheckDriverFile: Invalid file type flags\n"));

		ret = ERROR_BAD_FORMAT;

		goto cleanup;
	}

	if (HIWORD(fixedinfo->dwFileVersionMS)	  != HIWORD(VDK_DRIVER_VERSION_VAL) ||
		HIWORD(fixedinfo->dwProductVersionMS) != HIWORD(VDK_PRODUCT_VERSION_VAL)) {

		VDKTRACE(0, (
			"VdkCheckDriverFile: Invalid version values - file:%08x, prod: %08x\n",
			fixedinfo->dwFileVersionMS, fixedinfo->dwProductVersionMS));

		ret = ERROR_REVISION_MISMATCH;

		goto cleanup;
	}

	if (!VerQueryValue(info, VERSIONINFO_PATH, (PVOID *)&str, (PUINT)&return_len)) {

		VDKTRACE(0, (
			"VdkCheckDriverFile: Failed to get OriginalFileName\n"));

		ret = ERROR_BAD_FORMAT;

		goto cleanup;
	}

	if (strcmp(str, VDK_DRIVER_FILENAME)) {
		VDKTRACE(0, (
			"VdkCheckDriverFile: Invalid original file name\n"));

		ret = ERROR_BAD_FORMAT;

		goto cleanup;
	}

cleanup:
	VdkFreeMem(info);

	return ret;
}

//
//	open the Virtual Disk device without showing the "Insert Disk"
//	dialog when the drive is empty.
//
HANDLE VdkOpenDevice(
	ULONG disk_number,
	ULONG part_number)
{
	TCHAR	device_name[sizeof(VDK_DEV_TEMPLATE) + 10];
	HANDLE	hDevice;
	UINT	err_mode;

	sprintf(device_name, VDK_DEV_TEMPLATE, disk_number, part_number);

	// change error mode in order to avoid "Insert Disk" dialog

	err_mode = SetErrorMode(SEM_FAILCRITICALERRORS);

	//	open the Virtual Disk device

	hDevice = CreateFile(
		device_name,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_NO_BUFFERING,
		NULL);

	//	revert to the previous error mode

	SetErrorMode(err_mode);

	//	return the handle to the Virtual Disk device

	return hDevice;
}

//
//	check running VDK driver version
//
DWORD VdkCheckVersion(
	HANDLE	hDevice,
	PULONG	version)
{
	ULONG	ver;
	DWORD	len;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	if (version) {
		*version = 0;
	}

	//
	//	Open Virtual Disk device
	//
	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(0, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkCheckVersion: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				0, 0, VdkStatusStr(ret)));

			goto cleanup;
		}

		close_device = TRUE;
	}

	if (!DeviceIoControl(
		hDevice,
		IOCTL_VDK_GET_VERSION,
		NULL,
		0,
		&ver,
		sizeof(ver),
		&len,
		NULL))
	{
		ret = GetLastError();

		VDKTRACE(0, ("VdkCheckVersion: DeviceIoControl(IOCTL_VDK_GET_VERSION) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	if (HIWORD(ver) != HIWORD(VDK_DRIVER_VERSION_VAL)) {
		ret = ERROR_REVISION_MISMATCH;
	}

	if (version) {
		*version = ver;
	}

cleanup:
	if (hDevice != INVALID_HANDLE_VALUE && close_device) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Get running driver information
//
DWORD VdkGetDriverInfo(
	HANDLE		hDevice,
	PULONG		disk_device,
	PULONG		attached_part,
	PULONG		orphaned_part,
	PULONG		reference_count)
{
	DWORD	tmp;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;
	VDK_DRIVER_INFO	driver_info;

	if (disk_device) {
		*disk_device = 0;
	}

	if (attached_part) {
		*attached_part = 0;
	}

	if (orphaned_part) {
		*orphaned_part = 0;
	}

	if (reference_count) {
		*reference_count = 0;
	}

	//
	//	Open Virtual Disk device
	//
	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(0, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkGetDriverInfo: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				0, 0, VdkStatusStr(ret)));

			goto cleanup;
		}

		close_device = TRUE;
	}

	//
	//	Query driver information
	//
	if (!DeviceIoControl(hDevice, IOCTL_VDK_DRIVER_INFO,
		NULL, 0, &driver_info, sizeof(driver_info), &tmp, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDriverInfo: IOCTL_VDK_DRIVER_INFO - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	if (disk_device) {
		*disk_device = driver_info.DiskDevices;
	}
	if (attached_part) {
		*attached_part = driver_info.AttachedParts;
	}
	if (orphaned_part) {
		*orphaned_part = driver_info.OrphanedParts;
	}
	if (reference_count) {
		*reference_count = driver_info.TotalReference;
	}

cleanup:
	if (hDevice != INVALID_HANDLE_VALUE && close_device) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Create a new disk device
//
DWORD VdkCreateDisk(
	HANDLE	hDevice)
{
	DWORD	tmp;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	//
	//	Open Virtual Disk device
	//
	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(0, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkCreateDisk: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				0, 0, VdkStatusStr(ret)));

			goto cleanup;
		}

		close_device = TRUE;
	}

	//
	//	Create virtual disk device
	//
	if (!DeviceIoControl(hDevice, IOCTL_VDK_CREATE_DISK,
		NULL, 0, NULL, 0, &tmp, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkCreateDisk: DeviceIoControl(IOCTL_VDK_CREATE_DISK) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

cleanup:
	if (hDevice != INVALID_HANDLE_VALUE && close_device) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Delete a disk device
//
DWORD VdkDeleteDisk(
	HANDLE	hDevice)
{
	DWORD	tmp;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	//
	//	Open Virtual Disk device
	//
	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(0, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkDeleteDisk: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				0, 0, VdkStatusStr(ret)));

			goto cleanup;
		}

		close_device = TRUE;
	}

	//
	//	Delete a virtual disk device
	//
	if (!DeviceIoControl(hDevice, IOCTL_VDK_DELETE_DISK,
		NULL, 0, NULL, 0, &tmp, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkDeleteDisk: DeviceIoControl(IOCTL_VDK_DELETE_DISK) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

cleanup:
	if (hDevice != INVALID_HANDLE_VALUE && close_device) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Check current device state
//
DWORD VdkCheckDeviceState(
	HANDLE		hDevice,
	ULONG		disk_number,
	ULONG		part_number)
{
	DWORD	tmp;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		hDevice = VdkOpenDevice(disk_number, part_number);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkCheckDeviceState: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				disk_number, part_number, VdkStatusStr(ret)));

			return ret;
		}

		close_device = TRUE;
	}

	if (!DeviceIoControl(hDevice, IOCTL_DISK_IS_WRITABLE,
		NULL, 0, NULL, 0, &tmp, NULL)) {

		ret = GetLastError();

		if (ret == ERROR_BUSY) {
			//	drive is a zombie -- try to dismount
			if (VdkDismount(hDevice, 0, 0, TRUE) == ERROR_SUCCESS) {
				//	succeeded to dismount
				ret = ERROR_NOT_READY;
			}
		}
		else if (ret == ERROR_WRITE_PROTECT) {
			ret = ERROR_SUCCESS;
		}

#ifdef VDK_DEBUG
		if (ret != ERROR_NOT_READY) {
			VDKTRACE(0, ("VdkCheckDeviceState: IOCTL_DISK_IS_WRITABLE - %s\n",
				VdkStatusStr(ret)));
		}
#endif	// VDK_DEBUG
	}

	if (close_device && hDevice != INVALID_HANDLE_VALUE) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Get device information
//
DWORD VdkGetDeviceList(
	PULONG				device_num,
	PVDK_DEVICE_INFO	*device_info)
{
	HANDLE hDevice;
	ULONG disk_num, part_num, orphan_num;
	DWORD result;
	DWORD ret = ERROR_SUCCESS;

	if (device_num) {
		*device_num = 0;
	}

	if (device_info) {
		*device_info = NULL;
	}

	//
	//	Get information about all devices
	//
	hDevice = VdkOpenDevice(0, 0);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDeviceList: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
			0, 0, VdkStatusStr(ret)));

		return ret;
	}

	ret = VdkGetDriverInfo(hDevice, &disk_num, &part_num, &orphan_num, NULL);

	if (ret != ERROR_SUCCESS) {
		goto cleanup;
	}

	*device_num = disk_num + part_num + orphan_num;

	*device_info = (PVDK_DEVICE_INFO)VdkAllocMem(*device_num * sizeof(VDK_DEVICE_INFO));

	if (*device_info == NULL) {
		ret = GetLastError();
		goto cleanup;
	}

	if (!DeviceIoControl(hDevice, IOCTL_VDK_DEVICE_INFO, device_num, sizeof(device_num),
		(LPBYTE)*device_info, *device_num * sizeof(VDK_DEVICE_INFO), &result, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkGetDeviceList: IOCTL_VDK_DEVICE_INFO - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

cleanup:
	CloseHandle(hDevice);

	return ret;
}


#ifdef VDK_DEBUG
//
//	Get and set debug trace flags
//
ULONG VdkTraceFlags(
	PULONG		flags)
{
	HANDLE	hDevice;
	ULONG	result;
	ULONG	current = 0;

	hDevice = VdkOpenDevice(0, 0);

	if (hDevice != INVALID_HANDLE_VALUE) {
		DeviceIoControl(hDevice, IOCTL_VDK_DEBUG_TRACE,
			flags, flags ? sizeof(ULONG) : 0,
			¤t, sizeof(current),
			&result, NULL);

		CloseHandle(hDevice);
	}

	return current;
}
#endif

//
//	Open Virtual Disk Image File
//
DWORD VdkOpen(
	HANDLE	hDevice,
	ULONG	disk_number,
	PVOID	pDisk,
	ULONG	AccessMode)
{
	PVDK_OPEN_FILE_INFO		open_info = NULL;
	ULONG	info_len;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	if (!pDisk) {
		return ERROR_INVALID_PARAMETER;
	}

	ret = VDiskMapToOpenInfo(pDisk, (PVOID *)&open_info, &info_len);

	if (ret != ERROR_SUCCESS) {
		return ret;
	}

	open_info->DiskType = AccessMode;

	//
	//	Open Virtual Disk device
	//
	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(disk_number, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkOpen: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				disk_number, 0, VdkStatusStr(ret)));

			goto cleanup;
		}

		close_device = TRUE;
	}

	//
	//	Check driver version
	//
	ret = VdkCheckVersion(hDevice, NULL);

	if (ret != ERROR_SUCCESS) {
		goto cleanup;
	}

	//
	//	Open image file
	//
	if (!DeviceIoControl(
		hDevice,
		IOCTL_VDK_OPEN_FILE,
		open_info,
		info_len,
		NULL,
		0,
		&info_len,
		NULL))
	{
		ret = GetLastError();

		VDKTRACE(0, ("VdkOpen: DeviceIoControl(IOCTL_VDK_OPEN_FILE) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	//
	// update device configuration accordingly
	//
	if (!DeviceIoControl(
		hDevice,
		IOCTL_VDK_UPDATE_DEVICE,
		NULL,
		0,
		NULL,
		0,
		&info_len,
		NULL))
	{
		ULONG graceful = TRUE;

		ret = GetLastError();

		VDKTRACE(0, ("VdkOpen: DeviceIoControl(IOCTL_VDK_UPDATE_DEVICE) - %s\n",
			VdkStatusStr(ret)));

		DeviceIoControl(hDevice, IOCTL_VDK_CLOSE_FILE,
			&graceful, sizeof(graceful), NULL, 0, &info_len, NULL);

		goto cleanup;
	}

cleanup:
	if (hDevice != INVALID_HANDLE_VALUE && close_device) {
		CloseHandle(hDevice);
	}

	if (open_info) {
		VdkFreeMem(open_info);
	}

	return ret;
}

//
//	Close Image File
//
DWORD VdkClose(
	HANDLE	hDevice,
	ULONG	disk_number,
	ULONG	graceful)
{
	DWORD	return_len;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(disk_number, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkClose: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				disk_number, 0, VdkStatusStr(ret)));

			goto cleanup;
		}

		close_device = TRUE;
	}

	if (!DeviceIoControl(hDevice, IOCTL_VDK_CLOSE_FILE,
		&graceful, sizeof(graceful), NULL, 0, &return_len, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkClose: IOCTL_VDK_CLOSE_FILE - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

cleanup:

	if (close_device && hDevice != INVALID_HANDLE_VALUE) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Get Virtual Disk image file info
//
DWORD VdkGetFileInfo(
	HANDLE	hDevice,
	ULONG	disk_number,
	PVDK_OPEN_FILE_INFO	*file_info)
{
	ULONG	info_size;
	DWORD	return_len;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	if (!file_info) {
		return ERROR_INVALID_PARAMETER;
	}

	*file_info = NULL;

	//
	//	Open device
	//
	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		if ((hDevice = VdkOpenDevice(disk_number, 0)) == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, (
				"VdkGetFileInfo: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				disk_number, 0, VdkStatusStr(ret)));

			goto cleanup;;
		}

		close_device = TRUE;
	}

	//
	//	Query file information size
	//
	if (!DeviceIoControl(hDevice, IOCTL_VDK_QUERY_FILE_SIZE,
		NULL, 0, &info_size, sizeof(info_size), &return_len, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkGetFileInfo: DeviceIoControl(IOCTL_VDK_QUERY_FILE_SIZE) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	if (info_size == 0) {
		goto cleanup;
	}

	//
	//	Allocate information area
	//
	if ((*file_info = (PVDK_OPEN_FILE_INFO)VdkAllocMem(info_size)) == NULL) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkGetFileInfo: VdkAllocMem(%lu) - %s\n",
			info_size, VdkStatusStr(ret)));

		goto cleanup;
	}

	memset(*file_info, 0, info_size);

	//
	//	Query file information
	//
	if (!DeviceIoControl(hDevice, IOCTL_VDK_QUERY_FILE,
		NULL, 0, *file_info, info_size, &return_len, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkGetFileInfo: DeviceIoControl(IOCTL_VDK_QUERY_FILE) - %s\n",
			VdkStatusStr(ret)));

		VdkFreeMem(*file_info);
		*file_info = NULL;

		goto cleanup;
	}

cleanup:
	if (close_device && hDevice != INVALID_HANDLE_VALUE) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Dismount a single virtual partition
//
DWORD VdkDismount(
	HANDLE	hDevice,
	ULONG	disk_number,
	ULONG	part_number,
	ULONG	unlock)
{
	DWORD	return_len;
	BOOL	close_device = FALSE;
	DWORD	ret = ERROR_SUCCESS;

	if (!hDevice || hDevice == INVALID_HANDLE_VALUE) {
		hDevice = VdkOpenDevice(disk_number, part_number);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkDismount: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
				disk_number, part_number, VdkStatusStr(ret)));

			return ret;
		}

		close_device = TRUE;
	}

	if (!DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME,
		NULL, 0, NULL, 0, &return_len, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, (
			"VdkDismount: DeviceIoControl(FSCTL_LOCK_VOLUME) - %s\n",
			VdkStatusStr(ret)));

		goto cleanup;
	}

	if (!DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME,
		NULL, 0, NULL, 0, &return_len, NULL)) {

		ret = GetLastError();

		VDKTRACE(0, ("VdkDismount: DeviceIoControl(FSCTL_DISMOUNT_VOLUME) - %s\n",
			VdkStatusStr(ret)));
	}

	if (ret == ERROR_SUCCESS) {
		if (!DeviceIoControl(hDevice, IOCTL_VDK_NOTIFY_DISMOUNT,
			NULL, 0, NULL, 0, &return_len, NULL)) {

			VDKTRACE(0, ("VdkDismount: DeviceIoControl(IOCTL_VDK_NOTIFY_DISMOUNT) - %s\n",
				VdkStatusStr(GetLastError())));
		}
	}

	if (unlock && !DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME,
		NULL, 0, NULL, 0, &return_len, NULL)) {

		VDKTRACE(0, ("VdkDismount: DeviceIoControl(FSCTL_UNLOCK_VOLUME) - %s\n",
			VdkStatusStr(GetLastError())));
	}

cleanup:
	if (hDevice != INVALID_HANDLE_VALUE && close_device) {
		CloseHandle(hDevice);
	}

	return ret;
}

//
//	Dismount all virtual partition devices
//
DWORD VdkDismountAll(BOOL zombie_only)
{
	HANDLE hDevice;
	ULONG device_num = 0;
	PVDK_DEVICE_INFO device_info = NULL;
	DWORD tmp;
	DWORD ret;

	//
	//	Get information about all devices
	//
	ret = VdkGetDeviceList(&device_num, &device_info);

	if (ret != VDK_OK) {
		return ret;
	}

	//
	//	dismount existing devices
	//
	for (tmp = 0; tmp < device_num; tmp++) {
		if (!device_info[tmp].ReferenceCount ||
			(zombie_only && !device_info[tmp].Zombie)) {
			continue;
		}

		DefineDosDevice(DDD_RAW_TARGET_PATH, "VDKTMP", device_info[tmp].DeviceName);

		hDevice = CreateFile(
			"\\\\.\\VDKTMP",
			GENERIC_READ | GENERIC_WRITE,
			FILE_SHARE_READ | FILE_SHARE_WRITE,
			NULL,
			OPEN_EXISTING,
			FILE_FLAG_NO_BUFFERING,
			NULL);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			VDKTRACE(0, ("VdkDismountAll: CreateFile(\\\\.\\VDKTMP) - %s\n",
				VdkStatusStr(ret)));
		}

		DefineDosDevice(DDD_REMOVE_DEFINITION, "VDKTMP", NULL);

		if (ret == ERROR_SUCCESS) {
			VdkDismount(hDevice, 0, 0, TRUE);
			CloseHandle(hDevice);
		}
	}

	//
	//	check if all devices are released
	//
	ret = VdkGetDriverInfo(NULL, NULL, NULL, NULL, &tmp);

	if (ret == ERROR_SUCCESS && tmp > 1) {
		ret = ERROR_BUSY;
	}

	if (device_info) {
		VdkFreeMem(device_info);
	}

	return ret;
}

//
//	Get VDK device name in the kernel namespace
//
DWORD VdkGetDeviceName(
	ULONG disk_number,
	ULONG part_number,
	LPTSTR buf)
{
	TCHAR dos_name[sizeof(VDK_DOS_TEMPLATE) + 10];
	TCHAR dir_name[MAX_DEVNAME_LEN];

	*buf = '\0';

	sprintf(dos_name, VDK_DEVICE_BASENAME "%u", disk_number);

	if (!QueryDosDevice(dos_name, dir_name, sizeof(dir_name))) {
		return GetLastError();
	}

	if (part_number != (ULONG)-1) {
		sprintf(buf, "%s\\Partition%lu", dir_name, part_number);
	}
	else {
		strcpy(buf, dir_name);
	}

	return ERROR_SUCCESS;
}

//
//	Get Virtual Disk drive letter
//
DWORD VdkGetDriveLetter(
	ULONG	disk_number,
	ULONG	part_number,
	TCHAR	*drive_letter)
{
	DWORD	logical_drives;
	TCHAR	dos_device[] = " :";
	TCHAR	device_name[MAX_PATH], dos_name[MAX_PATH];
	DWORD	ret = ERROR_SUCCESS;

	if (!drive_letter) {
		return ERROR_INVALID_PARAMETER;
	}

	*drive_letter = '\0';

	ret = VdkGetDeviceName(disk_number, part_number, device_name);

	if (ret != ERROR_SUCCESS) {
		return ret;
	}

	logical_drives = GetLogicalDrives();

	if (logical_drives == 0) {
		ret = GetLastError();

		VDKTRACE(0, (
			"VdkGetDriveLetter: GetLogicalDrives - %s\n", VdkStatusStr(ret)));

		return ret;
	}

	dos_device[0] = 'A';

	while (logical_drives) {
		if (logical_drives & 0x01) {
			if (QueryDosDevice(dos_device, dos_name, sizeof(dos_name))) {
				if (_stricmp(device_name, dos_name) == 0) {
					*drive_letter = dos_device[0];
					return ERROR_SUCCESS;
				}
			}
			else {
				ret = GetLastError();

				VDKTRACE(0, (
					"VdkGetDriveLetter: QueryDosDevice(%s) - %s\n",
					dos_device, VdkStatusStr(ret)));

				return ret;
			}
		}
		logical_drives >>= 1;
		dos_device[0]++;
	}

	return ret;
}

//
//	Assign a DOS drive letter to Virtual Disk Drive
//
DWORD VdkSetDriveLetter(
	ULONG	disk_number,
	ULONG	part_number,
	TCHAR	drive_letter)
{
	DWORD ret = ERROR_SUCCESS;
	TCHAR dos_device[] = " :";
	TCHAR device_name[MAX_PATH], dos_name[MAX_PATH];

	if (!isalpha(drive_letter)) {
		return ERROR_INVALID_PARAMETER;
	}

	//	check if device is active

	ret = VdkCheckDeviceState(NULL, disk_number, part_number);

	if (ret != ERROR_SUCCESS) {
		return ret;
	}

	//	Check if the drive letter is already in use

	dos_device[0] = (TCHAR)toupper(drive_letter);

	ret = VdkGetDeviceName(disk_number, part_number, device_name);

	if (ret != ERROR_SUCCESS) {
		return ret;
	}

	if (QueryDosDevice(dos_device, dos_name, sizeof(dos_name))) {
		if (strcmp(dos_name, device_name) == 0) {
			return ERROR_SUCCESS;
		}
		else {
			VDKTRACE(0, (
				"VdkSetDriveLetter: Drive letter '%c' is linked to '%s'\n",
				dos_device[0], dos_name));

			return ERROR_ALREADY_ASSIGNED;
		}
	}
	else {
		if ((ret = GetLastError()) != ERROR_FILE_NOT_FOUND) {
			VDKTRACE(0, (
				"VdkSetDriveLetter: QueryDosDevice(%s) - %lu\n",
				dos_device, VdkStatusStr(ret)));

			return ret;
		}
	}

	//	Check if the Virtual Disk drive has a DriveLetter already assigned

	ret = VdkGetDriveLetter(disk_number, part_number, &drive_letter);

	if (ret != ERROR_SUCCESS) {
		return ret;
	}

	if (isalpha(drive_letter)) {
		if (drive_letter == dos_device[0]) {
			return ERROR_SUCCESS;
		}
		else {
			VDKTRACE(0, ("VdkSetDriveLetter: Drive Letter '%c' is already assigned to %s\n",
				drive_letter, device_name));

			return ERROR_ALREADY_ASSIGNED;
		}
	}

	//	Assign the new drive letter

	if (!DefineDosDevice(DDD_RAW_TARGET_PATH, dos_device, device_name)) {
		ret = GetLastError();

		VDKTRACE(0, ("VdkSetDriveLetter: DefineDosDevice(%s) - %s\n",
			dos_device, VdkStatusStr(ret)));
	}

	return ret;
}

//
//	Remove Dos Drive Letter
//
DWORD VdkDelDriveLetter(
	TCHAR	drive_letter)
{
	HANDLE	hDevice;
	ULONG	tmp;
	TCHAR	dos_device[] = "\\\\.\\ :";
	DWORD	ret = ERROR_SUCCESS;

	if (!isalpha(drive_letter)) {
		return ERROR_INVALID_DRIVE;
	}

	dos_device[4] = drive_letter;

	//
	//	Check if the target device is a VDK device
	//
	hDevice = CreateFile(
		dos_device,
		GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();

		if (ret != ERROR_FILE_NOT_FOUND &&
			ret != ERROR_PATH_NOT_FOUND) {

			return ret;
		}

		goto execute;
	}

	if (!DeviceIoControl(hDevice, IOCTL_VDK_GET_VERSION,
		NULL, 0, NULL, 0, &tmp, NULL)) {

		ret = ERROR_INVALID_PARAMETER;
	}

	CloseHandle(hDevice);

	if (ret != ERROR_SUCCESS) {
		return ret;
	}

execute:
	if (!DefineDosDevice(DDD_REMOVE_DEFINITION, &dos_device[4], NULL)) {
		ret = GetLastError();

		VDKTRACE(0, (
			"VdkUnlink: DefineDosDevice(%s) - %s\n",
			&dos_device[4], VdkStatusStr(ret)));
	}

	return ret;
}

//
// choose first available drive letter
//
char ChooseDriveLetter()
{
	DWORD	logical_drives = GetLogicalDrives();
	char	drive_letter = 'C';

	if (logical_drives == 0) {
		return '\0';
	}

	//
	//	Do not assign A and B to Virtual Disk even if they are not used
	//
	logical_drives >>= 2;

	while (logical_drives & 0x1) {
		logical_drives >>= 1;
		drive_letter++;
	}

	if (drive_letter > 'Z') {
		return '\0';
	}

	return drive_letter;
}

//
//	Ensure Virtual Disk image is closed
//
DWORD VdkCloseDrive(
	ULONG			disk_number,
	VDK_CALLBACK	retrycb,
	VDK_CALLBACK	contcb,
	PVOID			param)
{
	HANDLE	hDisk;
	TCHAR	vdk_dev[MAX_PATH];
	ULONG	dos_dev[26];
	ULONG	part_num;
	ULONG	max_parts;
	ULONG	graceful;
	ULONG	i;
	DWORD	ret = ERROR_SUCCESS;

	VdkGetDeviceName(disk_number, 0, vdk_dev);

	//
	//	Delete all drive letters
	//
	for (i = 0; i < 26; i++) {
		TCHAR	dos_link[] = " :";
		TCHAR	dos_device[MAX_PATH];

		dos_link[0] = (TCHAR)(i + 'a');
		dos_device[0] = '\0';

		if (QueryDosDevice(dos_link, dos_device, sizeof(dos_device)) &&
			!_strnicmp(vdk_dev, dos_device, strlen(vdk_dev) - 1)) {

			dos_dev[i] = atol(dos_device + strlen(vdk_dev) - 1);

			DefineDosDevice(
				(DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE),
				dos_link,
				dos_device);
		}
		else {
			dos_dev[i] = (ULONG)-1;
		}
	}

	hDisk = VdkOpenDevice(disk_number, 0);

	if (hDisk == INVALID_HANDLE_VALUE) {
		ret = VdkLastError();

		VDKTRACE(0, ("VdkCloseDrive: CreateFile(" VDK_DEV_TEMPLATE ") - %s\n",
			disk_number, 0, VdkStatusStr(ret)));

		return ret;
	}

	if (!DeviceIoControl(hDisk, IOCTL_VDK_NUMBER_OF_PARTS,
		NULL, 0, &max_parts, sizeof(max_parts), &part_num, NULL)) {

		ret = VdkLastError();

		VDKTRACE(0, ("VdkCloseDrive: IOCTL_VDK_NUMBER_OF_PARTS - %s\n",
			VdkStatusStr(ret)));

		CloseHandle(hDisk);
		return ret;
	}

	//
	//	Dismount all partitions on this disk
	//
	graceful = TRUE;

	for (part_num = 0; part_num <= max_parts; part_num++) {

		ret = VdkCheckDeviceState(
			part_num ? NULL : hDisk, disk_number, part_num);

		if (ret != ERROR_SUCCESS) {
			break;
		}

dismount_retry:

		SetCursor(LoadCursor(NULL, IDC_WAIT));

		for(i = 0;;) {
			ret = VdkDismount(
				part_num ? NULL : hDisk, disk_number, part_num, FALSE);

			if (ret != ERROR_ACCESS_DENIED || ++i > 10) {
				break;
			}

			Sleep(500);			//	retry after 0.5 sec.
		}

		SetCursor(LoadCursor(NULL, IDC_ARROW));

		if (ret == ERROR_ACCESS_DENIED &&
			retrycb && (*retrycb)(param, ret)) {

			goto dismount_retry;
		}

		if (ret != ERROR_SUCCESS) {

			// Failed to dismount
			if (contcb && (*contcb)(param, ret)) {

				//	proceed anyway
				if (!part_num) {
					graceful = FALSE;
				}

				ret = ERROR_SUCCESS;
			}
			else {

				//	abort operation
				break;
			}
		}
	}	// next partition

	//
	//	couldn't dismount partition 0 -> file is not opened
	//
	if (!part_num) {
		CloseHandle(hDisk);
		return ret;
	}

	//
	//	Close virtual disk file
	//
	if (ret == ERROR_SUCCESS) {
		ret = VdkClose(hDisk, disk_number, graceful);
	}

	if (ret != ERROR_SUCCESS) {
		//
		//	Close failed -- restore drive letters
		//
		for (i = 0; i < 26; i++) {

			if (dos_dev[i] != -1) {
				if (VdkSetDriveLetter(disk_number, dos_dev[i], (char)(i + 'a')) != ERROR_SUCCESS) {
				}
			}
		}
	}

	CloseHandle(hDisk);

	return ret;
}

// End Of File