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


/*
	vdkcmd.c

	Virtual Disk drive control program (console version)
	Copyright (C) 2003 Ken Kato
*/

#include "vdkbase.h"
#include "vdkutil.h"
#include "vdkfile.h"
#include "vdkioctl.h"
#include "vdkaccess.h"
#include "vdkpart.h"
#include "vdkmsg.h"
#include "vdkver.h"
#include "vdkctl.h"

#include "cowdisk.h"
#include "vmdisk.h"

#include "VDiskUtil.h"

//
//	current driver state
//
static DWORD driver_state = VDK_NOT_INSTALLED;

//
//	command processing functions
//
typedef int (*cmdfnc)(char **args);

static int	Install(char **args);
static int	Remove(char **args);
static int	Start(char **args);
static int	Stop(char **args);
static int	Driver(char **args);
static int	DiskNum(char **args);
static int	Create(char **args);
static int	Delete(char **args);
static int	Device(char **args);
static int	Open(char **args);
static int	Close(char **args);
static int	Link(char **args);
static int	Unlink(char **args);
static int	Image(char **args);
static int	View(char **args);
static int	Help(char **args);
#ifdef _DEBUG
static int	Trace(char **args);
#endif	// _DEBUG

static const struct {
	char	*cmd;
	int		min_argc;
	int		max_argc;
	cmdfnc	func;
	DWORD	usage;
	DWORD	helpmsg;
}
Commands[] = {
	{ "INSTALL",	2, 4, Install,	MSG_USAGE_INSTALL	, MSG_HELP_INSTALL	},
	{ "REMOVE",		2, 2, Remove,	MSG_USAGE_REMOVE	, MSG_HELP_REMOVE	},
	{ "START",		2, 2, Start,	MSG_USAGE_START		, MSG_HELP_START	},
	{ "STOP",		2, 2, Stop,		MSG_USAGE_STOP		, MSG_HELP_STOP		},
	{ "DRIVER",		2, 3, Driver,	MSG_USAGE_DRIVER	, MSG_HELP_DRIVER	},
	{ "DISK",		3, 3, DiskNum,	MSG_USAGE_DISK		, MSG_HELP_DISK		},
	{ "CREATE",		2, 2, Create,	MSG_USAGE_CREATE	, MSG_HELP_CREATE	},
	{ "DELETE",		2, 2, Delete,	MSG_USAGE_DELETE	, MSG_HELP_DELETE	},
	{ "VIEW",		3, 3, View,		MSG_USAGE_VIEW		, MSG_HELP_VIEW		},
	{ "OPEN",		4, 7, Open,		MSG_USAGE_OPEN		, MSG_HELP_OPEN		},
	{ "CLOSE",		3, 4, Close,	MSG_USAGE_CLOSE		, MSG_HELP_CLOSE	},
	{ "LINK",		4, 5, Link,		MSG_USAGE_LINK		, MSG_HELP_LINK		},
	{ "ULINK",		3, 4, Unlink,	MSG_USAGE_ULINK		, MSG_HELP_ULINK	},
	{ "IMAGE",		2, 3, Image,	MSG_USAGE_IMAGE		, MSG_HELP_IMAGE	},
	{ "HELP",		2, 3, Help,		MSG_USAGE_HELP		, MSG_HELP_HELP		},
#ifdef _DEBUG
	{ "DEVICE",		2, 2, Device,	0, 0 },
	{ "TRACE",		2, 20, Trace,	0, 0 },
#endif	// _DEBUG
	{ NULL,			0, 0, NULL,		0, 0 }
};

//
//	local functions
//
static BOOL IsUserAdmin();
static DWORD GetDiskFromDrive(CHAR drive, PULONG disk);
static void PrintFileInfo(PVDK_OPEN_FILE_INFO file_info);
static DWORD Retry_Callback(PVOID param, DWORD err);
static DWORD Continue_Callback(PVOID param, DWORD err);
static void PartList_Callback(PPARTITION_ITEM pitem, PVOID param);
static void Assign_Callback(PPARTITION_ITEM pitem, PVOID param);

//
//	struct used for Assign_Callback parameter
//
typedef struct _ASSIGN_PARAM {
	ULONG	disk_number;
	ULONG	part_number;
	BOOL	read_only;
	PCHAR	drive_letters;
}
ASSIGN_PARAM, *PASSIGN_PARAM;

#ifdef VDK_DEBUG
#define VDK_DEBUG_TAG	" (debug)"
#else
#define VDK_DEBUG_TAG
#endif

