675 lines
18 KiB
C
675 lines
18 KiB
C
/*++
|
|
|
|
Copyright (c) 2001-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
mountmu.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the XTL memory unit services.
|
|
|
|
--*/
|
|
|
|
#include "basedll.h"
|
|
#include <usbxapi.h>
|
|
#include <xboxp.h>
|
|
#include "..\..\fatx\fat.h"
|
|
|
|
#ifdef XAPILIBP
|
|
|
|
extern CRITICAL_SECTION XapiMountMUCriticalSection;
|
|
|
|
#ifdef DBG
|
|
extern ULONG g_XapiCurrentUserDevices;
|
|
#endif
|
|
|
|
#else // XAPILIBP
|
|
|
|
INITIALIZED_CRITICAL_SECTION(XapiMountMUCriticalSection);
|
|
|
|
#ifdef DBG
|
|
ULONG g_XapiCurrentUserDevices = 0;
|
|
#endif
|
|
|
|
#endif // XAPILIBP
|
|
|
|
#ifndef XAPILIBP
|
|
|
|
DWORD
|
|
WINAPI
|
|
XMountMU(
|
|
IN DWORD dwPort,
|
|
IN DWORD dwSlot,
|
|
OUT POCHAR pchDrive
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
OCHAR szTitleId[CCHMAX_HEX_DWORD];
|
|
OCHAR szDeviceName[64];
|
|
OCHAR szDosDevice[64];
|
|
OCHAR chDrive;
|
|
OBJECT_STRING DeviceName, DosDevice;
|
|
|
|
RIP_ON_NOT_TRUE_WITH_MESSAGE(XPP_XInitDevicesHasBeenCalled, "XMountMU: XInitDevices must be called first!");
|
|
|
|
if (NULL != pchDrive)
|
|
{
|
|
*pchDrive = OBJECT_NULL;
|
|
}
|
|
|
|
EnterCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
#if DBG
|
|
if ((dwPort < MU_PORT_MIN) || (dwPort > MU_PORT_MAX))
|
|
{
|
|
RIP("XMountMU() invalid parameter (dwPort)");
|
|
}
|
|
|
|
if ((dwSlot < MU_SLOT_MIN) || (dwSlot > MU_SLOT_MAX))
|
|
{
|
|
RIP("XMountMU() invalid parameter (dwSlot)");
|
|
}
|
|
|
|
if(MU_MaxUserDevices <= g_XapiCurrentUserDevices)
|
|
{
|
|
RIP("XMountMU() more devices than specified in XInitDevices");
|
|
}
|
|
#endif // DBG
|
|
|
|
//
|
|
// Make sure that the device is not already mounted.
|
|
// (if it is, we sort of fail)
|
|
chDrive = MU_DRIVE_LETTER_FROM_PORT_SLOT(dwPort, dwSlot);
|
|
if(MU_IS_MOUNTED(chDrive))
|
|
{
|
|
RIP("XMountMU() MU already mounted.");
|
|
*pchDrive = chDrive;
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
return ERROR_ALREADY_ASSIGNED;
|
|
}
|
|
|
|
//
|
|
// Setup the string buffer
|
|
//
|
|
DeviceName.Length = 0;
|
|
DeviceName.MaximumLength = sizeof(szDeviceName)/sizeof(OCHAR)-1;
|
|
DeviceName.Buffer = szDeviceName;
|
|
|
|
//
|
|
// create the device object
|
|
//
|
|
Status = MU_CreateDeviceObject(
|
|
dwPort,
|
|
dwSlot,
|
|
&DeviceName
|
|
);
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
soprintf(szDosDevice,
|
|
OTEXT("\\??\\%c:"),
|
|
chDrive);
|
|
|
|
RtlInitObjectString(&DosDevice, szDosDevice);
|
|
|
|
DwordToStringO(XeImageHeader()->Certificate->TitleID, szTitleId);
|
|
|
|
//
|
|
// Tack on a '\\' to the end. This way we are passing the MU's root directory,
|
|
// rather than the raw volume. This is important to prevent the raw file system
|
|
// from mounting by mistake on a corrupt or unformatted MU. The main evil of
|
|
// mounting the raw file system is that the error codes coming will be really
|
|
// strange.
|
|
//
|
|
ASSERT(DeviceName.Length+sizeof(OCHAR)<=DeviceName.MaximumLength);
|
|
DeviceName.Buffer[DeviceName.Length++ / sizeof(OCHAR)] = OTEXT('\\');
|
|
|
|
Status = XapiMapLetterToDirectory((PCOBJECT_STRING)&DosDevice,
|
|
(PCOBJECT_STRING)&DeviceName,
|
|
szTitleId,
|
|
TRUE,
|
|
XeImageHeader()->Certificate->TitleName,
|
|
FALSE);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (NULL != pchDrive)
|
|
{
|
|
*pchDrive = chDrive;
|
|
}
|
|
MU_SET_MOUNTED(chDrive);
|
|
#if DBG
|
|
g_XapiCurrentUserDevices++;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Dismount the volume if necessary.
|
|
IoDismountVolumeByName(&DeviceName);
|
|
|
|
// We must assume that either a handle was successfully opened and
|
|
// the filesystem was dismounted, or that it never managed to mount.
|
|
// If not, then the following step breaks all further attempts to
|
|
// mount MU's, but things were probably pretty well broken before
|
|
// this anyway.
|
|
MU_CloseDeviceObject(dwPort, dwSlot);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
return RtlNtStatusToDosError(Status);
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
XMountMURoot(
|
|
IN DWORD dwPort,
|
|
IN DWORD dwSlot,
|
|
OUT POCHAR pchDrive
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
OCHAR szDeviceName[64];
|
|
OCHAR szDosDevice[64];
|
|
OCHAR chDrive;
|
|
OBJECT_STRING DeviceName, DosDevice;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
HANDLE DirHandle;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
|
|
//
|
|
// Fill out the MU type so that XAPI can
|
|
// find it without explicitly referencing it.
|
|
// This way the MU driver is not sucked in
|
|
// by XAPI unless the MU is used.
|
|
//
|
|
|
|
if (NULL != pchDrive)
|
|
{
|
|
*pchDrive = OBJECT_NULL;
|
|
}
|
|
|
|
EnterCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
#if DBG
|
|
if ((dwPort < MU_PORT_MIN) || (dwPort > MU_PORT_MAX))
|
|
{
|
|
RIP("XMountMURoot() invalid parameter (dwPort)");
|
|
}
|
|
|
|
if ((dwSlot < MU_SLOT_MIN) || (dwSlot > MU_SLOT_MAX))
|
|
{
|
|
RIP("XMountMURoot() invalid parameter (dwSlot)");
|
|
}
|
|
if(MU_MaxUserDevices <= g_XapiCurrentUserDevices)
|
|
{
|
|
RIP("XMountMU() more devices than specified in XInitDevices");
|
|
}
|
|
#endif // DBG
|
|
|
|
//
|
|
// Make sure that the device is not already mounted.
|
|
// (if it is, we sort of fail)
|
|
chDrive = MU_DRIVE_LETTER_FROM_PORT_SLOT(dwPort, dwSlot);
|
|
if(MU_IS_MOUNTED(chDrive))
|
|
{
|
|
RIP("XMountMU() MU already mounted.");
|
|
*pchDrive = chDrive;
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
return ERROR_ALREADY_ASSIGNED;
|
|
}
|
|
|
|
//
|
|
// Setup the string buffer
|
|
//
|
|
DeviceName.Length = 0;
|
|
//lie - so we have guaranteed space for a back slash (lie leave room for NULL, and a '\\')
|
|
DeviceName.MaximumLength = sizeof(szDeviceName)/sizeof(OCHAR)-2;
|
|
DeviceName.Buffer = szDeviceName;
|
|
|
|
//
|
|
// create the device object
|
|
//
|
|
Status = MU_CreateDeviceObject(
|
|
dwPort,
|
|
dwSlot,
|
|
&DeviceName
|
|
);
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
//We previously lied about the maximum length to guarantee room for a backslash
|
|
//undo that lie.
|
|
DeviceName.MaximumLength++;
|
|
|
|
//Add a back slash to the name returned from MU_CreateDeviceObject
|
|
szDeviceName[DeviceName.Length++] = OTEXT('\\');
|
|
szDeviceName[DeviceName.Length] = OTEXT('\0');
|
|
|
|
//Attempt to open the root directory (this effectively mounts the drive).
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
(POBJECT_STRING)&DeviceName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = NtCreateFile(
|
|
&DirHandle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_OPEN_IF,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT
|
|
);
|
|
|
|
//Whack the backslash off the device name
|
|
szDeviceName[--DeviceName.Length] = OTEXT('\0');
|
|
|
|
//If we succeeded in opening the root directory we can
|
|
//create the symbolic link, etc.
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
NtClose(DirHandle); //Close the handle, we no longer need it
|
|
|
|
soprintf(szDosDevice,
|
|
OTEXT("\\??\\%c:"),
|
|
chDrive);
|
|
RtlInitObjectString(&DosDevice, szDosDevice);
|
|
|
|
Status = IoCreateSymbolicLink(&DosDevice, &DeviceName);
|
|
if(NT_ERROR(Status))
|
|
{
|
|
XDBGWRN("XAPI", "XMountMURoot() - IoCreateSymbolicLink failed with 0x%0.8x", Status);
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (NULL != pchDrive)
|
|
{
|
|
*pchDrive = chDrive;
|
|
}
|
|
MU_SET_MOUNTED(chDrive);
|
|
#if DBG
|
|
g_XapiCurrentUserDevices++;
|
|
#endif
|
|
} else
|
|
//
|
|
// Failed to open the root or to create a symbolic link. This does not mean a filesystem
|
|
// didn't mount, just in case we must force a dismount.
|
|
{
|
|
IoDismountVolumeByName(&DeviceName);
|
|
|
|
//We must assume that either a handle was successfully opened and the filesystem was dismounted, or
|
|
//that it never managed to mount. If not, then the following step breaks all further attempts to
|
|
//mount MU's, but things were probably pretty well broken before this anyway.
|
|
MU_CloseDeviceObject(dwPort, dwSlot);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
return RtlNtStatusToDosError(Status);
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
XUnmountMU(
|
|
IN DWORD dwPort,
|
|
IN DWORD dwSlot
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
OCHAR szDosDevice[64];
|
|
OBJECT_STRING DosDevice;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
HANDLE VolHandle;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OCHAR chDrive = MU_DRIVE_LETTER_FROM_PORT_SLOT(dwPort, dwSlot);
|
|
|
|
#if DBG
|
|
if ((dwPort < MU_PORT_MIN) || (dwPort > MU_PORT_MAX))
|
|
{
|
|
RIP("XUnmountMU() invalid parameter (dwPort)");
|
|
}
|
|
|
|
if ((dwSlot < MU_SLOT_MIN) || (dwSlot > MU_SLOT_MAX))
|
|
{
|
|
RIP("XUnmountMU() invalid parameter (dwSlot)");
|
|
}
|
|
#endif // DBG
|
|
|
|
EnterCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
//
|
|
// Fail if the device is not already mounted.
|
|
//
|
|
if(!MU_IS_MOUNTED(chDrive))
|
|
{
|
|
RIP("XUnmountMU() MU not mounted.");
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
return ERROR_INVALID_DRIVE;
|
|
}
|
|
|
|
//
|
|
// Unmount an alternate drive if it is mapped.
|
|
//
|
|
if (XapiMuInfo.DriveWithAltDriveMapped == chDrive)
|
|
{
|
|
//
|
|
// An alternate drive is mapped to this MU drive, so let's auto dismount it here
|
|
//
|
|
|
|
XDBGWRN("XAPI", "XUnmountMU() called on MU drive (%c:) with alternate drive mapped (%c:)",
|
|
chDrive, HD_ALT_UDATA_DRIVE);
|
|
|
|
XUnmountAlternateTitle(HD_ALT_UDATA_DRIVE);
|
|
}
|
|
|
|
//
|
|
// open the symbolic link
|
|
//
|
|
soprintf(szDosDevice, OTEXT("\\??\\%c:"), chDrive);
|
|
RtlInitObjectString(&DosDevice, szDosDevice);
|
|
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
(POBJECT_STRING)&DosDevice,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = NtCreateFile(
|
|
&VolHandle,
|
|
SYNCHRONIZE,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0,
|
|
FILE_OPEN,
|
|
FILE_SYNCHRONOUS_IO_NONALERT
|
|
);
|
|
|
|
//
|
|
// send FSCTL_DISMOUNT_VOLUME
|
|
//
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = NtFsControlFile(VolHandle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0);
|
|
NtClose(VolHandle); //Close handle whether the dismount succeeded or not.
|
|
}
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Delete the symbolic link.
|
|
//
|
|
|
|
Status = IoDeleteSymbolicLink(&DosDevice);
|
|
|
|
//
|
|
// Tell the MU driver to delete the DeviceObject
|
|
//
|
|
|
|
MU_CloseDeviceObject(dwPort, dwSlot);
|
|
|
|
//
|
|
// Mark the drive as not mounted
|
|
//
|
|
|
|
MU_CLEAR_MOUNTED(chDrive);
|
|
#if DBG
|
|
g_XapiCurrentUserDevices--;
|
|
#endif
|
|
}
|
|
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
return RtlNtStatusToDosError(Status);
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
XReadMUMetaData(
|
|
IN DWORD dwPort,
|
|
IN DWORD dwSlot,
|
|
IN LPVOID lpBuffer,
|
|
IN DWORD dwByteOffset,
|
|
IN DWORD dwNumberOfBytesToRead
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
OCHAR szDeviceName[64];
|
|
OBJECT_STRING DeviceName;
|
|
OCHAR chDrive = MU_DRIVE_LETTER_FROM_PORT_SLOT(dwPort, dwSlot);
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PFAT_VOLUME_METADATA VolumeMetadata;
|
|
LARGE_INTEGER StartingOffset;
|
|
DISK_GEOMETRY DiskGeometry;
|
|
PARTITION_INFORMATION PartitionInformation;
|
|
|
|
#if DBG
|
|
RIP_ON_NOT_TRUE_WITH_MESSAGE(XPP_XInitDevicesHasBeenCalled, "XReadMUMetaData: XInitDevices must be called first!");
|
|
|
|
if ((dwPort < MU_PORT_MIN) || (dwPort > MU_PORT_MAX))
|
|
{
|
|
RIP("XReadMUMetaData() invalid parameter (dwPort)");
|
|
}
|
|
|
|
if ((dwSlot < MU_SLOT_MIN) || (dwSlot > MU_SLOT_MAX))
|
|
{
|
|
RIP("XReadMUMetaData() invalid parameter (dwSlot)");
|
|
}
|
|
|
|
if (dwByteOffset >= PAGE_SIZE)
|
|
{
|
|
RIP("XReadMUMetaData() invalid parameter (dwByteOffset)");
|
|
}
|
|
|
|
if ((PAGE_SIZE - dwByteOffset) < dwNumberOfBytesToRead)
|
|
{
|
|
RIP("XReadMUMetaData() invalid parameter (dwNumberOfBytesToRead)");
|
|
}
|
|
#endif // DBG
|
|
|
|
EnterCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
if(!MU_IS_MOUNTED(chDrive))
|
|
{
|
|
//
|
|
// Setup the string buffer
|
|
//
|
|
DeviceName.Length = 0;
|
|
//lie - so we have guaranteed space for a back slash (lie leave room for NULL, and a '\\')
|
|
DeviceName.MaximumLength = sizeof(szDeviceName)/sizeof(OCHAR)-2;
|
|
DeviceName.Buffer = szDeviceName;
|
|
|
|
//
|
|
// create the device object
|
|
//
|
|
Status = MU_CreateDeviceObject(
|
|
dwPort,
|
|
dwSlot,
|
|
&DeviceName
|
|
);
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
DeviceObject = MU_GetExistingDeviceObject(dwPort, dwSlot),
|
|
|
|
//
|
|
// Before the memory unit can accept read requests, it must first
|
|
// initialize its drive geometry information. Send the same IOCTLs
|
|
// that the file system would send during the mount process. Neither
|
|
// of these need to synchronized with a file system that has already
|
|
// mounted, so send them directly to the device.
|
|
//
|
|
|
|
Status = IoSynchronousDeviceIoControlRequest(IOCTL_DISK_GET_DRIVE_GEOMETRY,
|
|
DeviceObject, NULL, 0, &DiskGeometry, sizeof(DISK_GEOMETRY),
|
|
NULL, FALSE);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = IoSynchronousDeviceIoControlRequest(IOCTL_DISK_GET_PARTITION_INFO,
|
|
DeviceObject, NULL, 0, &PartitionInformation,
|
|
sizeof(PARTITION_INFORMATION), NULL, FALSE);
|
|
|
|
if (PartitionInformation.PartitionLength.QuadPart < PAGE_SIZE)
|
|
{
|
|
Status = STATUS_UNRECOGNIZED_VOLUME;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Allocate a block of memory to hold the volume metadata block. All
|
|
// device objects must support at least PAGE_SIZE transfers in order to
|
|
// support the file system cache. Memory units typically have at least
|
|
// PAGE_SIZE sector sizes, so there's no point in trying to reduce this
|
|
// memory requirement.
|
|
//
|
|
|
|
VolumeMetadata = (PFAT_VOLUME_METADATA)LocalAlloc(LMEM_FIXED, PAGE_SIZE);
|
|
|
|
if (VolumeMetadata != NULL)
|
|
{
|
|
//
|
|
// Submit a read request for PAGE_SIZE bytes from the start of
|
|
// the device. Note that we don't need to synchronize this
|
|
// with the file system because all of the file systems we care
|
|
// about immediately flush any metadata writes to media.
|
|
//
|
|
|
|
StartingOffset.QuadPart = 0;
|
|
|
|
Status = IoSynchronousFsdRequest(IRP_MJ_READ, DeviceObject,
|
|
VolumeMetadata, PAGE_SIZE, &StartingOffset);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Verify that the volume is formatted as FATX. Memory units
|
|
// never use any other file system.
|
|
//
|
|
|
|
if (VolumeMetadata->Signature == FAT_VOLUME_SIGNATURE)
|
|
{
|
|
CopyMemory(lpBuffer, (LPBYTE)VolumeMetadata + dwByteOffset,
|
|
dwNumberOfBytesToRead);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_UNRECOGNIZED_VOLUME;
|
|
}
|
|
}
|
|
|
|
LocalFree(VolumeMetadata);
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if(!MU_IS_MOUNTED(chDrive))
|
|
{
|
|
//
|
|
// Tell the MU driver to delete the DeviceObject
|
|
//
|
|
|
|
MU_CloseDeviceObject(dwPort, dwSlot);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&XapiMountMUCriticalSection);
|
|
|
|
return RtlNtStatusToDosError(Status);
|
|
}
|
|
|
|
#endif // ! XAPILIBP
|
|
|
|
#ifdef XAPILIBP
|
|
|
|
DWORD
|
|
WINAPI
|
|
XMUNameFromPortSlot(
|
|
IN DWORD dwPort,
|
|
IN DWORD dwSlot,
|
|
OUT LPWSTR lpName,
|
|
IN UINT cchName
|
|
)
|
|
{
|
|
DWORD dwError;
|
|
WCHAR VolumeName[FAT_VOLUME_NAME_LENGTH];
|
|
|
|
dwError = XReadMUMetaData(dwPort, dwSlot, VolumeName,
|
|
FIELD_OFFSET(FAT_VOLUME_METADATA, VolumeName), sizeof(VolumeName));
|
|
|
|
if (dwError == ERROR_SUCCESS)
|
|
{
|
|
lstrcpynW(lpName, VolumeName, min(cchName, FAT_VOLUME_NAME_LENGTH));
|
|
}
|
|
|
|
return dwError;
|
|
}
|
|
|
|
#endif // XAPILIBP
|
|
|
|
#ifndef XAPILIBP
|
|
|
|
DWORD
|
|
WINAPI
|
|
XMUPortFromDriveLetter(
|
|
OCHAR chDrive
|
|
)
|
|
{
|
|
if ((chDrive >= MU_FIRST_DRIVE) && (chDrive <= MU_LAST_DRIVE))
|
|
{
|
|
return MU_PORT_FROM_DRIVE_LETTER(chDrive);
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
XMUSlotFromDriveLetter(
|
|
OCHAR chDrive
|
|
)
|
|
{
|
|
if ((chDrive >= MU_FIRST_DRIVE) && (chDrive <= MU_LAST_DRIVE))
|
|
{
|
|
return MU_SLOT_FROM_DRIVE_LETTER(chDrive);
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#endif // ! XAPILIBP
|