xref: /reactos/base/setup/lib/fsutil.c (revision b5218987)
1 /*
2  * PROJECT:     ReactOS Setup Library
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Filesystem support functions
5  * COPYRIGHT:   Copyright 2003-2019 Casper S. Hornstrup (chorns@users.sourceforge.net)
6  *              Copyright 2017-2019 Hermes Belusca-Maito
7  */
8 
9 //
10 // See also: https://git.reactos.org/?p=reactos.git;a=blob;f=reactos/dll/win32/fmifs/init.c;h=e895f5ef9cae4806123f6bbdd3dfed37ec1c8d33;hb=b9db9a4e377a2055f635b2fb69fef4e1750d219c
11 // for how to get FS providers in a dynamic way. In the (near) future we may
12 // consider merging some of this code with us into a fmifs / fsutil / fslib library...
13 //
14 
15 /* INCLUDES *****************************************************************/
16 
17 #include "precomp.h"
18 
19 #include "fsutil.h"
20 #include "partlist.h"
21 
22 #include <fslib/vfatlib.h>
23 #include <fslib/btrfslib.h>
24 // #include <fslib/ext2lib.h>
25 // #include <fslib/ntfslib.h>
26 
27 #define NDEBUG
28 #include <debug.h>
29 
30 
31 /* LOCALS *******************************************************************/
32 
33 /** IFS_PROVIDER **/
34 typedef struct _FILE_SYSTEM
35 {
36     PCWSTR FileSystemName;
37     FORMATEX FormatFunc;
38     CHKDSKEX ChkdskFunc;
39 } FILE_SYSTEM, *PFILE_SYSTEM;
40 
41 /* The list of file systems on which we can install ReactOS */
42 static FILE_SYSTEM RegisteredFileSystems[] =
43 {
44     /* NOTE: The FAT formatter automatically determines
45      * whether it will use FAT-16 or FAT-32. */
46     { L"FAT"  , VfatFormat, VfatChkdsk },
47 #if 0
48     { L"FAT32", VfatFormat, VfatChkdsk }, // Do we support specific FAT sub-formats specifications?
49     { L"FATX" , VfatxFormat, VfatxChkdsk },
50     { L"NTFS" , NtfsFormat, NtfsChkdsk },
51 #endif
52     { L"BTRFS", BtrfsFormatEx, BtrfsChkdskEx },
53 #if 0
54     { L"EXT2" , Ext2Format, Ext2Chkdsk },
55     { L"EXT3" , Ext2Format, Ext2Chkdsk },
56     { L"EXT4" , Ext2Format, Ext2Chkdsk },
57     { L"FFS"  , FfsFormat , FfsChkdsk  },
58     { L"REISERFS", ReiserfsFormat, ReiserfsChkdsk },
59 #endif
60 };
61 
62 
63 /* FUNCTIONS ****************************************************************/
64 
65 /** QueryAvailableFileSystemFormat() **/
66 BOOLEAN
67 GetRegisteredFileSystems(
68     IN ULONG Index,
69     OUT PCWSTR* FileSystemName)
70 {
71     if (Index >= ARRAYSIZE(RegisteredFileSystems))
72         return FALSE;
73 
74     *FileSystemName = RegisteredFileSystems[Index].FileSystemName;
75 
76     return TRUE;
77 }
78 
79 
80 /** GetProvider() **/
81 static PFILE_SYSTEM
82 GetFileSystemByName(
83     IN PCWSTR FileSystemName)
84 {
85 #if 0 // Reenable when the list of registered FSes will again be dynamic
86 
87     PLIST_ENTRY ListEntry;
88     PFILE_SYSTEM_ITEM Item;
89 
90     ListEntry = List->ListHead.Flink;
91     while (ListEntry != &List->ListHead)
92     {
93         Item = CONTAINING_RECORD(ListEntry, FILE_SYSTEM_ITEM, ListEntry);
94         if (Item->FileSystemName &&
95             (wcsicmp(FileSystemName, Item->FileSystemName) == 0 ||
96             /* Map FAT32 back to FAT */
97             (wcsicmp(FileSystemName, L"FAT32") == 0 && wcsicmp(Item->FileSystemName, L"FAT") == 0)))
98         {
99             return Item;
100         }
101 
102         ListEntry = ListEntry->Flink;
103     }
104 
105 #else
106 
107     ULONG Count = ARRAYSIZE(RegisteredFileSystems);
108     PFILE_SYSTEM FileSystems = RegisteredFileSystems;
109 
110     ASSERT(FileSystems && Count != 0);
111 
112     while (Count--)
113     {
114         if (FileSystems->FileSystemName &&
115             (wcsicmp(FileSystemName, FileSystems->FileSystemName) == 0 ||
116             /* Map FAT32 back to FAT */
117             (wcsicmp(FileSystemName, L"FAT32") == 0 && wcsicmp(FileSystems->FileSystemName, L"FAT") == 0)))
118         {
119             return FileSystems;
120         }
121 
122         ++FileSystems;
123     }
124 
125 #endif
126 
127     return NULL;
128 }
129 
130 
131 //
132 // FileSystem recognition, using NT OS functionality
133 //
134 
135 /* NOTE: Ripped & adapted from base/system/autochk/autochk.c */
136 NTSTATUS
137 GetFileSystemNameByHandle(
138     IN HANDLE PartitionHandle,
139     IN OUT PWSTR FileSystemName,
140     IN SIZE_T FileSystemNameSize)
141 {
142     NTSTATUS Status;
143     IO_STATUS_BLOCK IoStatusBlock;
144     UCHAR Buffer[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
145     PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer;
146 
147     /* Retrieve the FS attributes */
148     Status = NtQueryVolumeInformationFile(PartitionHandle,
149                                           &IoStatusBlock,
150                                           FileFsAttribute,
151                                           sizeof(Buffer),
152                                           FileFsAttributeInformation);
153     if (!NT_SUCCESS(Status))
154     {
155         DPRINT1("NtQueryVolumeInformationFile failed, Status 0x%08lx\n", Status);
156         return Status;
157     }
158 
159     if (FileSystemNameSize < FileFsAttribute->FileSystemNameLength + sizeof(WCHAR))
160         return STATUS_BUFFER_TOO_SMALL;
161 
162     return RtlStringCbCopyNW(FileSystemName, FileSystemNameSize,
163                              FileFsAttribute->FileSystemName,
164                              FileFsAttribute->FileSystemNameLength);
165 }
166 
167 NTSTATUS
168 GetFileSystemName_UStr(
169     IN PUNICODE_STRING PartitionPath,
170     IN OUT PWSTR FileSystemName,
171     IN SIZE_T FileSystemNameSize)
172 {
173     NTSTATUS Status;
174     OBJECT_ATTRIBUTES ObjectAttributes;
175     HANDLE PartitionHandle;
176     IO_STATUS_BLOCK IoStatusBlock;
177 
178     /* Open the partition */
179     InitializeObjectAttributes(&ObjectAttributes,
180                                PartitionPath,
181                                OBJ_CASE_INSENSITIVE,
182                                NULL,
183                                NULL);
184     Status = NtOpenFile(&PartitionHandle,
185                         FILE_GENERIC_READ /* | SYNCHRONIZE */,
186                         &ObjectAttributes,
187                         &IoStatusBlock,
188                         FILE_SHARE_READ | FILE_SHARE_WRITE,
189                         0 /* FILE_SYNCHRONOUS_IO_NONALERT */);
190     if (!NT_SUCCESS(Status))
191     {
192         DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", PartitionPath, Status);
193         return Status;
194     }
195 
196     /* Retrieve the FS attributes */
197     Status = GetFileSystemNameByHandle(PartitionHandle, FileSystemName, FileSystemNameSize);
198     if (!NT_SUCCESS(Status))
199     {
200         DPRINT1("GetFileSystemNameByHandle() failed for partition '%wZ', Status 0x%08lx\n",
201                 PartitionPath, Status);
202     }
203 
204     /* Close the partition */
205     NtClose(PartitionHandle);
206 
207     return Status;
208 }
209 
210 NTSTATUS
211 GetFileSystemName(
212     IN PCWSTR Partition,
213     IN OUT PWSTR FileSystemName,
214     IN SIZE_T FileSystemNameSize)
215 {
216     UNICODE_STRING PartitionPath;
217 
218     RtlInitUnicodeString(&PartitionPath, Partition);
219     return GetFileSystemName_UStr(&PartitionPath,
220                                   FileSystemName,
221                                   FileSystemNameSize);
222 }
223 
224 NTSTATUS
225 InferFileSystemByHandle(
226     IN HANDLE PartitionHandle,
227     IN UCHAR PartitionType,
228     IN OUT PWSTR FileSystemName,
229     IN SIZE_T FileSystemNameSize)
230 {
231     NTSTATUS Status;
232 
233     if (FileSystemNameSize < sizeof(WCHAR))
234         return STATUS_BUFFER_TOO_SMALL;
235 
236     *FileSystemName = L'\0';
237 
238     /* Try to infer a file system using NT file system recognition */
239     Status = GetFileSystemNameByHandle(PartitionHandle,
240                                        FileSystemName,
241                                        FileSystemNameSize);
242     if (NT_SUCCESS(Status) && *FileSystemName)
243     {
244         goto Quit;
245     }
246 
247     /*
248      * Try to infer a preferred file system for this partition, given its ID.
249      *
250      * WARNING: This is partly a hack, since partitions with the same ID can
251      * be formatted with different file systems: for example, usual Linux
252      * partitions that are formatted in EXT2/3/4, ReiserFS, etc... have the
253      * same partition ID 0x83.
254      *
255      * The proper fix is to make a function that detects the existing FS
256      * from a given partition (not based on the partition ID).
257      * On the contrary, for unformatted partitions with a given ID, the
258      * following code is OK.
259      */
260     if ((PartitionType == PARTITION_FAT_12) ||
261         (PartitionType == PARTITION_FAT_16) ||
262         (PartitionType == PARTITION_HUGE  ) ||
263         (PartitionType == PARTITION_XINT13))
264     {
265         /* FAT12 or FAT16 */
266         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"FAT");
267     }
268     else if ((PartitionType == PARTITION_FAT32) ||
269              (PartitionType == PARTITION_FAT32_XINT13))
270     {
271         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"FAT32");
272     }
273     else if (PartitionType == PARTITION_LINUX)
274     {
275         // WARNING: See the warning above.
276         /* Could also be EXT2/3/4, ReiserFS, ... */
277         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"BTRFS");
278     }
279     else if (PartitionType == PARTITION_IFS)
280     {
281         // WARNING: See the warning above.
282         /* Could also be HPFS */
283         Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"NTFS");
284     }
285 
286 Quit:
287     if (*FileSystemName)
288     {
289         // WARNING: We cannot write on this FS yet!
290         if (PartitionType == PARTITION_IFS)
291         {
292             DPRINT1("Recognized file system '%S' that doesn't have write support yet!\n",
293                     FileSystemName);
294         }
295     }
296 
297     DPRINT1("InferFileSystem -- PartitionType: 0x%02X ; FileSystem (guessed): %S\n",
298             PartitionType, *FileSystemName ? FileSystemName : L"None");
299 
300     return Status;
301 }
302 
303 NTSTATUS
304 InferFileSystem(
305     IN PCWSTR Partition,
306     IN UCHAR PartitionType,
307     IN OUT PWSTR FileSystemName,
308     IN SIZE_T FileSystemNameSize)
309 {
310     NTSTATUS Status;
311     UNICODE_STRING PartitionPath;
312     OBJECT_ATTRIBUTES ObjectAttributes;
313     HANDLE PartitionHandle;
314     IO_STATUS_BLOCK IoStatusBlock;
315 
316     /* Open the partition */
317     RtlInitUnicodeString(&PartitionPath, Partition);
318     InitializeObjectAttributes(&ObjectAttributes,
319                                &PartitionPath,
320                                OBJ_CASE_INSENSITIVE,
321                                NULL,
322                                NULL);
323     Status = NtOpenFile(&PartitionHandle,
324                         FILE_GENERIC_READ /* | SYNCHRONIZE */,
325                         &ObjectAttributes,
326                         &IoStatusBlock,
327                         FILE_SHARE_READ | FILE_SHARE_WRITE,
328                         0 /* FILE_SYNCHRONOUS_IO_NONALERT */);
329     if (!NT_SUCCESS(Status))
330     {
331         DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionPath, Status);
332         return Status;
333     }
334 
335     /* Retrieve the FS */
336     Status = InferFileSystemByHandle(PartitionHandle,
337                                      PartitionType,
338                                      FileSystemName,
339                                      FileSystemNameSize);
340 
341     /* Close the partition */
342     NtClose(PartitionHandle);
343 
344     return Status;
345 }
346 
347 /** ChkdskEx() **/
348 NTSTATUS
349 ChkdskFileSystem_UStr(
350     IN PUNICODE_STRING DriveRoot,
351     IN PCWSTR FileSystemName,
352     IN BOOLEAN FixErrors,
353     IN BOOLEAN Verbose,
354     IN BOOLEAN CheckOnlyIfDirty,
355     IN BOOLEAN ScanDrive,
356     IN PFMIFSCALLBACK Callback)
357 {
358     PFILE_SYSTEM FileSystem;
359 
360     FileSystem = GetFileSystemByName(FileSystemName);
361 
362     if (!FileSystem || !FileSystem->ChkdskFunc)
363     {
364         // BOOLEAN Argument = FALSE;
365         // Callback(DONE, 0, &Argument);
366         return STATUS_NOT_SUPPORTED;
367     }
368 
369     return FileSystem->ChkdskFunc(DriveRoot,
370                                   FixErrors,
371                                   Verbose,
372                                   CheckOnlyIfDirty,
373                                   ScanDrive,
374                                   Callback);
375 }
376 
377 NTSTATUS
378 ChkdskFileSystem(
379     IN PCWSTR DriveRoot,
380     IN PCWSTR FileSystemName,
381     IN BOOLEAN FixErrors,
382     IN BOOLEAN Verbose,
383     IN BOOLEAN CheckOnlyIfDirty,
384     IN BOOLEAN ScanDrive,
385     IN PFMIFSCALLBACK Callback)
386 {
387     UNICODE_STRING DriveRootU;
388 
389     RtlInitUnicodeString(&DriveRootU, DriveRoot);
390     return ChkdskFileSystem_UStr(&DriveRootU,
391                                  FileSystemName,
392                                  FixErrors,
393                                  Verbose,
394                                  CheckOnlyIfDirty,
395                                  ScanDrive,
396                                  Callback);
397 }
398 
399 
400 /** FormatEx() **/
401 NTSTATUS
402 FormatFileSystem_UStr(
403     IN PUNICODE_STRING DriveRoot,
404     IN PCWSTR FileSystemName,
405     IN FMIFS_MEDIA_FLAG MediaFlag,
406     IN PUNICODE_STRING Label,
407     IN BOOLEAN QuickFormat,
408     IN ULONG ClusterSize,
409     IN PFMIFSCALLBACK Callback)
410 {
411     PFILE_SYSTEM FileSystem;
412 
413     FileSystem = GetFileSystemByName(FileSystemName);
414 
415     if (!FileSystem || !FileSystem->FormatFunc)
416     {
417         // BOOLEAN Argument = FALSE;
418         // Callback(DONE, 0, &Argument);
419         return STATUS_NOT_SUPPORTED;
420     }
421 
422     return FileSystem->FormatFunc(DriveRoot,
423                                   MediaFlag,
424                                   Label,
425                                   QuickFormat,
426                                   ClusterSize,
427                                   Callback);
428 }
429 
430 NTSTATUS
431 FormatFileSystem(
432     IN PCWSTR DriveRoot,
433     IN PCWSTR FileSystemName,
434     IN FMIFS_MEDIA_FLAG MediaFlag,
435     IN PCWSTR Label,
436     IN BOOLEAN QuickFormat,
437     IN ULONG ClusterSize,
438     IN PFMIFSCALLBACK Callback)
439 {
440     UNICODE_STRING DriveRootU;
441     UNICODE_STRING LabelU;
442 
443     RtlInitUnicodeString(&DriveRootU, DriveRoot);
444     RtlInitUnicodeString(&LabelU, Label);
445 
446     return FormatFileSystem_UStr(&DriveRootU,
447                                  FileSystemName,
448                                  MediaFlag,
449                                  &LabelU,
450                                  QuickFormat,
451                                  ClusterSize,
452                                  Callback);
453 }
454 
455 
456 UCHAR
457 FileSystemToPartitionType(
458     IN PCWSTR FileSystem,
459     IN PULARGE_INTEGER StartSector,
460     IN PULARGE_INTEGER SectorCount)
461 {
462     ASSERT(FileSystem && StartSector && SectorCount);
463 
464     if (wcsicmp(FileSystem, L"FAT")   == 0 ||
465         wcsicmp(FileSystem, L"FAT32") == 0 ||
466         wcsicmp(FileSystem, L"RAW")   == 0)
467     {
468         if (SectorCount->QuadPart < 8192)
469         {
470             /* FAT12 CHS partition (disk is smaller than 4.1MB) */
471             return PARTITION_FAT_12;
472         }
473         else if (StartSector->QuadPart < 1450560)
474         {
475             /* Partition starts below the 8.4GB boundary ==> CHS partition */
476 
477             if (SectorCount->QuadPart < 65536)
478             {
479                 /* FAT16 CHS partition (partition size < 32MB) */
480                 return PARTITION_FAT_16;
481             }
482             else if (SectorCount->QuadPart < 1048576)
483             {
484                 /* FAT16 CHS partition (partition size < 512MB) */
485                 return PARTITION_HUGE;
486             }
487             else
488             {
489                 /* FAT32 CHS partition (partition size >= 512MB) */
490                 return PARTITION_FAT32;
491             }
492         }
493         else
494         {
495             /* Partition starts above the 8.4GB boundary ==> LBA partition */
496 
497             if (SectorCount->QuadPart < 1048576)
498             {
499                 /* FAT16 LBA partition (partition size < 512MB) */
500                 return PARTITION_XINT13;
501             }
502             else
503             {
504                 /* FAT32 LBA partition (partition size >= 512MB) */
505                 return PARTITION_FAT32_XINT13;
506             }
507         }
508     }
509     else if (wcsicmp(FileSystem, L"NTFS") == 0)
510     {
511         return PARTITION_IFS;
512     }
513     else if (wcsicmp(FileSystem, L"BTRFS") == 0 ||
514              wcsicmp(FileSystem, L"EXT2")  == 0 ||
515              wcsicmp(FileSystem, L"EXT3")  == 0 ||
516              wcsicmp(FileSystem, L"EXT4")  == 0 ||
517              wcsicmp(FileSystem, L"FFS")   == 0 ||
518              wcsicmp(FileSystem, L"REISERFS") == 0)
519     {
520         return PARTITION_LINUX;
521     }
522     else
523     {
524         /* Unknown file system */
525         DPRINT1("Unknown file system '%S'\n", FileSystem);
526         return PARTITION_ENTRY_UNUSED;
527     }
528 }
529 
530 
531 //
532 // Formatting routines
533 //
534 
535 BOOLEAN
536 PreparePartitionForFormatting(
537     IN struct _PARTENTRY* PartEntry,
538     IN PCWSTR FileSystemName)
539 {
540     UCHAR PartitionType;
541 
542     if (!FileSystemName || !*FileSystemName)
543     {
544         DPRINT1("No file system specified?\n");
545         return FALSE;
546     }
547 
548     PartitionType = FileSystemToPartitionType(FileSystemName,
549                                               &PartEntry->StartSector,
550                                               &PartEntry->SectorCount);
551     if (PartitionType == PARTITION_ENTRY_UNUSED)
552     {
553         /* Unknown file system */
554         DPRINT1("Unknown file system '%S'\n", FileSystemName);
555         return FALSE;
556     }
557 
558     SetPartitionType(PartEntry, PartitionType);
559 
560 //
561 // FIXME: Do this now, or after the partition was actually formatted??
562 //
563     /* Set the new partition's file system proper */
564     RtlStringCbCopyW(PartEntry->FileSystem,
565                      sizeof(PartEntry->FileSystem),
566                      FileSystemName);
567 
568     return TRUE;
569 }
570 
571 /* EOF */
572