//
//	main
//
int main(int argc, char **argv)
{
	char	*cmd;
	DWORD	ret;
	int		idx;

	//
	//	Reports memory leaks at process termination
	//
#ifdef VDK_DEBUG
	_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
#endif

	printf(
		VDK_PRODUCT_NAME " version "
		VDK_PRODUCT_VERSION_STR VDK_DEBUG_TAG "\n"
		"http://chitchat.at.infoseek.co.jp/vmware/\n\n"
	);

	if (!IsUserAdmin()) {
		PrintMessage(MSG_MUST_BE_ADMIN);
		return -1;
	}

	//
	//	At least one parameter (command) is required
	//
	if (argc < 2) {
		PrintMessage(MSG_HELP_USAGE);
		return -1;
	}

	//
	//	Get Current Driver state
	//
	if ((ret = VdkGetDriverState(&driver_state)) != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_STAT_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	//
	//	Try to dismount all zombie devices
	//
	VdkDismountAll(TRUE);

	//
	//	Process vdk command parameter
	//
	cmd = *(argv + 1);

	idx = 0;

	while (Commands[idx].cmd) {
		if (_stricmp(cmd, Commands[idx].cmd) == 0) {

			if (argc < Commands[idx].min_argc ||
				argc > Commands[idx].max_argc) {
				PrintMessage(Commands[idx].usage);
				return -1;
			}

			ret = (*Commands[idx].func)(argv + 2);

			return ret;
		}

		idx++;
	}

	//
	//	unknown command
	//
	PrintMessage(MSG_UNKNOWN_COMMAND, cmd);

	return -1;
}

//
//	Installs the Virtual Disk Driver Dynamically
//	Command Line Parameters:
//	vdk install [path] [/auto]
//	(optional) driver file path	- default to executive's dir
//	(optional) auto start switch - default to demand start
//
int	Install(char **args)
{
	char *install_path = NULL;
	BOOL auto_start = FALSE;

	DWORD ret;

	//	process parameters

	while (*args) {
		if (_stricmp(*args, "/auto") == 0) {
			if (auto_start) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return -1;
			}
			else {
				auto_start = TRUE;
			}
		}
		else if (**args == '/') {
			PrintMessage(MSG_UNKNOWN_OPTION, *args, "INSTALL");
			return -1;
		}
		else {
			if (install_path) {
				PrintMessage(MSG_DUPLICATE_ARGS, "path");
				return -1;
			}
			else {
				install_path = *args;
			}
		}
		args++;
	}

	//	already installed?

	if (driver_state != VDK_NOT_INSTALLED) {
		PrintMessage(MSG_ALREADY_INSTALLED);
		return -1;
	}

	//	install the driver

	if ((ret = VdkInstall(install_path, auto_start)) != ERROR_SUCCESS) {
		PrintMessage(MSG_INSTALL_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	//	operation successfull

	PrintMessage(MSG_INSTALL_OK);

	return 0;
}

//
//	Remove Virtual Disk Driver from system
//	Command Line Parameters: None
//
int	Remove(char **args)
{
	DWORD	ret;
	int		i;

	UNREFERENCED_PARAMETER(args);

	//	ensure the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure the driver is stopped

	if (driver_state != SERVICE_STOPPED) {
		if (Stop(NULL) == -1) {
			return -1;
		}
	}

	//	remove the driver

	if ((ret = VdkRemove()) != ERROR_SUCCESS) {
		PrintMessage(MSG_REMOVE_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	// Wait for the driver to be actually removed for 3 secs Max.

	for (i = 0; i < 10; i++) {
		ret = VdkGetDriverState(&driver_state);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_GET_STAT_NG);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}

		if (driver_state == VDK_NOT_INSTALLED) {
			break;
		}

		Sleep(300);
	}

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_REMOVE_OK);
	}
	else {
		PrintMessage(MSG_REMOVE_PENDING);
	}

	return 0;
}

//
//	Start the Virtual Disk Driver
//	Command Line Parameters: None
//
int	Start(char **args)
{
	DWORD	ret;

	UNREFERENCED_PARAMETER(args);

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		if ((ret = VdkInstall(NULL, FALSE)) == ERROR_SUCCESS) {
			PrintMessage(MSG_INSTALL_OK);
		}
		else {
			PrintMessage(MSG_INSTALL_NG);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}
	}

	//	ensure that the driver is not started yet

	if (driver_state == SERVICE_RUNNING) {
		PrintMessage(MSG_ALREADY_RUNNING);
		return -1;
	}

	//	start the driver

	if ((ret = VdkStart(&driver_state)) != ERROR_SUCCESS) {
		PrintMessage(MSG_START_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	//	operation successfull

	PrintMessage(MSG_START_OK);

	return 0;
}

//
//	Stop the Virtual Disk Driver
//	Command Line Parameters: None
//
int	Stop(char **args)
{
	DWORD ret;

	UNREFERENCED_PARAMETER(args);

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state == SERVICE_STOPPED) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	//	ensure that all image are unmounted

	if (driver_state == SERVICE_RUNNING) {
		char *param[] = { "*", NULL };

		if (Close(param) == -1) {
			return -1;
		}
	}

	ret = VdkDismountAll(FALSE);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_STOP_NG);
		PrintMessage(MSG_DRIVE_IN_USE);
		return -1;
	}

	//	stop the driver

	if ((ret = VdkStop(&driver_state)) != ERROR_SUCCESS) {
		PrintMessage(MSG_STOP_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	if (driver_state != SERVICE_STOPPED) {
		PrintMessage(MSG_STOP_PENDING);
		PrintMessage(MSG_RESTART_WARN);
		return -1;
	}

	PrintMessage(MSG_STOP_OK);

	return 0;
}

//
//	Show current driver status
//	Command Line Parameters: None
//
int	Driver(char **args)
{
	TCHAR	path[MAX_PATH];
	DWORD	start_type;
	ULONG	initial_dev;
	ULONG	version;
	ULONG	disk_device;
	ULONG	attached_part;
	ULONG	orphaned_part;
	ULONG	reference_count;
	DWORD	ret;

	// if the driver is not installed, no more info to show

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_CURRENT_STATUS);
		PrintMessage(MSG_STATUS_NOT_INST);
		return 0;
	}

	//	get current driver config

	if ((ret = VdkGetDriverConfig(path, &start_type, &initial_dev)) != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	//	print driver file path

	PrintMessage(MSG_DRIVER_LOCATION, path);

	//	print running driver version
	version = 0;

	if (driver_state == SERVICE_RUNNING) {
		ret = VdkCheckVersion(NULL, &version);
	}
	else {
		ret = VdkCheckFileVersion(path, &version);
	}

	if (ret != ERROR_SUCCESS &&
		ret != ERROR_REVISION_MISMATCH) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	if (version) {
		PrintMessage(MSG_DRIVER_VERSION,
			HIWORD(version),
			(version & 0x00007fff),
			(version & 0x00008000) ? " (debug)" : "");
	}

	//	print driver start type

	PrintMessage(MSG_START_TYPE);

	switch (start_type) {
	case SERVICE_AUTO_START:
		PrintMessage(MSG_START_AUTO);
		break;

	case SERVICE_BOOT_START:
		PrintMessage(MSG_START_BOOT);
		break;

	case SERVICE_DEMAND_START:
		PrintMessage(MSG_START_DEMAND);
		break;

	case SERVICE_DISABLED:
		PrintMessage(MSG_START_DISABLED);
		break;

	case SERVICE_SYSTEM_START :
		PrintMessage(MSG_START_SYSTEM);
		break;

	default:
		PrintMessage(MSG_UNKNOWN_ULONG, start_type);
		break;
	}

	//	print current status

	PrintMessage(MSG_CURRENT_STATUS);

	switch (driver_state) {
	case SERVICE_STOPPED:
		PrintMessage(MSG_STATUS_STOPPED);
		break;

	case SERVICE_START_PENDING:
		PrintMessage(MSG_STATUS_START_P);
		break;

	case SERVICE_STOP_PENDING:
		PrintMessage(MSG_STATUS_STOP_P);
		break;

	case SERVICE_RUNNING:
		PrintMessage(MSG_STATUS_RUNNING);
		break;

	case SERVICE_CONTINUE_PENDING:
		PrintMessage(MSG_STATUS_CONT_P);
		break;

	case SERVICE_PAUSE_PENDING:
		PrintMessage(MSG_STATUS_PAUSE_P);
		break;

	case SERVICE_PAUSED:
		PrintMessage(MSG_STATUS_PAUSED);
		break;

	default:
		PrintMessage(MSG_UNKNOWN_ULONG, driver_state);
		break;
	}

	if (version < VDK_DRIVER_VERSION_VAL) {
		return 0;
	}

	//	if driver is not running, no more info

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_DISK_DEVICE, initial_dev);
		return 0;
	}

	ret = VdkGetDriverInfo(NULL,
		&disk_device,
		&attached_part,
		&orphaned_part,
		&reference_count);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_DISK_DEVICE, disk_device);

	//
	//	hidden option
	//
	if (*args && !_stricmp(*args, "full")) {
		PrintMessage(MSG_ATTACHED_PART, attached_part);
		PrintMessage(MSG_ORPHANED_PART, orphaned_part);
		PrintMessage(MSG_REFERENCE_COUNT, reference_count);
	}

	return 0;
}

