xref: /reactos/base/setup/lib/utils/fsrec.c (revision 6c74e69d)
1 /*
2  * PROJECT:     ReactOS Setup Library
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Filesystem Recognition support functions,
5  *              using NT OS functionality.
6  * COPYRIGHT:   Copyright 2017-2020 Hermes Belusca-Maito
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include "precomp.h"
12 
13 #include "fsrec.h"
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 
19 /* FUNCTIONS ****************************************************************/
20 
21 /* NOTE: Ripped & adapted from base/system/autochk/autochk.c */
22 static inline
23 NTSTATUS
24 GetFileSystemNameWorker(
25     IN HANDLE PartitionHandle,
26     OUT PIO_STATUS_BLOCK IoStatusBlock,
27     IN OUT PWSTR FileSystemName,
28     IN SIZE_T FileSystemNameSize)
29 {
30     NTSTATUS Status;
31     UCHAR Buffer[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
32     PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer;
33 
34     /* Retrieve the FS attributes */
35     Status = NtQueryVolumeInformationFile(PartitionHandle,
36                                           IoStatusBlock,
37                                           FileFsAttribute,
38                                           sizeof(Buffer),
39                                           FileFsAttributeInformation);
40     if (!NT_SUCCESS(Status))
41     {
42         DPRINT1("NtQueryVolumeInformationFile() failed, Status 0x%08lx\n", Status);
43         return Status;
44     }
45 
46     if (FileSystemNameSize < FileFsAttribute->FileSystemNameLength + sizeof(WCHAR))
47         return STATUS_BUFFER_TOO_SMALL;
48 
49     return RtlStringCbCopyNW(FileSystemName, FileSystemNameSize,
50                              FileFsAttribute->FileSystemName,
51                              FileFsAttribute->FileSystemNameLength);
52 }
53 
54 NTSTATUS
55 GetFileSystemName_UStr(
56     IN PUNICODE_STRING PartitionPath OPTIONAL,
57     IN HANDLE PartitionHandle OPTIONAL,
58     IN OUT PWSTR FileSystemName,
59     IN SIZE_T FileSystemNameSize)
60 {
61     NTSTATUS Status;
62     OBJECT_ATTRIBUTES ObjectAttributes;
63     IO_STATUS_BLOCK IoStatusBlock;
64 
65     if (PartitionPath && PartitionHandle)
66         return STATUS_INVALID_PARAMETER;
67 
68     /* Open the partition if a path has been given;
69      * otherwise just use the provided handle. */
70     if (PartitionPath)
71     {
72         InitializeObjectAttributes(&ObjectAttributes,
73                                    PartitionPath,
74                                    OBJ_CASE_INSENSITIVE,
75                                    NULL,
76                                    NULL);
77         Status = NtOpenFile(&PartitionHandle,
78                             FILE_GENERIC_READ /* | SYNCHRONIZE */,
79                             &ObjectAttributes,
80                             &IoStatusBlock,
81                             FILE_SHARE_READ | FILE_SHARE_WRITE,
82                             0 /* FILE_SYNCHRONOUS_IO_NONALERT */);
83         if (!NT_SUCCESS(Status))
84         {
85             DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n",
86                     PartitionPath, Status);
87             return Status;
88         }
89     }
90 
91     /* Retrieve the FS attributes */
92     Status = GetFileSystemNameWorker(PartitionHandle,
93                                      &IoStatusBlock,
94                                      FileSystemName,
95                                      FileSystemNameSize);
96     if (!NT_SUCCESS(Status))
97     {
98         DPRINT1("GetFileSystemName() failed for partition '%wZ' (0x%p), Status 0x%08lx\n",
99                 PartitionPath, PartitionHandle, Status);
100     }
101 
102     if (PartitionPath)
103     {
104         /* Close the partition */
105         NtClose(PartitionHandle);
106     }
107 
108     return Status;
109 }
110 
111 NTSTATUS
112 GetFileSystemName(
113     IN PCWSTR PartitionPath OPTIONAL,
114     IN HANDLE PartitionHandle OPTIONAL,
115     IN OUT PWSTR FileSystemName,
116     IN SIZE_T FileSystemNameSize)
117 {
118     UNICODE_STRING PartitionPathU;
119 
120     if (PartitionPath && PartitionHandle)
121         return STATUS_INVALID_PARAMETER;
122 
123     if (PartitionPath)
124         RtlInitUnicodeString(&PartitionPathU, PartitionPath);
125 
126     return GetFileSystemName_UStr(PartitionPath ? &PartitionPathU : NULL,
127                                   PartitionPath ? NULL : PartitionHandle,
128                                   FileSystemName,
129                                   FileSystemNameSize);
130 }
131 
132 static inline
133 NTSTATUS
134 InferFileSystemWorker(
135     IN HANDLE PartitionHandle,
136     OUT PIO_STATUS_BLOCK IoStatusBlock,
137     IN OUT PWSTR FileSystemName,
138     IN SIZE_T FileSystemNameSize)
139 {
140     NTSTATUS Status, Status2;
141     union
142     {
143         PARTITION_INFORMATION_EX InfoEx;
144         PARTITION_INFORMATION Info;
145     } PartInfo;
146     UCHAR PartitionType;
147 
148     if (FileSystemNameSize < sizeof(WCHAR))
149         return STATUS_BUFFER_TOO_SMALL;
150 
151     *FileSystemName = L'\0';
152 
153     /* Try to infer a file system using NT file system recognition */
154     Status = GetFileSystemName_UStr(NULL, PartitionHandle,
155                                     FileSystemName,
156                                     FileSystemNameSize);
157     if (NT_SUCCESS(Status) && *FileSystemName)
158         goto Quit;
159 
160     /*
161      * Check whether the partition is MBR, and if so, retrieve its MBR
162      * partition type and try to infer a preferred file system for it.
163      */
164 
165     // NOTE: Use Status2 in order not to clobber the original Status.
166     Status2 = NtDeviceIoControlFile(PartitionHandle,
167                                     NULL,
168                                     NULL,
169                                     NULL,
170                                     IoStatusBlock,
171                                     IOCTL_DISK_GET_PARTITION_INFO_EX,
172                                     NULL,
173                                     0,
174                                     &PartInfo.InfoEx,
175                                     sizeof(PartInfo.InfoEx));
176     if (!NT_SUCCESS(Status2))
177     {
178         DPRINT1("IOCTL_DISK_GET_PARTITION_INFO_EX failed (Status %lx)\n", Status2);
179 
180         if (Status2 != STATUS_INVALID_DEVICE_REQUEST)
181             goto Quit;
182 
183         /*
184          * We could have failed because the partition is on a dynamic
185          * MBR or GPT data disk, so retry with the non-EX IOCTL.
186          */
187         Status2 = NtDeviceIoControlFile(PartitionHandle,
188                                         NULL,
189                                         NULL,
190                                         NULL,
191                                         IoStatusBlock,
192                                         IOCTL_DISK_GET_PARTITION_INFO,
193                                         NULL,
194                                         0,
195                                         &PartInfo.Info,
196                                         sizeof(PartInfo.Info));
197         if (!NT_SUCCESS(Status2))
198         {
199             /* We failed again, bail out */
200             DPRINT1("IOCTL_DISK_GET_PARTITION_INFO failed (Status %lx)\n", Status2);
201             goto Quit;
202         }
203 
204         /* The partition is supposed to be on an MBR disk; retrieve its type */
205         PartitionType = PartInfo.Info.PartitionType;
206     }
207     else
208     {
209         /* We succeeded; retrieve the partition type only if it is on an MBR disk */
210         if (PartInfo.InfoEx.PartitionStyle != PARTITION_STYLE_MBR)
211         {
212             /* Disk is not MBR, bail out */
213             goto Quit;
214         }
215         PartitionType = PartInfo.InfoEx.Mbr.PartitionType;
216     }
217 
218     /*
219      * Given an MBR partition type, try to infer a preferred file system.
220      *
221      * WARNING: This is partly a hack, since partitions with the same type
222      * can be formatted with different file systems: for example, usual Linux
223      * partitions that are formatted in EXT2/3/4, ReiserFS, etc... have the
224      * same partition type 0x83.
225      *
226      * The proper fix is to make a function that detects the existing FS
227      * from a given partition (not based on the partition type).
228      * On the contrary, for unformatted partitions with a given type, the
229      * following code is OK.
230      */
231     if ((PartitionType == PARTITION_FAT_12) ||
232         (PartitionType == PARTITION_FAT_16) ||
233         (PartitionType == PARTITION_HUGE  ) ||
234         (PartitionType == PARTITION_XINT13))
235     {
236         /* FAT12 or FAT16 */
237         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"FAT");
238     }
239     else if ((PartitionType == PARTITION_FAT32) ||
240              (PartitionType == PARTITION_FAT32_XINT13))
241     {
242         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"FAT32");
243     }
244     else if (PartitionType == PARTITION_LINUX)
245     {
246         // WARNING: See the warning above.
247         /* Could also be EXT2/3/4, ReiserFS, ... */
248         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"BTRFS");
249     }
250     else if (PartitionType == PARTITION_IFS)
251     {
252         // WARNING: See the warning above.
253         /* Could also be HPFS */
254         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"NTFS");
255     }
256 
257 Quit:
258     if (*FileSystemName && _wcsicmp(FileSystemName, L"NTFS") == 0)
259     {
260         // WARNING: We cannot write on this FS yet!
261         DPRINT1("Recognized file system '%S' that doesn't have write support yet!\n",
262                 FileSystemName);
263     }
264 
265     return Status;
266 }
267 
268 NTSTATUS
269 InferFileSystem(
270     IN PCWSTR PartitionPath OPTIONAL,
271     IN HANDLE PartitionHandle OPTIONAL,
272     IN OUT PWSTR FileSystemName,
273     IN SIZE_T FileSystemNameSize)
274 {
275     NTSTATUS Status;
276     UNICODE_STRING PartitionPathU;
277     OBJECT_ATTRIBUTES ObjectAttributes;
278     IO_STATUS_BLOCK IoStatusBlock;
279 
280     if (PartitionPath && PartitionHandle)
281         return STATUS_INVALID_PARAMETER;
282 
283     /* Open the partition if a path has been given;
284      * otherwise just use the provided handle. */
285     if (PartitionPath)
286     {
287         RtlInitUnicodeString(&PartitionPathU, PartitionPath);
288         InitializeObjectAttributes(&ObjectAttributes,
289                                    &PartitionPathU,
290                                    OBJ_CASE_INSENSITIVE,
291                                    NULL,
292                                    NULL);
293         Status = NtOpenFile(&PartitionHandle,
294                             FILE_GENERIC_READ /* | SYNCHRONIZE */,
295                             &ObjectAttributes,
296                             &IoStatusBlock,
297                             FILE_SHARE_READ | FILE_SHARE_WRITE,
298                             0 /* FILE_SYNCHRONOUS_IO_NONALERT */);
299         if (!NT_SUCCESS(Status))
300         {
301             DPRINT1("Failed to open partition '%S', Status 0x%08lx\n",
302                     PartitionPath, Status);
303             return Status;
304         }
305     }
306 
307     /* Retrieve the FS */
308     Status = InferFileSystemWorker(PartitionHandle,
309                                    &IoStatusBlock,
310                                    FileSystemName,
311                                    FileSystemNameSize);
312     if (!NT_SUCCESS(Status))
313     {
314         DPRINT1("InferFileSystem() failed for partition '%S' (0x%p), Status 0x%08lx\n",
315                 PartitionPath, PartitionHandle, Status);
316     }
317     else
318     {
319         DPRINT1("InferFileSystem(): FileSystem (guessed): %S\n",
320                 *FileSystemName ? FileSystemName : L"None");
321     }
322 
323     if (PartitionPath)
324     {
325         /* Close the partition */
326         NtClose(PartitionHandle);
327     }
328 
329     return Status;
330 }
331 
332 UCHAR
333 FileSystemToMBRPartitionType(
334     IN PCWSTR FileSystem,
335     IN ULONGLONG StartSector,
336     IN ULONGLONG SectorCount)
337 {
338     ASSERT(FileSystem);
339 
340     if (SectorCount == 0)
341         return PARTITION_ENTRY_UNUSED;
342 
343     if (_wcsicmp(FileSystem, L"FAT")   == 0 ||
344         _wcsicmp(FileSystem, L"FAT32") == 0 ||
345         _wcsicmp(FileSystem, L"RAW")   == 0)
346     {
347         if (SectorCount < 8192ULL)
348         {
349             /* FAT12 CHS partition (disk is smaller than 4.1MB) */
350             return PARTITION_FAT_12;
351         }
352         else if (StartSector < 1450560ULL)
353         {
354             /* Partition starts below the 8.4GB boundary ==> CHS partition */
355 
356             if (SectorCount < 65536ULL)
357             {
358                 /* FAT16 CHS partition (partition size < 32MB) */
359                 return PARTITION_FAT_16;
360             }
361             else if (SectorCount < 1048576ULL)
362             {
363                 /* FAT16 CHS partition (partition size < 512MB) */
364                 return PARTITION_HUGE;
365             }
366             else
367             {
368                 /* FAT32 CHS partition (partition size >= 512MB) */
369                 return PARTITION_FAT32;
370             }
371         }
372         else
373         {
374             /* Partition starts above the 8.4GB boundary ==> LBA partition */
375 
376             if (SectorCount < 1048576ULL)
377             {
378                 /* FAT16 LBA partition (partition size < 512MB) */
379                 return PARTITION_XINT13;
380             }
381             else
382             {
383                 /* FAT32 LBA partition (partition size >= 512MB) */
384                 return PARTITION_FAT32_XINT13;
385             }
386         }
387     }
388     else if (_wcsicmp(FileSystem, L"NTFS") == 0)
389     {
390         return PARTITION_IFS;
391     }
392     else if (_wcsicmp(FileSystem, L"BTRFS") == 0 ||
393              _wcsicmp(FileSystem, L"EXT2")  == 0 ||
394              _wcsicmp(FileSystem, L"EXT3")  == 0 ||
395              _wcsicmp(FileSystem, L"EXT4")  == 0)
396     {
397         return PARTITION_LINUX;
398     }
399     else
400     {
401         /* Unknown file system */
402         DPRINT1("Unknown file system '%S'\n", FileSystem);
403         return PARTITION_ENTRY_UNUSED;
404     }
405 }
406 
407 /* EOF */
408