xref: /reactos/dll/win32/kernel32/client/file/disk.c (revision 45fd48bd)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/file/disk.c
5  * PURPOSE:         Disk and Drive functions
6  * PROGRAMMER:      Ariadne ( ariadne@xs4all.nl)
7  *                  Erik Bos, Alexandre Julliard :
8  *                      GetLogicalDriveStringsA,
9  *                      GetLogicalDriveStringsW, GetLogicalDrives
10  * UPDATE HISTORY:
11  *                  Created 01/11/98
12  */
13 //WINE copyright notice:
14 /*
15  * DOS drives handling functions
16  *
17  * Copyright 1993 Erik Bos
18  * Copyright 1996 Alexandre Julliard
19  */
20 
21 #include <k32.h>
22 
23 #define NDEBUG
24 #include <debug.h>
25 
26 #define MAX_DOS_DRIVES 26
27 
28 /*
29  * @implemented
30  */
31 /* Synced to Wine-2008/12/28 */
32 DWORD
33 WINAPI
34 GetLogicalDriveStringsA(IN DWORD nBufferLength,
35                         IN LPSTR lpBuffer)
36 {
37     DWORD drive, count;
38     DWORD dwDriveMap;
39     LPSTR p;
40 
41     dwDriveMap = GetLogicalDrives();
42 
43     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
44     {
45         if (dwDriveMap & (1<<drive))
46             count++;
47     }
48 
49 
50     if ((count * 4) + 1 > nBufferLength) return ((count * 4) + 1);
51 
52     p = lpBuffer;
53 
54     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
55         if (dwDriveMap & (1<<drive))
56         {
57             *p++ = 'A' + (UCHAR)drive;
58             *p++ = ':';
59             *p++ = '\\';
60             *p++ = '\0';
61         }
62     *p = '\0';
63 
64     return (count * 4);
65 }
66 
67 /*
68  * @implemented
69  */
70 /* Synced to Wine-2008/12/28 */
71 DWORD
72 WINAPI
73 GetLogicalDriveStringsW(IN DWORD nBufferLength,
74                         IN LPWSTR lpBuffer)
75 {
76     DWORD drive, count;
77     DWORD dwDriveMap;
78     LPWSTR p;
79 
80     dwDriveMap = GetLogicalDrives();
81 
82     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
83     {
84         if (dwDriveMap & (1<<drive))
85             count++;
86     }
87 
88     if ((count * 4) + 1 > nBufferLength) return ((count * 4) + 1);
89 
90     p = lpBuffer;
91     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
92         if (dwDriveMap & (1<<drive))
93         {
94             *p++ = (WCHAR)('A' + drive);
95             *p++ = (WCHAR)':';
96             *p++ = (WCHAR)'\\';
97             *p++ = (WCHAR)'\0';
98         }
99     *p = (WCHAR)'\0';
100 
101     return (count * 4);
102 }
103 
104 /*
105  * @implemented
106  */
107 /* Synced to Wine-? */
108 DWORD
109 WINAPI
110 GetLogicalDrives(VOID)
111 {
112     NTSTATUS Status;
113     PROCESS_DEVICEMAP_INFORMATION ProcessDeviceMapInfo;
114 
115     /* Get the Device Map for this Process */
116     Status = NtQueryInformationProcess(NtCurrentProcess(),
117                                        ProcessDeviceMap,
118                                        &ProcessDeviceMapInfo.Query,
119                                        sizeof(ProcessDeviceMapInfo.Query),
120                                        NULL);
121 
122     /* Return the Drive Map */
123     if (!NT_SUCCESS(Status))
124     {
125         BaseSetLastNTError(Status);
126         return 0;
127     }
128 
129     if (ProcessDeviceMapInfo.Query.DriveMap == 0)
130     {
131         SetLastError(ERROR_SUCCESS);
132     }
133 
134     return ProcessDeviceMapInfo.Query.DriveMap;
135 }
136 
137 /*
138  * @implemented
139  */
140 BOOL
141 WINAPI
142 GetDiskFreeSpaceA(IN LPCSTR lpRootPathName,
143                   OUT LPDWORD lpSectorsPerCluster,
144                   OUT LPDWORD lpBytesPerSector,
145                   OUT LPDWORD lpNumberOfFreeClusters,
146                   OUT LPDWORD lpTotalNumberOfClusters)
147 {
148     PCSTR RootPath;
149     PUNICODE_STRING RootPathU;
150 
151     RootPath = lpRootPathName;
152     if (RootPath == NULL)
153     {
154         RootPath = "\\";
155     }
156 
157     RootPathU = Basep8BitStringToStaticUnicodeString(RootPath);
158     if (RootPathU == NULL)
159     {
160         return FALSE;
161     }
162 
163     return GetDiskFreeSpaceW(RootPathU->Buffer, lpSectorsPerCluster,
164                              lpBytesPerSector, lpNumberOfFreeClusters,
165                              lpTotalNumberOfClusters);
166 }
167 
168 /*
169  * @implemented
170  */
171 BOOL
172 WINAPI
173 GetDiskFreeSpaceW(IN LPCWSTR lpRootPathName,
174                   OUT LPDWORD lpSectorsPerCluster,
175                   OUT LPDWORD lpBytesPerSector,
176                   OUT LPDWORD lpNumberOfFreeClusters,
177                   OUT LPDWORD lpTotalNumberOfClusters)
178 {
179     BOOL Below2GB;
180     PCWSTR RootPath;
181     NTSTATUS Status;
182     HANDLE RootHandle;
183     UNICODE_STRING FileName;
184     IO_STATUS_BLOCK IoStatusBlock;
185     OBJECT_ATTRIBUTES ObjectAttributes;
186     FILE_FS_SIZE_INFORMATION FileFsSize;
187 
188     /* If no path provided, get root path */
189     RootPath = lpRootPathName;
190     if (lpRootPathName == NULL)
191     {
192         RootPath = L"\\";
193     }
194 
195     /* Convert the path to NT path */
196     if (!RtlDosPathNameToNtPathName_U(RootPath, &FileName, NULL, NULL))
197     {
198         SetLastError(ERROR_PATH_NOT_FOUND);
199         return FALSE;
200     }
201 
202     /* Open it for disk space query! */
203     InitializeObjectAttributes(&ObjectAttributes, &FileName,
204                                OBJ_CASE_INSENSITIVE, NULL, NULL);
205     Status = NtOpenFile(&RootHandle, SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock,
206                         FILE_SHARE_READ | FILE_SHARE_WRITE,
207                         FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_FREE_SPACE_QUERY);
208     if (!NT_SUCCESS(Status))
209     {
210         BaseSetLastNTError(Status);
211         RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
212         if (lpBytesPerSector != NULL)
213         {
214             *lpBytesPerSector = 0;
215         }
216 
217         return FALSE;
218     }
219 
220     /* We don't need the name any longer */
221     RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
222 
223     /* Query disk space! */
224     Status = NtQueryVolumeInformationFile(RootHandle, &IoStatusBlock, &FileFsSize,
225                                           sizeof(FILE_FS_SIZE_INFORMATION),
226                                           FileFsSizeInformation);
227     NtClose(RootHandle);
228     if (!NT_SUCCESS(Status))
229     {
230         BaseSetLastNTError(Status);
231         return FALSE;
232     }
233 
234     /* Are we in some compatibility mode where size must be below 2GB? */
235     Below2GB = ((NtCurrentPeb()->AppCompatFlags.LowPart & GetDiskFreeSpace2GB) == GetDiskFreeSpace2GB);
236 
237     /* If we're to overflow output, make sure we return the maximum */
238     if (FileFsSize.TotalAllocationUnits.HighPart != 0)
239     {
240         FileFsSize.TotalAllocationUnits.LowPart = -1;
241     }
242 
243     if (FileFsSize.AvailableAllocationUnits.HighPart != 0)
244     {
245         FileFsSize.AvailableAllocationUnits.LowPart = -1;
246     }
247 
248     /* Return what user asked for */
249     if (lpSectorsPerCluster != NULL)
250     {
251         *lpSectorsPerCluster = FileFsSize.SectorsPerAllocationUnit;
252     }
253 
254     if (lpBytesPerSector != NULL)
255     {
256         *lpBytesPerSector = FileFsSize.BytesPerSector;
257     }
258 
259     if (lpNumberOfFreeClusters != NULL)
260     {
261         if (!Below2GB)
262         {
263             *lpNumberOfFreeClusters = FileFsSize.AvailableAllocationUnits.LowPart;
264         }
265         /* If we have to remain below 2GB... */
266         else
267         {
268             DWORD FreeClusters;
269 
270             /* Compute how many clusters there are in less than 2GB: 2 * 1024 * 1024 * 1024- 1 */
271             FreeClusters = 0x7FFFFFFF / (FileFsSize.SectorsPerAllocationUnit * FileFsSize.BytesPerSector);
272             /* If that's higher than what was queried, then return the queried value, it's OK! */
273             if (FreeClusters > FileFsSize.AvailableAllocationUnits.LowPart)
274             {
275                 FreeClusters = FileFsSize.AvailableAllocationUnits.LowPart;
276             }
277 
278             *lpNumberOfFreeClusters = FreeClusters;
279         }
280     }
281 
282     if (lpTotalNumberOfClusters != NULL)
283     {
284         if (!Below2GB)
285         {
286             *lpTotalNumberOfClusters = FileFsSize.TotalAllocationUnits.LowPart;
287         }
288         /* If we have to remain below 2GB... */
289         else
290         {
291             DWORD TotalClusters;
292 
293             /* Compute how many clusters there are in less than 2GB: 2 * 1024 * 1024 * 1024- 1 */
294             TotalClusters = 0x7FFFFFFF / (FileFsSize.SectorsPerAllocationUnit * FileFsSize.BytesPerSector);
295             /* If that's higher than what was queried, then return the queried value, it's OK! */
296             if (TotalClusters > FileFsSize.TotalAllocationUnits.LowPart)
297             {
298                 TotalClusters = FileFsSize.TotalAllocationUnits.LowPart;
299             }
300 
301             *lpTotalNumberOfClusters = TotalClusters;
302         }
303     }
304 
305     return TRUE;
306 }
307 
308 /*
309  * @implemented
310  */
311 BOOL
312 WINAPI
313 GetDiskFreeSpaceExA(IN LPCSTR lpDirectoryName OPTIONAL,
314                     OUT PULARGE_INTEGER lpFreeBytesAvailableToCaller,
315                     OUT PULARGE_INTEGER lpTotalNumberOfBytes,
316                     OUT PULARGE_INTEGER lpTotalNumberOfFreeBytes)
317 {
318     PCSTR RootPath;
319     PUNICODE_STRING RootPathU;
320 
321     RootPath = lpDirectoryName;
322     if (RootPath == NULL)
323     {
324         RootPath = "\\";
325     }
326 
327     RootPathU = Basep8BitStringToStaticUnicodeString(RootPath);
328     if (RootPathU == NULL)
329     {
330         return FALSE;
331     }
332 
333     return GetDiskFreeSpaceExW(RootPathU->Buffer, lpFreeBytesAvailableToCaller,
334                               lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes);
335 }
336 
337 /*
338  * @implemented
339  */
340 BOOL
341 WINAPI
342 GetDiskFreeSpaceExW(IN LPCWSTR lpDirectoryName OPTIONAL,
343                     OUT PULARGE_INTEGER lpFreeBytesAvailableToCaller,
344                     OUT PULARGE_INTEGER lpTotalNumberOfBytes,
345                     OUT PULARGE_INTEGER lpTotalNumberOfFreeBytes)
346 {
347     PCWSTR RootPath;
348     NTSTATUS Status;
349     HANDLE RootHandle;
350     UNICODE_STRING FileName;
351     DWORD BytesPerAllocationUnit;
352     IO_STATUS_BLOCK IoStatusBlock;
353     OBJECT_ATTRIBUTES ObjectAttributes;
354     FILE_FS_SIZE_INFORMATION FileFsSize;
355 
356     /* If no path provided, get root path */
357     RootPath = lpDirectoryName;
358     if (lpDirectoryName == NULL)
359     {
360         RootPath = L"\\";
361     }
362 
363     /* Convert the path to NT path */
364     if (!RtlDosPathNameToNtPathName_U(RootPath, &FileName, NULL, NULL))
365     {
366         SetLastError(ERROR_PATH_NOT_FOUND);
367         return FALSE;
368     }
369 
370     /* Open it for disk space query! */
371     InitializeObjectAttributes(&ObjectAttributes, &FileName,
372                                OBJ_CASE_INSENSITIVE, NULL, NULL);
373     Status = NtOpenFile(&RootHandle, SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock,
374                         FILE_SHARE_READ | FILE_SHARE_WRITE,
375                         FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_FREE_SPACE_QUERY);
376     if (!NT_SUCCESS(Status))
377     {
378         BaseSetLastNTError(Status);
379         /* If error conversion lead to file not found, override to use path not found
380          * which is more accurate
381          */
382         if (GetLastError() == ERROR_FILE_NOT_FOUND)
383         {
384             SetLastError(ERROR_PATH_NOT_FOUND);
385         }
386 
387         RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
388 
389         return FALSE;
390     }
391 
392     RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
393 
394     /* If user asks for lpTotalNumberOfFreeBytes, try to use full size information */
395     if (lpTotalNumberOfFreeBytes != NULL)
396     {
397         FILE_FS_FULL_SIZE_INFORMATION FileFsFullSize;
398 
399         /* Issue the full fs size request */
400         Status = NtQueryVolumeInformationFile(RootHandle, &IoStatusBlock, &FileFsFullSize,
401                                               sizeof(FILE_FS_FULL_SIZE_INFORMATION),
402                                               FileFsFullSizeInformation);
403         /* If it succeed, complete out buffers */
404         if (NT_SUCCESS(Status))
405         {
406             /* We can close here, we'll return */
407             NtClose(RootHandle);
408 
409             /* Compute the size of an AU */
410             BytesPerAllocationUnit = FileFsFullSize.SectorsPerAllocationUnit * FileFsFullSize.BytesPerSector;
411 
412             /* And then return what was asked */
413             if (lpFreeBytesAvailableToCaller != NULL)
414             {
415                 lpFreeBytesAvailableToCaller->QuadPart = FileFsFullSize.CallerAvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
416             }
417 
418             if (lpTotalNumberOfBytes != NULL)
419             {
420                 lpTotalNumberOfBytes->QuadPart = FileFsFullSize.TotalAllocationUnits.QuadPart * BytesPerAllocationUnit;
421             }
422 
423             /* No need to check for nullness ;-) */
424             lpTotalNumberOfFreeBytes->QuadPart = FileFsFullSize.ActualAvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
425 
426             return TRUE;
427         }
428     }
429 
430     /* Otherwise, fallback to normal size information */
431     Status = NtQueryVolumeInformationFile(RootHandle, &IoStatusBlock,
432                                           &FileFsSize, sizeof(FILE_FS_SIZE_INFORMATION),
433                                           FileFsSizeInformation);
434     NtClose(RootHandle);
435     if (!NT_SUCCESS(Status))
436     {
437         BaseSetLastNTError(Status);
438         return FALSE;
439     }
440 
441     /* Compute the size of an AU */
442     BytesPerAllocationUnit = FileFsSize.SectorsPerAllocationUnit * FileFsSize.BytesPerSector;
443 
444     /* And then return what was asked, available is free, the same! */
445     if (lpFreeBytesAvailableToCaller != NULL)
446     {
447         lpFreeBytesAvailableToCaller->QuadPart = FileFsSize.AvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
448     }
449 
450     if (lpTotalNumberOfBytes != NULL)
451     {
452         lpTotalNumberOfBytes->QuadPart = FileFsSize.TotalAllocationUnits.QuadPart * BytesPerAllocationUnit;
453     }
454 
455     if (lpTotalNumberOfFreeBytes != NULL)
456     {
457         lpTotalNumberOfFreeBytes->QuadPart = FileFsSize.AvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
458     }
459 
460     return TRUE;
461 }
462 
463 /*
464  * @implemented
465  */
466 UINT
467 WINAPI
468 GetDriveTypeA(IN LPCSTR lpRootPathName)
469 {
470     PWSTR RootPathU;
471 
472     if (lpRootPathName != NULL)
473     {
474         PUNICODE_STRING RootPathUStr;
475 
476         RootPathUStr = Basep8BitStringToStaticUnicodeString(lpRootPathName);
477         if (RootPathUStr == NULL)
478         {
479             return DRIVE_NO_ROOT_DIR;
480         }
481 
482         RootPathU = RootPathUStr->Buffer;
483     }
484     else
485     {
486         RootPathU = NULL;
487     }
488 
489     return GetDriveTypeW(RootPathU);
490 }
491 
492 /*
493  * @implemented
494  */
495 UINT
496 WINAPI
497 GetDriveTypeW(IN LPCWSTR lpRootPathName)
498 {
499     BOOL RetryOpen;
500     PCWSTR RootPath;
501     NTSTATUS Status;
502     WCHAR DriveLetter;
503     HANDLE RootHandle;
504     IO_STATUS_BLOCK IoStatusBlock;
505     OBJECT_ATTRIBUTES ObjectAttributes;
506     UNICODE_STRING PathName, VolumeString;
507     FILE_FS_DEVICE_INFORMATION FileFsDevice;
508     WCHAR Buffer[MAX_PATH], VolumeName[MAX_PATH];
509 
510     /* If no path, get one */
511     if (lpRootPathName == NULL)
512     {
513         RootPath = Buffer;
514         /* This will be current drive (<letter>:\ - drop the rest)*/
515         if (RtlGetCurrentDirectory_U(sizeof(Buffer), Buffer) > 3 * sizeof(WCHAR))
516         {
517             Buffer[3] = UNICODE_NULL;
518         }
519     }
520     else
521     {
522         /* Handle broken value */
523         if (lpRootPathName == (PVOID)-1)
524         {
525             return DRIVE_UNKNOWN;
526         }
527 
528         RootPath = lpRootPathName;
529         /* If provided path is 2-len, it might be a drive letter... */
530         if (wcslen(lpRootPathName) == 2)
531         {
532             /* Check it! */
533             DriveLetter = RtlUpcaseUnicodeChar(lpRootPathName[0]);
534             /* That's a drive letter! */
535             if (DriveLetter >= L'A' && DriveLetter <= L'Z' && lpRootPathName[1] == L':')
536             {
537                 /* Make it a volume */
538                 Buffer[0] = DriveLetter;
539                 Buffer[1] = L':';
540                 Buffer[2] = L'\\';
541                 Buffer[3] = UNICODE_NULL;
542                 RootPath = Buffer;
543             }
544         }
545     }
546 
547     /* If the provided looks like a DOS device... Like <letter>:\<0> */
548     DriveLetter = RtlUpcaseUnicodeChar(RootPath[0]);
549     /* We'll take the quick path!
550      * We'll find the device type looking at the device map (and types ;-))
551      * associated with the current process
552      */
553     if (DriveLetter >= L'A' && DriveLetter <= L'Z' && RootPath[1] == L':' &&
554         RootPath[2] == L'\\' && RootPath[3] == UNICODE_NULL)
555     {
556         USHORT Index;
557         PROCESS_DEVICEMAP_INFORMATION DeviceMap;
558 
559         /* Query the device map */
560         Status = NtQueryInformationProcess(NtCurrentProcess(),
561                                            ProcessDeviceMap,
562                                            &DeviceMap.Query,
563                                            sizeof(DeviceMap.Query),
564                                            NULL);
565         /* Zero output if we failed */
566         if (!NT_SUCCESS(Status))
567         {
568             RtlZeroMemory(&DeviceMap, sizeof(PROCESS_DEVICEMAP_INFORMATION));
569         }
570 
571         /* Get our index in the device map */
572         Index = DriveLetter - L'A';
573         /* Check we're in the device map (bit set) */
574         if (((1 << Index) & DeviceMap.Query.DriveMap) != 0)
575         {
576             /* Validate device type and return it */
577             if (DeviceMap.Query.DriveType[Index] >= DRIVE_REMOVABLE &&
578                 DeviceMap.Query.DriveType[Index] <= DRIVE_RAMDISK)
579             {
580                 return DeviceMap.Query.DriveType[Index];
581             }
582             /* Otherwise, return we don't know the type */
583             else
584             {
585                 return DRIVE_UNKNOWN;
586             }
587         }
588 
589         /* We couldn't find ourselves, do it the slow way */
590     }
591 
592     /* No path provided, use root */
593     if (lpRootPathName == NULL)
594     {
595         RootPath = L"\\";
596     }
597 
598     /* Convert to NT path */
599     if (!RtlDosPathNameToNtPathName_U(RootPath, &PathName, NULL, NULL))
600     {
601         return DRIVE_NO_ROOT_DIR;
602     }
603 
604     /* If not a directory, fail, we need a volume */
605     if (PathName.Buffer[(PathName.Length / sizeof(WCHAR)) - 1] != L'\\')
606     {
607         RtlFreeHeap(RtlGetProcessHeap(), 0, PathName.Buffer);
608         return DRIVE_NO_ROOT_DIR;
609     }
610 
611     /* Let's probe for it, by forcing open failure! */
612     RetryOpen = TRUE;
613     InitializeObjectAttributes(&ObjectAttributes, &PathName,
614                                OBJ_CASE_INSENSITIVE, NULL, NULL);
615     Status = NtOpenFile(&RootHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
616                         &ObjectAttributes, &IoStatusBlock,
617                         FILE_SHARE_READ | FILE_SHARE_WRITE,
618                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
619     /* It properly failed! */
620     if (Status == STATUS_FILE_IS_A_DIRECTORY)
621     {
622         /* It might be a mount point, then, query for target */
623         if (BasepGetVolumeNameFromReparsePoint(lpRootPathName, VolumeName, MAX_PATH, NULL))
624         {
625             /* We'll reopen the target */
626             RtlInitUnicodeString(&VolumeString, VolumeName);
627             VolumeName[1] = L'?';
628             VolumeString.Length -= sizeof(WCHAR);
629             InitializeObjectAttributes(&ObjectAttributes, &VolumeString,
630                                        OBJ_CASE_INSENSITIVE, NULL, NULL);
631         }
632     }
633     else
634     {
635         /* heh. It worked? Or failed for whatever other reason?
636          * Check we have a directory if we get farther in path
637          */
638         PathName.Length += sizeof(WCHAR);
639         if (IsThisARootDirectory(0, &PathName))
640         {
641             /* Yes? Heh, then it's fine, keep our current handle */
642             RetryOpen = FALSE;
643         }
644         else
645         {
646             /* Then, retry to open without forcing non directory type */
647             PathName.Length -= sizeof(WCHAR);
648             if (NT_SUCCESS(Status))
649             {
650                 NtClose(RootHandle);
651             }
652         }
653     }
654 
655     /* Now, we retry without forcing file type - should work now */
656     if (RetryOpen)
657     {
658         Status = NtOpenFile(&RootHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
659                             &ObjectAttributes, &IoStatusBlock,
660                             FILE_SHARE_READ | FILE_SHARE_WRITE,
661                             FILE_SYNCHRONOUS_IO_NONALERT);
662     }
663 
664     /* We don't need path any longer */
665     RtlFreeHeap(RtlGetProcessHeap(), 0, PathName.Buffer);
666     if (!NT_SUCCESS(Status))
667     {
668         return DRIVE_NO_ROOT_DIR;
669     }
670 
671     /* Query the device for its type */
672     Status = NtQueryVolumeInformationFile(RootHandle,
673                                           &IoStatusBlock,
674                                           &FileFsDevice,
675                                           sizeof(FILE_FS_DEVICE_INFORMATION),
676                                           FileFsDeviceInformation);
677     /* No longer required */
678     NtClose(RootHandle);
679     if (!NT_SUCCESS(Status))
680     {
681         return DRIVE_UNKNOWN;
682     }
683 
684     /* Do we have a remote device? Return so! */
685     if ((FileFsDevice.Characteristics & FILE_REMOTE_DEVICE) == FILE_REMOTE_DEVICE)
686     {
687         return DRIVE_REMOTE;
688     }
689 
690     /* Check the device type */
691     switch (FileFsDevice.DeviceType)
692     {
693         /* CDROM, easy */
694         case FILE_DEVICE_CD_ROM:
695         case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
696             return DRIVE_CDROM;
697 
698         /* Disk... */
699         case FILE_DEVICE_DISK:
700         case FILE_DEVICE_DISK_FILE_SYSTEM:
701             /* Removable media? Floppy is one */
702             if ((FileFsDevice.Characteristics & FILE_REMOVABLE_MEDIA) == FILE_REMOVABLE_MEDIA ||
703                 (FileFsDevice.Characteristics & FILE_FLOPPY_DISKETTE) == FILE_FLOPPY_DISKETTE)
704             {
705                 return DRIVE_REMOVABLE;
706             }
707             else
708             {
709                 return DRIVE_FIXED;
710             }
711 
712         /* Easy cases */
713         case FILE_DEVICE_NETWORK:
714         case FILE_DEVICE_NETWORK_FILE_SYSTEM:
715             return DRIVE_REMOTE;
716 
717         case FILE_DEVICE_VIRTUAL_DISK:
718             return DRIVE_RAMDISK;
719     }
720 
721     /* Nothing matching, just fail */
722     return DRIVE_UNKNOWN;
723 }
724 
725 /* EOF */
726