//
//	Set initial number of disk devices
//	Command Line Parameters:
//		device number
//
int DiskNum(char **args)
{
	ULONG	dev_num;
	DWORD	ret;

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	if (!isdigit(**args)) {
		PrintMessage(MSG_INVALID_DISKNUM, *args);
		return -1;
	}

	dev_num = atol(*args);

	if (dev_num == 0 || dev_num > VDK_MAXIMUM_DISK_NUM) {
		PrintMessage(MSG_INVALID_DISKNUM, *args);
		return -1;
	}

	ret = VdkSetDeviceNum(dev_num);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_DISKNUM_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_DISKNUM_OK, dev_num);

	return 0;
}

//
//	Create a new disk device
//	Command Line Parameters: none
//
int Create(char **args)
{
	ULONG disk_device;
	DWORD ret;

	UNREFERENCED_PARAMETER(args);

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	//	get current number of disk devices

	ret = VdkGetDriverInfo(NULL, &disk_device, NULL, NULL, NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	if (disk_device >= VDK_MAXIMUM_DISK_NUM) {
		PrintMessage(MSG_NO_MORE_DISK);
		return -1;
	}

	//	create a disk

	ret = VdkCreateDisk(NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_CREATEDISK_NG, disk_device);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_CREATEDISK_OK, disk_device);

	//	print new number of disk devices

	ret = VdkGetDriverInfo(NULL, &disk_device, NULL, NULL, NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_DISK_DEVICE, disk_device);

	return 0;
}

//
//	Delete a virtual disk device
//	Command Line Parameters: none
//
int Delete(char **args)
{
	ULONG disk_device;
	DWORD ret;

	UNREFERENCED_PARAMETER(args);

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	//	get current number of disk devices

	ret = VdkGetDriverInfo(NULL, &disk_device, NULL, NULL, NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	if (disk_device == 1) {
		PrintMessage(MSG_NO_LESS_DISK);
		return -1;
	}

	//	delete disk

	ret = VdkDeleteDisk(NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_DELETEDISK_NG, disk_device - 1);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_DELETEDISK_OK, disk_device - 1);

	//	print new number of disk devices

	ret = VdkGetDriverInfo(NULL, &disk_device, NULL, NULL, NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_DISK_DEVICE, disk_device);

	return 0;
}

//
//	Print disk image files information
//	Command Line Parameters:
//		path
//
int	View(char **args)
{
	PVOID				disk		= NULL;
	PVDK_OPEN_FILE_INFO	open_info	= NULL;
	VDK_DISK_INFO		disk_info	= { 0 };
	ULONG				info_size;
	DWORD				ret;

	//	Create File structure

	VDiskSetCallBack(VDiskCallback);

	ret = VDiskLoadFile(&disk, *args, NULL);

	if (ret != ERROR_SUCCESS || disk == NULL) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret == ERROR_SUCCESS ? ERROR_INVALID_FUNCTION : ret));
		goto cleanup;
	}

	ret = VDiskCreateTree(disk);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

	ret = VDiskMapToOpenInfo(disk, (PVOID *)&open_info, &info_size);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

	//	print image file information

	PrintMessage(MSG_DISKIMAGE_NAME, VDiskGetDiskName(disk));
	PrintFileInfo(open_info);

	//	open virtual disk

	open_info->DiskType = VDK_DISKTYPE_READONLY;

	ret = VdkOpenDisk(open_info, &disk_info);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

	//	print partition info

	PrintMessage(MSG_PARTITION_HEADER);

	ret = VdkListPartitions(&disk_info, NULL, 0, PartList_Callback, (PVOID)-1);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_PART_NG);
		printf("%s\n", VdkStatusStr(ret));
	}

