xref: /reactos/dll/win32/kernel32/client/file/disk.c (revision ba3f0743)
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 #include <strsafe.h>
23 
24 #define NDEBUG
25 #include <debug.h>
26 
27 #define MAX_DOS_DRIVES 26
28 
29 /*
30  * @implemented
31  */
32 /* Synced to Wine-2008/12/28 */
33 DWORD
34 WINAPI
35 GetLogicalDriveStringsA(IN DWORD nBufferLength,
36                         IN LPSTR lpBuffer)
37 {
38     DWORD drive, count;
39     DWORD dwDriveMap;
40     LPSTR p;
41 
42     dwDriveMap = GetLogicalDrives();
43 
44     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
45     {
46         if (dwDriveMap & (1<<drive))
47             count++;
48     }
49 
50 
51     if ((count * 4) + 1 > nBufferLength) return ((count * 4) + 1);
52 
53     p = lpBuffer;
54 
55     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
56         if (dwDriveMap & (1<<drive))
57         {
58             *p++ = 'A' + (UCHAR)drive;
59             *p++ = ':';
60             *p++ = '\\';
61             *p++ = '\0';
62         }
63     *p = '\0';
64 
65     return (count * 4);
66 }
67 
68 /*
69  * @implemented
70  */
71 /* Synced to Wine-2008/12/28 */
72 DWORD
73 WINAPI
74 GetLogicalDriveStringsW(IN DWORD nBufferLength,
75                         IN LPWSTR lpBuffer)
76 {
77     DWORD drive, count;
78     DWORD dwDriveMap;
79     LPWSTR p;
80 
81     dwDriveMap = GetLogicalDrives();
82 
83     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
84     {
85         if (dwDriveMap & (1<<drive))
86             count++;
87     }
88 
89     if ((count * 4) + 1 > nBufferLength) return ((count * 4) + 1);
90 
91     p = lpBuffer;
92     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
93         if (dwDriveMap & (1<<drive))
94         {
95             *p++ = (WCHAR)('A' + drive);
96             *p++ = (WCHAR)':';
97             *p++ = (WCHAR)'\\';
98             *p++ = (WCHAR)'\0';
99         }
100     *p = (WCHAR)'\0';
101 
102     return (count * 4);
103 }
104 
105 /*
106  * @implemented
107  */
108 /* Synced to Wine-? */
109 DWORD
110 WINAPI
111 GetLogicalDrives(VOID)
112 {
113     NTSTATUS Status;
114     PROCESS_DEVICEMAP_INFORMATION ProcessDeviceMapInfo;
115 
116     /* Get the Device Map for this Process */
117     Status = NtQueryInformationProcess(NtCurrentProcess(),
118                                        ProcessDeviceMap,
119                                        &ProcessDeviceMapInfo,
120                                        sizeof(ProcessDeviceMapInfo),
121                                        NULL);
122 
123     /* Return the Drive Map */
124     if (!NT_SUCCESS(Status))
125     {
126         BaseSetLastNTError(Status);
127         return 0;
128     }
129 
130     if (ProcessDeviceMapInfo.Query.DriveMap == 0)
131     {
132         SetLastError(ERROR_SUCCESS);
133     }
134 
135     return ProcessDeviceMapInfo.Query.DriveMap;
136 }
137 
138 /*
139  * @implemented
140  */
141 BOOL
142 WINAPI
143 GetDiskFreeSpaceA(IN LPCSTR lpRootPathName,
144                   OUT LPDWORD lpSectorsPerCluster,
145                   OUT LPDWORD lpBytesPerSector,
146                   OUT LPDWORD lpNumberOfFreeClusters,
147                   OUT LPDWORD lpTotalNumberOfClusters)
148 {
149     PCSTR RootPath;
150     PUNICODE_STRING RootPathU;
151 
152     RootPath = lpRootPathName;
153     if (RootPath == NULL)
154     {
155         RootPath = "\\";
156     }
157 
158     RootPathU = Basep8BitStringToStaticUnicodeString(RootPath);
159     if (RootPathU == NULL)
160     {
161         return FALSE;
162     }
163 
164     return GetDiskFreeSpaceW(RootPathU->Buffer, lpSectorsPerCluster,
165                              lpBytesPerSector, lpNumberOfFreeClusters,
166                              lpTotalNumberOfClusters);
167 }
168 
169 /*
170  * @implemented
171  */
172 BOOL
173 WINAPI
174 GetDiskFreeSpaceW(IN LPCWSTR lpRootPathName,
175                   OUT LPDWORD lpSectorsPerCluster,
176                   OUT LPDWORD lpBytesPerSector,
177                   OUT LPDWORD lpNumberOfFreeClusters,
178                   OUT LPDWORD lpTotalNumberOfClusters)
179 {
180     BOOL Below2GB;
181     PCWSTR RootPath;
182     NTSTATUS Status;
183     HANDLE RootHandle;
184     UNICODE_STRING FileName;
185     IO_STATUS_BLOCK IoStatusBlock;
186     OBJECT_ATTRIBUTES ObjectAttributes;
187     FILE_FS_SIZE_INFORMATION FileFsSize;
188 
189     /* If no path provided, get root path */
190     RootPath = lpRootPathName;
191     if (lpRootPathName == NULL)
192     {
193         RootPath = L"\\";
194     }
195 
196     /* Convert the path to NT path */
197     if (!RtlDosPathNameToNtPathName_U(RootPath, &FileName, NULL, NULL))
198     {
199         SetLastError(ERROR_PATH_NOT_FOUND);
200         return FALSE;
201     }
202 
203     /* Open it for disk space query! */
204     InitializeObjectAttributes(&ObjectAttributes, &FileName,
205                                OBJ_CASE_INSENSITIVE, NULL, NULL);
206     Status = NtOpenFile(&RootHandle, SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock,
207                         FILE_SHARE_READ | FILE_SHARE_WRITE,
208                         FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_FREE_SPACE_QUERY);
209     if (!NT_SUCCESS(Status))
210     {
211         BaseSetLastNTError(Status);
212         RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
213         if (lpBytesPerSector != NULL)
214         {
215             *lpBytesPerSector = 0;
216         }
217 
218         return FALSE;
219     }
220 
221     /* We don't need the name any longer */
222     RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
223 
224     /* Query disk space! */
225     Status = NtQueryVolumeInformationFile(RootHandle, &IoStatusBlock, &FileFsSize,
226                                           sizeof(FILE_FS_SIZE_INFORMATION),
227                                           FileFsSizeInformation);
228     NtClose(RootHandle);
229     if (!NT_SUCCESS(Status))
230     {
231         BaseSetLastNTError(Status);
232         return FALSE;
233     }
234 
235     /* Are we in some compatibility mode where size must be below 2GB? */
236     Below2GB = ((NtCurrentPeb()->AppCompatFlags.LowPart & GetDiskFreeSpace2GB) == GetDiskFreeSpace2GB);
237 
238     /* If we're to overflow output, make sure we return the maximum */
239     if (FileFsSize.TotalAllocationUnits.HighPart != 0)
240     {
241         FileFsSize.TotalAllocationUnits.LowPart = -1;
242     }
243 
244     if (FileFsSize.AvailableAllocationUnits.HighPart != 0)
245     {
246         FileFsSize.AvailableAllocationUnits.LowPart = -1;
247     }
248 
249     /* Return what user asked for */
250     if (lpSectorsPerCluster != NULL)
251     {
252         *lpSectorsPerCluster = FileFsSize.SectorsPerAllocationUnit;
253     }
254 
255     if (lpBytesPerSector != NULL)
256     {
257         *lpBytesPerSector = FileFsSize.BytesPerSector;
258     }
259 
260     if (lpNumberOfFreeClusters != NULL)
261     {
262         if (!Below2GB)
263         {
264             *lpNumberOfFreeClusters = FileFsSize.AvailableAllocationUnits.LowPart;
265         }
266         /* If we have to remain below 2GB... */
267         else
268         {
269             DWORD FreeClusters;
270 
271             /* Compute how many clusters there are in less than 2GB: 2 * 1024 * 1024 * 1024- 1 */
272             FreeClusters = 0x7FFFFFFF / (FileFsSize.SectorsPerAllocationUnit * FileFsSize.BytesPerSector);
273             /* If that's higher than what was queried, then return the queried value, it's OK! */
274             if (FreeClusters > FileFsSize.AvailableAllocationUnits.LowPart)
275             {
276                 FreeClusters = FileFsSize.AvailableAllocationUnits.LowPart;
277             }
278 
279             *lpNumberOfFreeClusters = FreeClusters;
280         }
281     }
282 
283     if (lpTotalNumberOfClusters != NULL)
284     {
285         if (!Below2GB)
286         {
287             *lpTotalNumberOfClusters = FileFsSize.TotalAllocationUnits.LowPart;
288         }
289         /* If we have to remain below 2GB... */
290         else
291         {
292             DWORD TotalClusters;
293 
294             /* Compute how many clusters there are in less than 2GB: 2 * 1024 * 1024 * 1024- 1 */
295             TotalClusters = 0x7FFFFFFF / (FileFsSize.SectorsPerAllocationUnit * FileFsSize.BytesPerSector);
296             /* If that's higher than what was queried, then return the queried value, it's OK! */
297             if (TotalClusters > FileFsSize.TotalAllocationUnits.LowPart)
298             {
299                 TotalClusters = FileFsSize.TotalAllocationUnits.LowPart;
300             }
301 
302             *lpTotalNumberOfClusters = TotalClusters;
303         }
304     }
305 
306     return TRUE;
307 }
308 
309 /*
310  * @implemented
311  */
312 BOOL
313 WINAPI
314 GetDiskFreeSpaceExA(IN LPCSTR lpDirectoryName OPTIONAL,
315                     OUT PULARGE_INTEGER lpFreeBytesAvailableToCaller,
316                     OUT PULARGE_INTEGER lpTotalNumberOfBytes,
317                     OUT PULARGE_INTEGER lpTotalNumberOfFreeBytes)
318 {
319     PCSTR RootPath;
320     PUNICODE_STRING RootPathU;
321 
322     RootPath = lpDirectoryName;
323     if (RootPath == NULL)
324     {
325         RootPath = "\\";
326     }
327 
328     RootPathU = Basep8BitStringToStaticUnicodeString(RootPath);
329     if (RootPathU == NULL)
330     {
331         return FALSE;
332     }
333 
334     return GetDiskFreeSpaceExW(RootPathU->Buffer, lpFreeBytesAvailableToCaller,
335                               lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes);
336 }
337 
338 /*
339  * @implemented
340  */
341 BOOL
342 WINAPI
343 GetDiskFreeSpaceExW(IN LPCWSTR lpDirectoryName OPTIONAL,
344                     OUT PULARGE_INTEGER lpFreeBytesAvailableToCaller,
345                     OUT PULARGE_INTEGER lpTotalNumberOfBytes,
346                     OUT PULARGE_INTEGER lpTotalNumberOfFreeBytes)
347 {
348     PCWSTR RootPath;
349     NTSTATUS Status;
350     HANDLE RootHandle;
351     UNICODE_STRING FileName;
352     DWORD BytesPerAllocationUnit;
353     IO_STATUS_BLOCK IoStatusBlock;
354     OBJECT_ATTRIBUTES ObjectAttributes;
355     FILE_FS_SIZE_INFORMATION FileFsSize;
356 
357     /* If no path provided, get root path */
358     RootPath = lpDirectoryName;
359     if (lpDirectoryName == NULL)
360     {
361         RootPath = L"\\";
362     }
363 
364     /* Convert the path to NT path */
365     if (!RtlDosPathNameToNtPathName_U(RootPath, &FileName, NULL, NULL))
366     {
367         SetLastError(ERROR_PATH_NOT_FOUND);
368         return FALSE;
369     }
370 
371     /* Open it for disk space query! */
372     InitializeObjectAttributes(&ObjectAttributes, &FileName,
373                                OBJ_CASE_INSENSITIVE, NULL, NULL);
374     Status = NtOpenFile(&RootHandle, SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock,
375                         FILE_SHARE_READ | FILE_SHARE_WRITE,
376                         FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_FREE_SPACE_QUERY);
377     if (!NT_SUCCESS(Status))
378     {
379         BaseSetLastNTError(Status);
380         /* If error conversion lead to file not found, override to use path not found
381          * which is more accurate
382          */
383         if (GetLastError() == ERROR_FILE_NOT_FOUND)
384         {
385             SetLastError(ERROR_PATH_NOT_FOUND);
386         }
387 
388         RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
389 
390         return FALSE;
391     }
392 
393     RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
394 
395     /* If user asks for lpTotalNumberOfFreeBytes, try to use full size information */
396     if (lpTotalNumberOfFreeBytes != NULL)
397     {
398         FILE_FS_FULL_SIZE_INFORMATION FileFsFullSize;
399 
400         /* Issue the full fs size request */
401         Status = NtQueryVolumeInformationFile(RootHandle, &IoStatusBlock, &FileFsFullSize,
402                                               sizeof(FILE_FS_FULL_SIZE_INFORMATION),
403                                               FileFsFullSizeInformation);
404         /* If it succeed, complete out buffers */
405         if (NT_SUCCESS(Status))
406         {
407             /* We can close here, we'll return */
408             NtClose(RootHandle);
409 
410             /* Compute the size of an AU */
411             BytesPerAllocationUnit = FileFsFullSize.SectorsPerAllocationUnit * FileFsFullSize.BytesPerSector;
412 
413             /* And then return what was asked */
414             if (lpFreeBytesAvailableToCaller != NULL)
415             {
416                 lpFreeBytesAvailableToCaller->QuadPart = FileFsFullSize.CallerAvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
417             }
418 
419             if (lpTotalNumberOfBytes != NULL)
420             {
421                 lpTotalNumberOfBytes->QuadPart = FileFsFullSize.TotalAllocationUnits.QuadPart * BytesPerAllocationUnit;
422             }
423 
424             /* No need to check for nullness ;-) */
425             lpTotalNumberOfFreeBytes->QuadPart = FileFsFullSize.ActualAvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
426 
427             return TRUE;
428         }
429     }
430 
431     /* Otherwise, fallback to normal size information */
432     Status = NtQueryVolumeInformationFile(RootHandle, &IoStatusBlock,
433                                           &FileFsSize, sizeof(FILE_FS_SIZE_INFORMATION),
434                                           FileFsSizeInformation);
435     NtClose(RootHandle);
436     if (!NT_SUCCESS(Status))
437     {
438         BaseSetLastNTError(Status);
439         return FALSE;
440     }
441 
442     /* Compute the size of an AU */
443     BytesPerAllocationUnit = FileFsSize.SectorsPerAllocationUnit * FileFsSize.BytesPerSector;
444 
445     /* And then return what was asked, available is free, the same! */
446     if (lpFreeBytesAvailableToCaller != NULL)
447     {
448         lpFreeBytesAvailableToCaller->QuadPart = FileFsSize.AvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
449     }
450 
451     if (lpTotalNumberOfBytes != NULL)
452     {
453         lpTotalNumberOfBytes->QuadPart = FileFsSize.TotalAllocationUnits.QuadPart * BytesPerAllocationUnit;
454     }
455 
456     if (lpTotalNumberOfFreeBytes != NULL)
457     {
458         lpTotalNumberOfFreeBytes->QuadPart = FileFsSize.AvailableAllocationUnits.QuadPart * BytesPerAllocationUnit;
459     }
460 
461     return TRUE;
462 }
463 
464 /*
465  * @implemented
466  */
467 UINT
468 WINAPI
469 GetDriveTypeA(IN LPCSTR lpRootPathName)
470 {
471     PWSTR RootPathU;
472 
473     if (lpRootPathName != NULL)
474     {
475         PUNICODE_STRING RootPathUStr;
476 
477         RootPathUStr = Basep8BitStringToStaticUnicodeString(lpRootPathName);
478         if (RootPathUStr == NULL)
479         {
480             return DRIVE_NO_ROOT_DIR;
481         }
482 
483         RootPathU = RootPathUStr->Buffer;
484     }
485     else
486     {
487         RootPathU = NULL;
488     }
489 
490     return GetDriveTypeW(RootPathU);
491 }
492 
493 /*
494  * @implemented
495  */
496 UINT
497 WINAPI
498 GetDriveTypeW(IN LPCWSTR lpRootPathName)
499 {
500     BOOL RetryOpen;
501     PCWSTR RootPath;
502     NTSTATUS Status;
503     WCHAR DriveLetter;
504     HANDLE RootHandle;
505     IO_STATUS_BLOCK IoStatusBlock;
506     OBJECT_ATTRIBUTES ObjectAttributes;
507     UNICODE_STRING PathName, VolumeString;
508     FILE_FS_DEVICE_INFORMATION FileFsDevice;
509     WCHAR Buffer[MAX_PATH], VolumeName[MAX_PATH];
510 
511     /* If no path, get one */
512     if (lpRootPathName == NULL)
513     {
514         RootPath = Buffer;
515         /* This will be current drive (<letter>:\ - drop the rest)*/
516         if (RtlGetCurrentDirectory_U(sizeof(Buffer), Buffer) > 3 * sizeof(WCHAR))
517         {
518             Buffer[3] = UNICODE_NULL;
519         }
520     }
521     else
522     {
523         /* Handle broken value */
524         if (lpRootPathName == (PVOID)-1)
525         {
526             return DRIVE_UNKNOWN;
527         }
528 
529         RootPath = lpRootPathName;
530         /* If provided path is 2-len, it might be a drive letter... */
531         if (wcslen(lpRootPathName) == 2)
532         {
533             /* Check it! */
534             DriveLetter = RtlUpcaseUnicodeChar(lpRootPathName[0]);
535             /* That's a drive letter! */
536             if (DriveLetter >= L'A' && DriveLetter <= L'Z' && lpRootPathName[1] == L':')
537             {
538                 /* Make it a volume */
539                 Buffer[0] = DriveLetter;
540                 Buffer[1] = L':';
541                 Buffer[2] = L'\\';
542                 Buffer[3] = UNICODE_NULL;
543                 RootPath = Buffer;
544             }
545         }
546     }
547 
548     /* If the provided looks like a DOS device... Like <letter>:\<0> */
549     DriveLetter = RtlUpcaseUnicodeChar(RootPath[0]);
550     /* We'll take the quick path!
551      * We'll find the device type looking at the device map (and types ;-))
552      * associated with the current process
553      */
554     if (DriveLetter >= L'A' && DriveLetter <= L'Z' && RootPath[1] == L':' &&
555         RootPath[2] == L'\\' && RootPath[3] == UNICODE_NULL)
556     {
557         USHORT Index;
558         PROCESS_DEVICEMAP_INFORMATION DeviceMap;
559 
560         /* Query the device map */
561         Status = NtQueryInformationProcess(NtCurrentProcess(), ProcessDeviceMap,
562                                            &DeviceMap,
563                                            sizeof(PROCESS_DEVICEMAP_INFORMATION),
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