[KERNEL32]: Reimplement GetDriveTypeW(). This allows providing a quick path for DOS drives and fixes a few detection cases. It allows brings in support for mount points.

This commit is contained in:
Pierre Schweitzer 2017-10-05 23:41:26 +02:00
parent 676bd0cf37
commit cb17d5dba4
4 changed files with 189 additions and 75 deletions

View file

@ -23,7 +23,6 @@
#define NDEBUG #define NDEBUG
#include <debug.h> #include <debug.h>
DEBUG_CHANNEL(kernel32file);
#define MAX_DOS_DRIVES 26 #define MAX_DOS_DRIVES 26
@ -482,127 +481,228 @@ UINT
WINAPI WINAPI
GetDriveTypeW(IN LPCWSTR lpRootPathName) GetDriveTypeW(IN LPCWSTR lpRootPathName)
{ {
FILE_FS_DEVICE_INFORMATION FileFsDevice; BOOL RetryOpen;
OBJECT_ATTRIBUTES ObjectAttributes; PCWSTR RootPath;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING PathName;
HANDLE FileHandle;
NTSTATUS Status; NTSTATUS Status;
PWSTR CurrentDir = NULL; WCHAR DriveLetter;
PCWSTR lpRootPath; HANDLE RootHandle;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING PathName, VolumeString;
FILE_FS_DEVICE_INFORMATION FileFsDevice;
WCHAR Buffer[MAX_PATH], VolumeName[MAX_PATH];
if (!lpRootPathName) /* If no path, get one */
if (lpRootPathName == NULL)
{ {
/* If NULL is passed, use current directory path */ RootPath = Buffer;
DWORD BufferSize = GetCurrentDirectoryW(0, NULL); /* This will be current drive (<letter>:\ - drop the rest)*/
CurrentDir = HeapAlloc(GetProcessHeap(), 0, BufferSize * sizeof(WCHAR)); if (RtlGetCurrentDirectory_U(sizeof(Buffer), Buffer) > 3 * sizeof(WCHAR))
if (!CurrentDir)
return DRIVE_UNKNOWN;
if (!GetCurrentDirectoryW(BufferSize, CurrentDir))
{ {
HeapFree(GetProcessHeap(), 0, CurrentDir); Buffer[3] = UNICODE_NULL;
return DRIVE_UNKNOWN;
} }
if (wcslen(CurrentDir) > 3)
CurrentDir[3] = 0;
lpRootPath = CurrentDir;
} }
else else
{ {
size_t Length = wcslen(lpRootPathName); /* Handle broken value */
if (lpRootPathName == (PVOID)-1)
TRACE("lpRootPathName: %S\n", lpRootPathName);
lpRootPath = lpRootPathName;
if (Length == 2)
{ {
WCHAR DriveLetter = RtlUpcaseUnicodeChar(lpRootPathName[0]); return DRIVE_UNKNOWN;
}
RootPath = lpRootPathName;
/* If provided path is 2-len, it might be a drive letter... */
if (wcslen(lpRootPathName) == 2)
{
/* Check it! */
DriveLetter = RtlUpcaseUnicodeChar(lpRootPathName[0]);
/* That's a drive letter! */
if (DriveLetter >= L'A' && DriveLetter <= L'Z' && lpRootPathName[1] == L':') if (DriveLetter >= L'A' && DriveLetter <= L'Z' && lpRootPathName[1] == L':')
{ {
Length = (Length + 2) * sizeof(WCHAR); /* Make it a volume */
Buffer[0] = DriveLetter;
CurrentDir = HeapAlloc(GetProcessHeap(), 0, Length); Buffer[1] = L':';
if (!CurrentDir) Buffer[2] = L'\\';
return DRIVE_UNKNOWN; Buffer[3] = UNICODE_NULL;
RootPath = Buffer;
StringCbPrintfW(CurrentDir, Length, L"%s\\", lpRootPathName);
lpRootPath = CurrentDir;
} }
} }
} }
TRACE("lpRootPath: %S\n", lpRootPath); /* If the provided looks like a DOS device... Like <letter>:\<0> */
DriveLetter = RtlUpcaseUnicodeChar(RootPath[0]);
if (!RtlDosPathNameToNtPathName_U(lpRootPath, &PathName, NULL, NULL)) /* We'll take the quick path!
* We'll find the device type looking at the device map (and types ;-))
* associated with the current process
*/
if (DriveLetter >= L'A' && DriveLetter <= L'Z' && RootPath[1] == L':' &&
RootPath[2] == L'\\' && RootPath[3] == UNICODE_NULL)
{ {
if (CurrentDir != NULL) USHORT Index;
HeapFree(GetProcessHeap(), 0, CurrentDir); PROCESS_DEVICEMAP_INFORMATION DeviceMap;
return DRIVE_NO_ROOT_DIR; /* Query the device map */
} Status = NtQueryInformationProcess(NtCurrentProcess(), ProcessDeviceMap,
&DeviceMap,
TRACE("PathName: %S\n", PathName.Buffer); sizeof(PROCESS_DEVICEMAP_INFORMATION),
if (CurrentDir != NULL)
HeapFree(GetProcessHeap(), 0, CurrentDir);
if (PathName.Buffer[(PathName.Length >> 1) - 1] != L'\\')
{
return DRIVE_NO_ROOT_DIR;
}
InitializeObjectAttributes(&ObjectAttributes,
&PathName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL); NULL);
/* Zero output if we failed */
if (!NT_SUCCESS(Status))
{
RtlZeroMemory(&DeviceMap, sizeof(PROCESS_DEVICEMAP_INFORMATION));
}
Status = NtOpenFile(&FileHandle, /* Get our index in the device map */
FILE_READ_ATTRIBUTES | SYNCHRONIZE, Index = DriveLetter - L'A';
&ObjectAttributes, /* Check we're in the device map (bit set) */
&IoStatusBlock, if (((1 << Index) & DeviceMap.Query.DriveMap) != 0)
{
/* Validate device type and return it */
if (DeviceMap.Query.DriveType[Index] >= DRIVE_REMOVABLE &&
DeviceMap.Query.DriveType[Index] <= DRIVE_RAMDISK)
{
return DeviceMap.Query.DriveType[Index];
}
/* Otherwise, return we don't know the type */
else
{
return DRIVE_UNKNOWN;
}
}
/* We couldn't find ourselves, do it the slow way */
}
/* No path provided, use root */
if (lpRootPathName == NULL)
{
RootPath = L"\\";
}
/* Convert to NT path */
if (!RtlDosPathNameToNtPathName_U(RootPath, &PathName, NULL, NULL))
{
return DRIVE_NO_ROOT_DIR;
}
/* If not a directory, fail, we need a volume */
if (PathName.Buffer[(PathName.Length / sizeof(WCHAR)) - 1] != L'\\')
{
RtlFreeHeap(RtlGetProcessHeap(), 0, PathName.Buffer);
return DRIVE_NO_ROOT_DIR;
}
/* Let's probe for it, by forcing open failure! */
RetryOpen = TRUE;
InitializeObjectAttributes(&ObjectAttributes, &PathName,
OBJ_CASE_INSENSITIVE, NULL, NULL);
Status = NtOpenFile(&RootHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&ObjectAttributes, &IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
/* It properly failed! */
if (Status == STATUS_FILE_IS_A_DIRECTORY)
{
/* It might be a mount point, then, query for target */
if (BasepGetVolumeNameFromReparsePoint(lpRootPathName, VolumeName, MAX_PATH, NULL))
{
/* We'll reopen the target */
RtlInitUnicodeString(&VolumeString, VolumeName);
VolumeName[1] = L'?';
VolumeString.Length -= sizeof(WCHAR);
InitializeObjectAttributes(&ObjectAttributes, &VolumeString,
OBJ_CASE_INSENSITIVE, NULL, NULL);
}
}
else
{
/* heh. It worked? Or failed for whatever other reason?
* Check we have a directory if we get farther in path
*/
PathName.Length += sizeof(WCHAR);
if (IsThisARootDirectory(0, &PathName))
{
/* Yes? Heh, then it's fine, keep our current handle */
RetryOpen = FALSE;
}
else
{
/* Then, retry to open without forcing non directory type */
PathName.Length -= sizeof(WCHAR);
if (NT_SUCCESS(Status))
{
NtClose(RootHandle);
}
}
}
/* Now, we retry without forcing file type - should work now */
if (RetryOpen)
{
Status = NtOpenFile(&RootHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&ObjectAttributes, &IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_NONALERT); FILE_SYNCHRONOUS_IO_NONALERT);
}
/* We don't need path any longer */
RtlFreeHeap(RtlGetProcessHeap(), 0, PathName.Buffer); RtlFreeHeap(RtlGetProcessHeap(), 0, PathName.Buffer);
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
return DRIVE_NO_ROOT_DIR; /* According to WINE regression tests */ {
return DRIVE_NO_ROOT_DIR;
}
Status = NtQueryVolumeInformationFile(FileHandle, /* Query the device for its type */
Status = NtQueryVolumeInformationFile(RootHandle,
&IoStatusBlock, &IoStatusBlock,
&FileFsDevice, &FileFsDevice,
sizeof(FILE_FS_DEVICE_INFORMATION), sizeof(FILE_FS_DEVICE_INFORMATION),
FileFsDeviceInformation); FileFsDeviceInformation);
NtClose(FileHandle); /* No longer required */
NtClose(RootHandle);
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
{ {
return 0; return DRIVE_UNKNOWN;
} }
/* Do we have a remote device? Return so! */
if ((FileFsDevice.Characteristics & FILE_REMOTE_DEVICE) == FILE_REMOTE_DEVICE)
{
return DRIVE_REMOTE;
}
/* Check the device type */
switch (FileFsDevice.DeviceType) switch (FileFsDevice.DeviceType)
{ {
/* CDROM, easy */
case FILE_DEVICE_CD_ROM: case FILE_DEVICE_CD_ROM:
case FILE_DEVICE_CD_ROM_FILE_SYSTEM: case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
return DRIVE_CDROM; return DRIVE_CDROM;
case FILE_DEVICE_VIRTUAL_DISK:
return DRIVE_RAMDISK; /* Disk... */
case FILE_DEVICE_NETWORK_FILE_SYSTEM:
return DRIVE_REMOTE;
case FILE_DEVICE_DISK: case FILE_DEVICE_DISK:
case FILE_DEVICE_DISK_FILE_SYSTEM: case FILE_DEVICE_DISK_FILE_SYSTEM:
if (FileFsDevice.Characteristics & FILE_REMOTE_DEVICE) /* Removable media? Floppy is one */
return DRIVE_REMOTE; if ((FileFsDevice.Characteristics & FILE_REMOVABLE_MEDIA) == FILE_REMOVABLE_MEDIA ||
if (FileFsDevice.Characteristics & FILE_REMOVABLE_MEDIA) (FileFsDevice.Characteristics & FILE_FLOPPY_DISKETTE) == FILE_FLOPPY_DISKETTE)
{
return DRIVE_REMOVABLE; return DRIVE_REMOVABLE;
}
else
{
return DRIVE_FIXED; return DRIVE_FIXED;
} }
ERR("Returning DRIVE_UNKNOWN for device type %lu\n", FileFsDevice.DeviceType); /* Easy cases */
case FILE_DEVICE_NETWORK:
case FILE_DEVICE_NETWORK_FILE_SYSTEM:
return DRIVE_REMOTE;
case FILE_DEVICE_VIRTUAL_DISK:
return DRIVE_RAMDISK;
}
/* Nothing matching, just fail */
return DRIVE_UNKNOWN; return DRIVE_UNKNOWN;
} }

View file

@ -253,7 +253,7 @@ GetVolumeNameForRoot(IN LPCWSTR lpszRootPath,
/* /*
* @implemented * @implemented
*/ */
static BOOL BOOL
BasepGetVolumeNameFromReparsePoint(IN LPCWSTR lpszMountPoint, BasepGetVolumeNameFromReparsePoint(IN LPCWSTR lpszMountPoint,
OUT LPWSTR lpszVolumeName, OUT LPWSTR lpszVolumeName,
IN DWORD cchBufferLength, IN DWORD cchBufferLength,

View file

@ -150,7 +150,7 @@ CleanAndQuit:
/* /*
* @implemented * @implemented
*/ */
static BOOL BOOL
IsThisARootDirectory(IN HANDLE VolumeHandle, IsThisARootDirectory(IN HANDLE VolumeHandle,
IN PUNICODE_STRING NtPathName) IN PUNICODE_STRING NtPathName)
{ {

View file

@ -448,6 +448,20 @@ BasepGetVolumeNameForVolumeMountPoint(
OUT LPBOOL IsAMountPoint OUT LPBOOL IsAMountPoint
); );
BOOL
BasepGetVolumeNameFromReparsePoint(
IN LPCWSTR lpszMountPoint,
OUT LPWSTR lpszVolumeName,
IN DWORD cchBufferLength,
OUT LPBOOL IsAMountPoint
);
BOOL
IsThisARootDirectory(
IN HANDLE VolumeHandle,
IN PUNICODE_STRING NtPathName
);
/* FIXME: This is EXPORTED! It should go in an external kernel32.h header */ /* FIXME: This is EXPORTED! It should go in an external kernel32.h header */
VOID VOID
WINAPI WINAPI