cleanup:
	if (disk) {
		VDiskDelete(disk);
	}

	if (open_info) {
		VdkFreeMem(open_info);
	}

	if (disk_info.DiskType) {
		VdkCloseDisk(&disk_info);
	}

	return (ret == ERROR_SUCCESS ? 0 : -1);
}

//
//	Opens virtual disk image
//	Command Line Parameters:
//		disk number
//		image file path
//		(optional) access mode - default to read-only
//		(optional) partition to assign drive letter - default to all mountable
//		(optional) drive letters - default to the first available letter
//
int	Open(char **args)
{
	// for command line parameters
	ULONG	disk_number;
	LPTSTR	file_path;
	BOOL	writable		= FALSE;
	BOOL	undoable		= FALSE;
	BOOL	writeblock		= FALSE;
	LPTSTR	drive_letters	= NULL;
	ULONG	part_number		= (ULONG)-1;

	//	for virtual disk operation
	PVOID	disk			= NULL;

	// for vdk driver operation
	ULONG	access;
	HANDLE	hDevice			= NULL;
	PVDK_OPEN_FILE_INFO		file_info = NULL;
	ASSIGN_PARAM			assign;

	DWORD	ret;

	//	process command line parameters
	if (**args == '*') {
		disk_number = (ULONG)-1;
	}
	else if (isdigit(**args)) {
		disk_number = atol(*args);

		if (disk_number >= VDK_MAXIMUM_DISK_NUM) {
			PrintMessage(MSG_INVALID_DISK, *args);
			return -1;
		}
	}
	else {
		PrintMessage(MSG_INVALID_DISK, *args);
		return -1;
	}

	args++;

	if (!*args || !**args) {
		PrintMessage(MSG_USAGE_OPEN);
		return -1;
	}

	file_path = *args;

	while (*(++args)) {
		if (_stricmp(*args, "/rw") == 0) {
			if (writable) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return -1;
			}

			writable = TRUE;
		}
		else if (_stricmp(*args, "/undo") == 0) {
			if (undoable) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return -1;
			}

			undoable = TRUE;
		}
		else if (_stricmp(*args, "/wb") == 0) {
			if (writeblock) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return -1;
			}

			writeblock = TRUE;
		}
		else if (_strnicmp(*args, "/l:", 3) == 0) {
			if (drive_letters) {
				PrintMessage(MSG_DUPLICATE_ARGS, "/L");
				return -1;
			}

			drive_letters = *args + 3;
		}
		else if (_strnicmp(*args, "/p:", 3) == 0) {
			if (part_number != (ULONG)-1) {
				PrintMessage(MSG_DUPLICATE_ARGS, "/P");
				return -1;
			}

			if (!isdigit(*(*args + 3))) {
				PrintMessage(MSG_INVALID_PART, *args + 3);
				return -1;
			}

			part_number = atol(*args + 3);
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args, "OPEN");
			return -1;
		}
	}

	if ((writable && undoable) ||
		(writable && writeblock) ||
		(undoable && writeblock)) {
		PrintMessage(MSG_OPENMODE_OPTION);
		return -1;
	}

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		if ((ret = VdkInstall(NULL, FALSE)) == ERROR_SUCCESS) {
			PrintMessage(MSG_INSTALL_OK);
		}
		else {
			PrintMessage(MSG_INSTALL_NG);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}
	}

	//	ensure that the driver is started

	if (driver_state != SERVICE_RUNNING) {
		if ((ret = VdkStart(&driver_state)) == ERROR_SUCCESS) {
			PrintMessage(MSG_START_OK);
		}
		else {
			PrintMessage(MSG_START_NG);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}
	}

	//	Open device

	if (disk_number == (ULONG)-1) {
		disk_number = 0;

		for (;;) {
			ret = VdkCheckDeviceState(NULL, disk_number, 0);

			if (ret == ERROR_NOT_READY) {
				//	drive is empty -- use this disk
				break;
			}
			else if (ret == ERROR_SUCCESS || ret == ERROR_BUSY) {
				//	drive is busy --  try next disk
				CloseHandle(hDevice);
				disk_number++;
			}
			else if (ret == ERROR_PATH_NOT_FOUND) {
				//	no more drive
				//	-- create a new disk
				ret = VdkCreateDisk(NULL);

				if (ret != ERROR_SUCCESS) {
					PrintMessage(MSG_CREATEDISK_NG, disk_number);
					printf("%s\n", VdkStatusStr(ret));
					return -1;
				}

				PrintMessage(MSG_CREATEDISK_OK, disk_number);
			}
			else {
				//	unexpected error -- abort operation
				PrintMessage(MSG_OPENIMAGE_NG);
				printf("%s\n", VdkStatusStr(ret));
				return -1;
			}
		}
	}
	else {
		//	Ensure that another image is not opened

		ret = VdkCheckDeviceState(NULL, disk_number, 0);

		if (ret != ERROR_NOT_READY) {
			PrintMessage(MSG_OPENIMAGE_NG);
			printf("%s\n", VdkStatusStr(ret == ERROR_SUCCESS ? ERROR_BUSY : ret));
			return -1;
		}
	}

	hDevice = VdkOpenDevice(disk_number, 0);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	//
	//	Create File structure
	//
	VDiskSetCallBack(VDiskCallback);

	ret = VDiskLoadFile(&disk, file_path, NULL);

	if (ret != ERROR_SUCCESS || disk == NULL) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret == ERROR_SUCCESS ? ERROR_INVALID_FUNCTION : ret));
		goto cleanup;
	}

	ret = VDiskCreateTree(disk);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

	//
	//	Create REDO log
	//
	if (undoable) {
		ret = VDiskCreateRedo(&disk);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_CREATEREDO_NG);
			printf("%s\n", VdkStatusStr(ret));
			VDiskDelete(disk);
			return -1;
		}
	}

	//	Open the image files
	if (undoable || writable) {
		access = VDK_DISKTYPE_WRITABLE;
	}
	else if (writeblock) {
		access = VDK_DISKTYPE_WRITEBLOCK;
	}
	else {
		access = VDK_DISKTYPE_READONLY;
	}

	PrintMessage(MSG_VIRTUAL_DISK, disk_number);

	ret = VdkOpen(hDevice, 0, disk, access);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_OPENIMAGE_NG);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

	//
	//	print file information
	//
	if ((ret = VdkGetFileInfo(hDevice, 0, &file_info)) != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_IMAGE_NG, disk_number);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

	if (file_info) {
		PrintFileInfo(file_info);
	}

	//
	//	assign drive letters and print partition info
	//
	PrintMessage(MSG_PARTITION_HEADER);

	assign.disk_number	= disk_number;
	assign.part_number	= part_number;
	assign.read_only	= (access == VDK_DISKTYPE_READONLY);
	assign.drive_letters = drive_letters;

	ret = VdkListPartitions(NULL, hDevice, file_info->Capacity, Assign_Callback, &assign);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_PART_NG);
		printf("%s\n", VdkStatusStr(ret));
		goto cleanup;
	}

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

	if (file_info) {
		VdkFreeMem(file_info);
	}

	if (disk) {
		VDiskDelete(disk);
	}

	return (ret == ERROR_SUCCESS ? 0 : -1);
}

//
//	Close current file(s)
//	Command Line Parameters:
//		disk number or drive letter
//
#define CLOSE_PROMPT	0
#define CLOSE_QUIET		1
#define CLOSE_FORCE		2

int	Close(char **args)
{
	ULONG	disk_number;
	ULONG	max_disk;
	CHAR	drive_letter;
	ULONG	interact = CLOSE_PROMPT;
	DWORD	ret;

	if (**args == '*') {
		disk_number = (ULONG)-1;
		drive_letter = '\0';
	}
	else if (isdigit(**args)) {
		disk_number = atol(*args);
		drive_letter = '\0';
	}
	else if (isalpha(**args)) {
		disk_number = (ULONG)-1;
		drive_letter = (CHAR)toupper(**args);
	}
	else {
		PrintMessage(MSG_INVALID_DISK, *args);
		return -1;
	}

	if (*(++args)) {
		if (!_stricmp(*args, "/q")) {
			interact = CLOSE_QUIET;
		}
		else if (!_stricmp(*args, "/f")) {
			interact = CLOSE_FORCE;
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args, "CLOSE");
			return -1;
		}
	}

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	//	get the disk number from the drive letter

	if (drive_letter) {
		ret = GetDiskFromDrive(drive_letter, &disk_number);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_RESOLVE_LINK_NG, drive_letter);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}
	}

	//	Close virtual disk

	if (disk_number == (ULONG)-1) {
		disk_number = 0;

		ret = VdkGetDriverInfo(NULL, &max_disk, NULL, NULL, NULL);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_GET_CONFIG_NG);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}
	}
	else {
		max_disk = 0;
	}

	do {
		PrintMessage(MSG_CLOSING_DISK, disk_number);
		fflush(stdout);

		ret = VdkCloseDrive(
			disk_number, Retry_Callback, Continue_Callback, (PVOID)interact);

		if (ret == ERROR_SUCCESS) {
			PrintMessage(MSG_CLOSE_OK);
		}
		else if (ret == ERROR_NOT_READY) {
			PrintMessage(MSG_DEVICE_EMPTY);
		}
		else if (ret == ERROR_BUSY) {
			PrintMessage(MSG_DEVICE_BUSY);
		}
		else if (!interact || max_disk == 0) {
			return -1;
		}
	}
	while (++disk_number < max_disk);

	return 0;
}

//
//	Assign a drive letter to a partition
//	Command Line Parameters:
//		disk number - required
//		partition number - required
//		(optional) drive letter - default to first available letter
//
int	Link(char **args)
{
	ULONG	disk_number;
	ULONG	part_number;
	char	drive_letter;
	DWORD	ret;

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	//	process command line parameters

	if (!isdigit(**args)) {
		PrintMessage(MSG_INVALID_DISK, *args);
		return -1;
	}

	disk_number = atol(*(args++));

	if (!isdigit(**args)) {
		PrintMessage(MSG_INVALID_PART, *args);
		return -1;
	}

	part_number = atol(*(args++));

	if (*args) {
		if (!isalpha(**args)) {
			PrintMessage(MSG_UNKNOWN_OPTION, *args, "LINK");
			return -1;
		}

		drive_letter = **args;
	}
	else {
		drive_letter = ChooseDriveLetter();

		if (!isalpha(drive_letter)) {
			PrintMessage(MSG_DRIVE_FULL);
			return -1;
		}
	}

	drive_letter = (char)toupper(drive_letter);

	ret = VdkSetDriveLetter(disk_number, part_number, drive_letter);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_LINK_NG, drive_letter, disk_number, part_number);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	PrintMessage(MSG_LINK_OK, drive_letter, disk_number, part_number);

	return 0;
}

//
//	Remove a drive letter from a partition
//	Command Line Parameters:
//		partition number or drive letter
//
int	Unlink(char **args)
{
	ULONG	disk_number		= (ULONG)-1;
	ULONG	part_number		= (ULONG)-1;
	TCHAR	drive_letter	= '\0';
	DWORD	ret;

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	//	process command line parameters

	if (isdigit(**args)) {
		disk_number = atol(*(args++));

		if (!*args) {
			PrintMessage(MSG_USAGE_ULINK);
			return -1;
		}

		if (!isdigit(**args)) {
			PrintMessage(MSG_INVALID_PART, *args);
			return -1;
		}

		part_number = atol(*(args++));
	}
	else if (isalpha(**args)) {
		drive_letter = (char)toupper(**(args++));
	}
	else {
		PrintMessage(MSG_UNKNOWN_OPTION, *args, "ULINK");
		return -1;
	}

	if (*args) {
		PrintMessage(MSG_USAGE_ULINK);
		return -1;
	}

	if (!drive_letter) {
		// disk, part are specified
		ret = VdkGetDriveLetter(disk_number, part_number, &drive_letter);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_GET_LINK_NG, disk_number, part_number);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}

		if (!isalpha(drive_letter)) {
			PrintMessage(MSG_GET_LINK_NG, disk_number, part_number);
			printf("%s\n", VdkStatusStr(ERROR_FILE_NOT_FOUND));
			return -1;
		}
	}

	if (isalpha(drive_letter)) {
		ret = VdkDelDriveLetter(drive_letter);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_UNLINK_NG, drive_letter);
			printf("%s\n", VdkStatusStr(ret));
			return -1;
		}
	}

	PrintMessage(MSG_UNLINK_OK, drive_letter);

	return 0;
}

//
//	Print image file information
//	Command Line Parameters:
//		disk number or drive letter
//
int Image(char **args)
{
	ULONG	disk_number;
	ULONG	max_disk;
	DWORD	ret;

	//	ensure that the driver is installed

	if (driver_state == VDK_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return -1;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_RUNNING);
		return -1;
	}

	ret = VdkGetDriverInfo(NULL, &max_disk, NULL, NULL, NULL);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_CONFIG_NG);
		printf("%s\n", VdkStatusStr(ret));
		return -1;
	}

	if (*args) {
		if (isdigit(**args)) {
			disk_number = atol(*args);
		}
		else if (isalpha(**args)) {
			ret = GetDiskFromDrive(**args, &disk_number);

			if (ret != ERROR_SUCCESS) {
				PrintMessage(MSG_RESOLVE_LINK_NG, **args);
				return -1;
			}
		}
		else {
			PrintMessage(MSG_INVALID_DISK, *args);
			return -1;
		}

		if (disk_number >= max_disk) {
			PrintMessage(MSG_INVALID_DISK, *args);
			return -1;
		}

		max_disk = disk_number;
	}
	else {
		disk_number = 0;
	}

	do {
		HANDLE hDisk;
		PVDK_OPEN_FILE_INFO	file_info;

		PrintMessage(MSG_VIRTUAL_DISK, disk_number);

		hDisk = VdkOpenDevice(disk_number, 0);

		if (hDisk == INVALID_HANDLE_VALUE) {
			ret = VdkLastError();
			PrintMessage(MSG_GET_IMAGE_NG, disk_number);
			printf("%s\n", VdkStatusStr(ret));
			continue;
		}

		ret = VdkCheckDeviceState(hDisk, 0, 0);

		if (ret != ERROR_SUCCESS) {
			if (ret == ERROR_BUSY) {
				PrintMessage(MSG_DEVICE_BUSY);
				printf("\n");
			}
			else if (ret == ERROR_NOT_READY) {
				PrintMessage(MSG_IMAGE_NONE);
				printf("\n");
			}
			else {
				printf("%s\n\n", VdkStatusStr(ret));
			}

			CloseHandle(hDisk);
			continue;
		}

		ret = VdkGetFileInfo(hDisk, 0, &file_info);

		if (ret != ERROR_SUCCESS) {
			CloseHandle(hDisk);
			PrintMessage(MSG_GET_IMAGE_NG, disk_number);
			printf("%s\n", VdkStatusStr(ret));
			continue;
		}

		//	print image file information

		PrintFileInfo(file_info);

		if (!file_info) {
			CloseHandle(hDisk);
			continue;
		}

		if (!file_info->DiskType) {
			VdkFreeMem(file_info);
			CloseHandle(hDisk);
			continue;
		}

		//
		//	Print partition information
		//
		PrintMessage(MSG_PARTITION_HEADER);

		ret = VdkListPartitions(NULL, hDisk, file_info->Capacity, PartList_Callback, (PVOID)disk_number);

		VdkFreeMem(file_info);
		CloseHandle(hDisk);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_GET_PART_NG);
			printf("%s\n", VdkStatusStr(ret));
			continue;
		}

		printf("\n");
	}
	while (++disk_number < max_disk);

	return 0;
}

//
//	Print usage help
//	Command Line Parameters:
//		(optional) command
//
int	Help(char **args)
{
	DWORD msg = MSG_HELP_GENERAL;

	if (args && *args) {
		int idx = 0;

		while (Commands[idx].cmd) {
			if (_stricmp(*args, Commands[idx].cmd) == 0) {
				msg = Commands[idx].helpmsg;
				break;
			}
			idx++;
		}

		if (!Commands[idx].cmd) {
			PrintMessage(MSG_UNKNOWN_COMMAND, *args);
			msg = MSG_HELP_HELP;
		}
	}

	PrintMessage(msg);

	return 0;
}

//
//	Print device information
//
int	Device(char **args)
{
	ULONG device_num;
	PVDK_DEVICE_INFO device_info;

	UNREFERENCED_PARAMETER(args);

	if (VdkGetDeviceList(&device_num, &device_info) != ERROR_SUCCESS) {
		return -1;
	}

	while (device_num--) {
		printf("Device Type  : %s\n", (device_info[device_num].DeviceType == VDK_DEVICE_DISK) ? "DISK" : "PART");
		printf("Device Name  : %s\n", device_info[device_num].DeviceName);
		printf("Symbolic Link: %s\n", device_info[device_num].SymbolicLink);
		printf("Reference    : %lu\n", device_info[device_num].ReferenceCount);
		printf("\n");
	}

	VdkFreeMem(device_info);
	return 0;
}

#ifdef _DEBUG

struct _traceflags {
	char *name;
	ULONG value;
}
flagtable[] = {
	"WARN",		VDKWARN,
	"INFO",		VDKINFO,
	"OPEN",		VDKOPEN,
	"CLOSE",	VDKCLOSE,
	"READ",		VDKREAD,
	"WRITE",	VDKWRITE,
	"DISPATCH",	VDKDISPATCH,
	"IOCTL",	VDKIOCTL,
	"UPDATE",	VDKUPDATE,
	"CREATE",	VDKCREATE,
	"DELETE",	VDKDELETE,
	"FORMAT",	VDKFORMAT,
	"SERVER",	VDKSERVER,
	"CLIENT",	VDKCLIENT,
	NULL, 0
};

ULONG ParseFlag(PCHAR name)
{
	int idx;

	if (!_stricmp(name, "ALL")) {
		return 0xffffffff;
	}

	idx = 0;

	while (flagtable[idx].name) {
		if (!_stricmp(name, flagtable[idx].name)) {
			return flagtable[idx].value;
		}
		idx++;
	}

	return 0;
}

int	Trace(char **args)
{
	ULONG	flags, changed;
	int idx;

	flags = VdkTraceFlags(NULL);
	changed = flags;

	while (*args) {
		if (**args == '-') {
			changed &= ~(ParseFlag(*args + 1));
		}
		else if (**args == '+') {
			changed |= ParseFlag(*args + 1);
		}
		else {
			changed |= ParseFlag(*args);
		}
		args++;
	}

	if (changed != flags) {
		flags = VdkTraceFlags(&changed);
	}

	printf("Trace flag is : 0x%08x\n", flags);

	printf("Enabled : ");

	idx = 0;

	while (flagtable[idx].name) {
		if ((flagtable[idx].value & flags) == flagtable[idx].value) {
			printf("%s ", flagtable[idx].name);
		}
		idx++;
	}

	printf("\nDisabled: ");

	idx = 0;

	while (flagtable[idx].name) {
		if ((flagtable[idx].value & flags) != flagtable[idx].value) {
			printf("%s ", flagtable[idx].name);
		}
		idx++;
	}
	printf("\n");
	return 0;
}
#endif _DEBUG

//
//	Get disk number from a drive letter
//
DWORD GetDiskFromDrive(CHAR drive, PULONG disk)
{
	TCHAR	dos_device[] = " :";
	TCHAR	device_name[MAX_DEVNAME_LEN], dos_name[MAX_PATH];
	DWORD	ret;

	dos_device[0] = drive;

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

	ret = VdkGetDriverInfo(NULL, disk, NULL, NULL, NULL);

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

	while ((*disk)--) {
		ret = VdkGetDeviceName((*disk), (ULONG)-1, device_name);

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

		if (!_strnicmp(dos_name, device_name, strlen(device_name))) {
			return ERROR_SUCCESS;
		}
	}

	return ERROR_FILE_NOT_FOUND;
}

//
//	Print image file information
//
void PrintFileInfo(PVDK_OPEN_FILE_INFO file_info)
{
	ULONG total_sectors;
	PCHAR name_pos;
	ULONG idx;

	if (!file_info) {
		PrintMessage(MSG_IMAGE_NONE);
		printf("\n");
		return;
	}

	//	Virtual disk access type

	if (file_info->DiskType) {
		PrintMessage(MSG_ACCESS_TYPE);

		switch (file_info->DiskType) {
		case VDK_DISKTYPE_WRITABLE:
			PrintMessage(MSG_ACCESS_RW);
			break;

		case VDK_DISKTYPE_READONLY:
			PrintMessage(MSG_ACCESS_RO);
			break;

		case VDK_DISKTYPE_WRITEBLOCK:
			PrintMessage(MSG_ACCESS_WB);
			break;
		}
	}

	PrintMessage(MSG_DISK_CAPACITY,
		file_info->Capacity,
		file_info->Capacity / 2048);

	if (file_info->Cylinders &&
		file_info->Tracks &&
		file_info->Sectors) {

		PrintMessage(MSG_DISK_GEOMETRY,
			file_info->Cylinders,
			file_info->Tracks,
			file_info->Sectors);
	}

	PrintMessage(MSG_DISK_FILES,
		file_info->FilesTotal);

	PrintMessage(MSG_FILE_HEADER);

	total_sectors = 0;
	name_pos = (PCHAR)&(file_info->Files[file_info->FilesTotal]);

	for (idx = 0; idx < file_info->FilesTotal; idx++) {

		if (total_sectors == 0) {
			printf(" -------  -------  ----\n");
		}
		switch (file_info->Files[idx].FileType) {
		case VDK_FILETYPE_NONE:
			printf("  NONE ");
			break;

		case VDK_FILETYPE_FLAT:
			printf("  FLAT ");
			break;

		case VDK_FILETYPE_COWD:
			printf("  COWD ");
			break;

		case VDK_FILETYPE_VMDK:
			printf("  VMDK ");
			break;

		default:
			printf("  ???  ");
			break;
		}

		printf("  %8lu  %s\n",
			file_info->Files[idx].Capacity,
			name_pos);

		name_pos += file_info->Files[idx].NameLength;

		total_sectors += file_info->Files[idx].Capacity;

		if (total_sectors == file_info->Capacity) {
			total_sectors = 0;
		}
	}
	printf("\n");
}

//
//	Confirms unmount retry
//
DWORD Retry_Callback(PVOID param, DWORD err)
{
	printf("\n");
	PrintMessage(MSG_DISMOUNT_NG);
	printf("%s\n", VdkStatusStr(err));

	if (param) {
		// quiet / force mode
		return FALSE;
	}
	else {
		// normal mode
		PrintMessage(MSG_DRIVE_IN_USE);
		return (InputChar(MSG_PROMPT_RETRY, "yn") == 'y');
	}
}

//
//	Confirms unmount continue
//
DWORD Continue_Callback(PVOID param, DWORD err)
{
	UNREFERENCED_PARAMETER(err);

	if ((ULONG)param == CLOSE_QUIET) {
		// quiet mode
		return FALSE;
	}
	else if ((ULONG)param == CLOSE_FORCE) {
		// force mode
		PrintMessage(MSG_CLOSE_FORCED);
		return TRUE;
	}
	else {
		// prompt mode
		printf("\n");
		PrintMessage(MSG_FORCING_WARN);

		return (InputChar(MSG_PROMPT_CONTINUE, "yn") == 'y');
	}
}

//
//	VdkListPartitions callback
//	called for each partition item
//
void PartList_Callback(PPARTITION_ITEM pitem, PVOID param)
{
	CHAR drive[] = " :";

	if ((ULONG)param != (ULONG)-1) {
		VdkGetDriveLetter(
			(ULONG)param,
			pitem->idx,
			&drive[0]);
	}

	if (!isalpha(drive[0])) {
		strcpy(drive, "  ");
	}

	if (pitem->idx) {
		PCHAR name = GetPartitionTypeName(pitem->type);

		printf(" %s  %2d%c  %12lu   %8lu (%6lu MB)  %02xh:%s\n",
			drive,
			pitem->idx,
			pitem->num > 4 ? '*' : ' ',
			pitem->offset,
			pitem->length,
			pitem->length / 2048,
			pitem->type,
			name ? name : "");
	}
	else {
		printf(" %s  %2d%c  %12lu   %8lu (%6lu MB)  %s\n",
			drive,
			pitem->idx,
			pitem->num > 4 ? '*' : ' ',
			pitem->offset,
			pitem->length,
			pitem->length / 2048,
			pitem->fsname);
	}
}

//
//	VdkGetDriveLayout callback
//	called for each recognized partition item
//
void Assign_Callback(PPARTITION_ITEM pitem, PVOID param)
{
	PASSIGN_PARAM assign = (PASSIGN_PARAM)param;
	VDKSTAT ret;

	if ((assign->part_number == (ULONG)-1 &&
		IsPartitionMountable(pitem->type, assign->read_only)) ||
		assign->part_number == pitem->idx) {

		CHAR letter = ChooseDriveLetter();

		if (!isalpha(letter)) {
			PrintMessage(MSG_DRIVE_FULL);
			goto print_info;
		}

		if (assign->drive_letters &&
			isalpha(*(assign->drive_letters)) &&
			*(assign->drive_letters) > letter) {

			letter = *((assign->drive_letters)++);
		}

		ret = VdkSetDriveLetter(
			assign->disk_number,
			pitem->idx,
			letter);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_LINK_NG, letter, assign->disk_number, pitem->idx);
			printf("%s\n", VdkStatusStr(ret));
		}
	}

print_info:
	//
	//	print partition information
	//
	PartList_Callback(pitem, (PVOID)(assign->disk_number));
}

//
//	Check if current user belongs to Administrators group
//
BOOL IsUserAdmin()
{
	DWORD	i, dwSize = 0;
	HANDLE	hToken;
	PTOKEN_GROUPS pGroupInfo;
	BYTE	sidBuffer[100];
	PSID	pSID = (PSID)&sidBuffer;
	SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
	BOOL	ret = FALSE;

	// Open a handle to the access token for the calling process.

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
		return FALSE;
	}

	// Call GetTokenInformation to get the buffer size.

	if(!GetTokenInformation(hToken, TokenGroups, NULL, dwSize, &dwSize)) {

		if(GetLastError() != ERROR_INSUFFICIENT_BUFFER ) {
			return FALSE;
		}
	}

	// Allocate the buffer.

	pGroupInfo = (PTOKEN_GROUPS)GlobalAlloc(GPTR, dwSize);

	// Call GetTokenInformation again to get the group information.

	if (!GetTokenInformation(hToken, TokenGroups, pGroupInfo, dwSize, &dwSize)) {
		goto cleanup;
	}

	// Create a SID for the BUILTIN\Administrators group.

	if (!AllocateAndInitializeSid(
		&SIDAuth, 2, SECURITY_BUILTIN_DOMAIN_RID,
		DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pSID)) {
		goto cleanup;
	}

	// Loop through the group SIDs looking for the administrator SID.

	for (i = 0; i < pGroupInfo->GroupCount; i++) {

		if (EqualSid(pSID, pGroupInfo->Groups[i].Sid)) {
			ret = TRUE;
			break;
		}
	}

cleanup:
	if (pSID) {
		FreeSid(pSID);
	}

	if (pGroupInfo) {
		GlobalFree(pGroupInfo);
	}

	return ret;
}