xref: /reactos/base/setup/lib/fsutil.c (revision 6f15802a)
1 /*
2  * PROJECT:     ReactOS Setup Library
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Filesystem Format and ChkDsk support functions
5  * COPYRIGHT:   Copyright 2003-2019 Casper S. Hornstrup <chorns@users.sourceforge.net>
6  *              Copyright 2017-2024 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
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 "partlist.h"
20 #include "fsrec.h"
21 #include "bootcode.h"
22 #include "fsutil.h"
23 
24 #include <fslib/vfatlib.h>
25 #include <fslib/btrfslib.h>
26 // #include <fslib/ext2lib.h>
27 // #include <fslib/ntfslib.h>
28 
29 #define NDEBUG
30 #include <debug.h>
31 
32 
33 /* TYPEDEFS *****************************************************************/
34 
35 #include <pshpack1.h>
36 typedef struct _FAT_BOOTSECTOR
37 {
38     UCHAR       JumpBoot[3];                // Jump instruction to boot code
39     CHAR        OemName[8];                 // "MSWIN4.1" for MS formatted volumes
40     USHORT      BytesPerSector;             // Bytes per sector
41     UCHAR       SectorsPerCluster;          // Number of sectors in a cluster
42     USHORT      ReservedSectors;            // Reserved sectors, usually 1 (the bootsector)
43     UCHAR       NumberOfFats;               // Number of FAT tables
44     USHORT      RootDirEntries;             // Number of root directory entries (fat12/16)
45     USHORT      TotalSectors;               // Number of total sectors on the drive, 16-bit
46     UCHAR       MediaDescriptor;            // Media descriptor byte
47     USHORT      SectorsPerFat;              // Sectors per FAT table (fat12/16)
48     USHORT      SectorsPerTrack;            // Number of sectors in a track
49     USHORT      NumberOfHeads;              // Number of heads on the disk
50     ULONG       HiddenSectors;              // Hidden sectors (sectors before the partition start like the partition table)
51     ULONG       TotalSectorsBig;            // This field is the new 32-bit total count of sectors on the volume
52     UCHAR       DriveNumber;                // Int 0x13 drive number (e.g. 0x80)
53     UCHAR       Reserved1;                  // Reserved (used by Windows NT). Code that formats FAT volumes should always set this byte to 0.
54     UCHAR       BootSignature;              // Extended boot signature (0x29). This is a signature byte that indicates that the following three fields in the boot sector are present.
55     ULONG       VolumeSerialNumber;         // Volume serial number
56     CHAR        VolumeLabel[11];            // Volume label. This field matches the 11-byte volume label recorded in the root directory
57     CHAR        FileSystemType[8];          // One of the strings "FAT12   ", "FAT16   ", or "FAT     "
58 
59     UCHAR       BootCodeAndData[448];       // The remainder of the boot sector
60 
61     USHORT      BootSectorMagic;            // 0xAA55
62 
63 } FAT_BOOTSECTOR, *PFAT_BOOTSECTOR;
64 C_ASSERT(sizeof(FAT_BOOTSECTOR) == FAT_BOOTSECTOR_SIZE);
65 
66 typedef struct _FAT32_BOOTSECTOR
67 {
68     UCHAR       JumpBoot[3];                // Jump instruction to boot code
69     CHAR        OemName[8];                 // "MSWIN4.1" for MS formatted volumes
70     USHORT      BytesPerSector;             // Bytes per sector
71     UCHAR       SectorsPerCluster;          // Number of sectors in a cluster
72     USHORT      ReservedSectors;            // Reserved sectors, usually 1 (the bootsector)
73     UCHAR       NumberOfFats;               // Number of FAT tables
74     USHORT      RootDirEntries;             // Number of root directory entries (fat12/16)
75     USHORT      TotalSectors;               // Number of total sectors on the drive, 16-bit
76     UCHAR       MediaDescriptor;            // Media descriptor byte
77     USHORT      SectorsPerFat;              // Sectors per FAT table (fat12/16)
78     USHORT      SectorsPerTrack;            // Number of sectors in a track
79     USHORT      NumberOfHeads;              // Number of heads on the disk
80     ULONG       HiddenSectors;              // Hidden sectors (sectors before the partition start like the partition table)
81     ULONG       TotalSectorsBig;            // This field is the new 32-bit total count of sectors on the volume
82     ULONG       SectorsPerFatBig;           // This field is the FAT32 32-bit count of sectors occupied by ONE FAT. BPB_FATSz16 must be 0
83     USHORT      ExtendedFlags;              // Extended flags (fat32)
84     USHORT      FileSystemVersion;          // File system version (fat32)
85     ULONG       RootDirStartCluster;        // Starting cluster of the root directory (fat32)
86     USHORT      FsInfo;                     // Sector number of FSINFO structure in the reserved area of the FAT32 volume. Usually 1.
87     USHORT      BackupBootSector;           // If non-zero, indicates the sector number in the reserved area of the volume of a copy of the boot record. Usually 6.
88     UCHAR       Reserved[12];               // Reserved for future expansion
89     UCHAR       DriveNumber;                // Int 0x13 drive number (e.g. 0x80)
90     UCHAR       Reserved1;                  // Reserved (used by Windows NT). Code that formats FAT volumes should always set this byte to 0.
91     UCHAR       BootSignature;              // Extended boot signature (0x29). This is a signature byte that indicates that the following three fields in the boot sector are present.
92     ULONG       VolumeSerialNumber;         // Volume serial number
93     CHAR        VolumeLabel[11];            // Volume label. This field matches the 11-byte volume label recorded in the root directory
94     CHAR        FileSystemType[8];          // Always set to the string "FAT32   "
95 
96     UCHAR       BootCodeAndData[420];       // The remainder of the boot sector
97 
98     USHORT      BootSectorMagic;            // 0xAA55
99 
100 } FAT32_BOOTSECTOR, *PFAT32_BOOTSECTOR;
101 C_ASSERT(sizeof(FAT32_BOOTSECTOR) == FAT32_BOOTSECTOR_SIZE);
102 
103 typedef struct _BTRFS_BOOTSECTOR
104 {
105     UCHAR JumpBoot[3];
106     UCHAR ChunkMapSize;
107     UCHAR BootDrive;
108     ULONGLONG PartitionStartLBA;
109     UCHAR Fill[1521]; // 1536 - 15
110     USHORT BootSectorMagic;
111 } BTRFS_BOOTSECTOR, *PBTRFS_BOOTSECTOR;
112 C_ASSERT(sizeof(BTRFS_BOOTSECTOR) == BTRFS_BOOTSECTOR_SIZE);
113 
114 typedef struct _NTFS_BOOTSECTOR
115 {
116     UCHAR Jump[3];
117     UCHAR OEMID[8];
118     USHORT BytesPerSector;
119     UCHAR SectorsPerCluster;
120     UCHAR Unused0[7];
121     UCHAR MediaId;
122     UCHAR Unused1[2];
123     USHORT SectorsPerTrack;
124     USHORT Heads;
125     UCHAR Unused2[4];
126     UCHAR Unused3[4];
127     USHORT Unknown[2];
128     ULONGLONG SectorCount;
129     ULONGLONG MftLocation;
130     ULONGLONG MftMirrLocation;
131     CHAR ClustersPerMftRecord;
132     UCHAR Unused4[3];
133     CHAR ClustersPerIndexRecord;
134     UCHAR Unused5[3];
135     ULONGLONG SerialNumber;
136     UCHAR Checksum[4];
137     UCHAR BootStrap[426];
138     USHORT EndSector;
139     UCHAR BootCodeAndData[7680]; // The remainder of the boot sector (8192 - 512)
140 } NTFS_BOOTSECTOR, *PNTFS_BOOTSECTOR;
141 C_ASSERT(sizeof(NTFS_BOOTSECTOR) == NTFS_BOOTSECTOR_SIZE);
142 
143 // TODO: Add more bootsector structures!
144 
145 #include <poppack.h>
146 
147 
148 /* LOCALS *******************************************************************/
149 
150 /** IFS_PROVIDER **/
151 typedef struct _FILE_SYSTEM
152 {
153     PCWSTR FileSystemName;
154     PULIB_FORMAT FormatFunc;
155     PULIB_CHKDSK ChkdskFunc;
156 } FILE_SYSTEM, *PFILE_SYSTEM;
157 
158 /* The list of file systems on which we can install ReactOS */
159 static FILE_SYSTEM RegisteredFileSystems[] =
160 {
161     /* NOTE: The FAT formatter will automatically
162      * determine whether to use FAT12/16 or FAT32. */
163     { L"FAT"  , VfatFormat, VfatChkdsk },
164     { L"FAT32", VfatFormat, VfatChkdsk },
165 #if 0
166     { L"FATX" , VfatxFormat, VfatxChkdsk },
167     { L"NTFS" , NtfsFormat, NtfsChkdsk },
168 #endif
169     { L"BTRFS", BtrfsFormat, BtrfsChkdsk },
170 #if 0
171     { L"EXT2" , Ext2Format, Ext2Chkdsk },
172     { L"EXT3" , Ext2Format, Ext2Chkdsk },
173     { L"EXT4" , Ext2Format, Ext2Chkdsk },
174 #endif
175 };
176 
177 
178 /* FUNCTIONS ****************************************************************/
179 
180 /** QueryAvailableFileSystemFormat() **/
181 BOOLEAN
GetRegisteredFileSystems(IN ULONG Index,OUT PCWSTR * FileSystemName)182 GetRegisteredFileSystems(
183     IN ULONG Index,
184     OUT PCWSTR* FileSystemName)
185 {
186     if (Index >= ARRAYSIZE(RegisteredFileSystems))
187         return FALSE;
188 
189     *FileSystemName = RegisteredFileSystems[Index].FileSystemName;
190 
191     return TRUE;
192 }
193 
194 
195 /** GetProvider() **/
196 static PFILE_SYSTEM
GetFileSystemByName(IN PCWSTR FileSystemName)197 GetFileSystemByName(
198     IN PCWSTR FileSystemName)
199 {
200 #if 0 // Reenable when the list of registered FSes will again be dynamic
201 
202     PLIST_ENTRY ListEntry;
203     PFILE_SYSTEM_ITEM Item;
204 
205     ListEntry = List->ListHead.Flink;
206     while (ListEntry != &List->ListHead)
207     {
208         Item = CONTAINING_RECORD(ListEntry, FILE_SYSTEM_ITEM, ListEntry);
209         if (Item->FileSystemName &&
210             (_wcsicmp(FileSystemName, Item->FileSystemName) == 0))
211         {
212             return Item;
213         }
214 
215         ListEntry = ListEntry->Flink;
216     }
217 
218 #else
219 
220     ULONG Count = ARRAYSIZE(RegisteredFileSystems);
221     PFILE_SYSTEM FileSystems = RegisteredFileSystems;
222 
223     ASSERT(FileSystems && Count != 0);
224 
225     while (Count--)
226     {
227         if (FileSystems->FileSystemName &&
228             (_wcsicmp(FileSystemName, FileSystems->FileSystemName) == 0))
229         {
230             return FileSystems;
231         }
232 
233         ++FileSystems;
234     }
235 
236 #endif
237 
238     return NULL;
239 }
240 
241 
242 /** ChkdskEx() **/
243 NTSTATUS
ChkdskFileSystem_UStr(_In_ PUNICODE_STRING DriveRoot,_In_ PCWSTR FileSystemName,_In_ BOOLEAN FixErrors,_In_ BOOLEAN Verbose,_In_ BOOLEAN CheckOnlyIfDirty,_In_ BOOLEAN ScanDrive,_In_opt_ PFMIFSCALLBACK Callback)244 ChkdskFileSystem_UStr(
245     _In_ PUNICODE_STRING DriveRoot,
246     _In_ PCWSTR FileSystemName,
247     _In_ BOOLEAN FixErrors,
248     _In_ BOOLEAN Verbose,
249     _In_ BOOLEAN CheckOnlyIfDirty,
250     _In_ BOOLEAN ScanDrive,
251     _In_opt_ PFMIFSCALLBACK Callback)
252 {
253     PFILE_SYSTEM FileSystem;
254     NTSTATUS Status;
255     BOOLEAN Success;
256 
257     FileSystem = GetFileSystemByName(FileSystemName);
258 
259     if (!FileSystem || !FileSystem->ChkdskFunc)
260     {
261         // Success = FALSE;
262         // Callback(DONE, 0, &Success);
263         return STATUS_NOT_SUPPORTED;
264     }
265 
266     Status = STATUS_SUCCESS;
267     Success = FileSystem->ChkdskFunc(DriveRoot,
268                                      Callback,
269                                      FixErrors,
270                                      Verbose,
271                                      CheckOnlyIfDirty,
272                                      ScanDrive,
273                                      NULL,
274                                      NULL,
275                                      NULL,
276                                      NULL,
277                                      (PULONG)&Status);
278     if (!Success)
279         DPRINT1("ChkdskFunc() failed with Status 0x%lx\n", Status);
280 
281     // Callback(DONE, 0, &Success);
282 
283     return Status;
284 }
285 
286 NTSTATUS
ChkdskFileSystem(_In_ PCWSTR DriveRoot,_In_ PCWSTR FileSystemName,_In_ BOOLEAN FixErrors,_In_ BOOLEAN Verbose,_In_ BOOLEAN CheckOnlyIfDirty,_In_ BOOLEAN ScanDrive,_In_opt_ PFMIFSCALLBACK Callback)287 ChkdskFileSystem(
288     _In_ PCWSTR DriveRoot,
289     _In_ PCWSTR FileSystemName,
290     _In_ BOOLEAN FixErrors,
291     _In_ BOOLEAN Verbose,
292     _In_ BOOLEAN CheckOnlyIfDirty,
293     _In_ BOOLEAN ScanDrive,
294     _In_opt_ PFMIFSCALLBACK Callback)
295 {
296     UNICODE_STRING DriveRootU;
297 
298     RtlInitUnicodeString(&DriveRootU, DriveRoot);
299     return ChkdskFileSystem_UStr(&DriveRootU,
300                                  FileSystemName,
301                                  FixErrors,
302                                  Verbose,
303                                  CheckOnlyIfDirty,
304                                  ScanDrive,
305                                  Callback);
306 }
307 
308 
309 /** FormatEx() **/
310 NTSTATUS
FormatFileSystem_UStr(_In_ PUNICODE_STRING DriveRoot,_In_ PCWSTR FileSystemName,_In_ FMIFS_MEDIA_FLAG MediaFlag,_In_opt_ PUNICODE_STRING Label,_In_ BOOLEAN QuickFormat,_In_ ULONG ClusterSize,_In_opt_ PFMIFSCALLBACK Callback)311 FormatFileSystem_UStr(
312     _In_ PUNICODE_STRING DriveRoot,
313     _In_ PCWSTR FileSystemName,
314     _In_ FMIFS_MEDIA_FLAG MediaFlag,
315     _In_opt_ PUNICODE_STRING Label,
316     _In_ BOOLEAN QuickFormat,
317     _In_ ULONG ClusterSize,
318     _In_opt_ PFMIFSCALLBACK Callback)
319 {
320     PFILE_SYSTEM FileSystem;
321     BOOLEAN Success;
322     BOOLEAN BackwardCompatible = FALSE; // Default to latest FS versions.
323     MEDIA_TYPE MediaType;
324 
325     FileSystem = GetFileSystemByName(FileSystemName);
326 
327     if (!FileSystem || !FileSystem->FormatFunc)
328     {
329         // Success = FALSE;
330         // Callback(DONE, 0, &Success);
331         return STATUS_NOT_SUPPORTED;
332     }
333 
334     /* Set the BackwardCompatible flag in case we format with older FAT12/16 */
335     if (_wcsicmp(FileSystemName, L"FAT") == 0)
336         BackwardCompatible = TRUE;
337     // else if (_wcsicmp(FileSystemName, L"FAT32") == 0)
338         // BackwardCompatible = FALSE;
339 
340     /* Convert the FMIFS MediaFlag to a NT MediaType */
341     // FIXME: Actually covert all the possible flags.
342     switch (MediaFlag)
343     {
344     case FMIFS_FLOPPY:
345         MediaType = F5_320_1024; // FIXME: This is hardfixed!
346         break;
347     case FMIFS_REMOVABLE:
348         MediaType = RemovableMedia;
349         break;
350     case FMIFS_HARDDISK:
351         MediaType = FixedMedia;
352         break;
353     default:
354         DPRINT1("Unknown FMIFS MediaFlag %d, converting 1-to-1 to NT MediaType\n",
355                 MediaFlag);
356         MediaType = (MEDIA_TYPE)MediaFlag;
357         break;
358     }
359 
360     Success = FileSystem->FormatFunc(DriveRoot,
361                                      Callback,
362                                      QuickFormat,
363                                      BackwardCompatible,
364                                      MediaType,
365                                      Label,
366                                      ClusterSize);
367     if (!Success)
368         DPRINT1("FormatFunc() failed\n");
369 
370     // Callback(DONE, 0, &Success);
371 
372     return (Success ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL);
373 }
374 
375 NTSTATUS
FormatFileSystem(_In_ PCWSTR DriveRoot,_In_ PCWSTR FileSystemName,_In_ FMIFS_MEDIA_FLAG MediaFlag,_In_opt_ PCWSTR Label,_In_ BOOLEAN QuickFormat,_In_ ULONG ClusterSize,_In_opt_ PFMIFSCALLBACK Callback)376 FormatFileSystem(
377     _In_ PCWSTR DriveRoot,
378     _In_ PCWSTR FileSystemName,
379     _In_ FMIFS_MEDIA_FLAG MediaFlag,
380     _In_opt_ PCWSTR Label,
381     _In_ BOOLEAN QuickFormat,
382     _In_ ULONG ClusterSize,
383     _In_opt_ PFMIFSCALLBACK Callback)
384 {
385     UNICODE_STRING DriveRootU;
386     UNICODE_STRING LabelU;
387 
388     RtlInitUnicodeString(&DriveRootU, DriveRoot);
389     RtlInitUnicodeString(&LabelU, Label);
390 
391     return FormatFileSystem_UStr(&DriveRootU,
392                                  FileSystemName,
393                                  MediaFlag,
394                                  &LabelU,
395                                  QuickFormat,
396                                  ClusterSize,
397                                  Callback);
398 }
399 
400 
401 //
402 // Bootsector routines
403 //
404 
405 NTSTATUS
InstallFatBootCode(IN PCWSTR SrcPath,IN HANDLE DstPath,IN HANDLE RootPartition)406 InstallFatBootCode(
407     IN PCWSTR SrcPath,          // FAT12/16 bootsector source file (on the installation medium)
408     IN HANDLE DstPath,          // Where to save the bootsector built from the source + partition information
409     IN HANDLE RootPartition)    // Partition holding the (old) FAT12/16 information
410 {
411     NTSTATUS Status;
412     UNICODE_STRING Name;
413     IO_STATUS_BLOCK IoStatusBlock;
414     LARGE_INTEGER FileOffset;
415     BOOTCODE OrigBootSector = {0};
416     BOOTCODE NewBootSector  = {0};
417 
418     /* Allocate and read the current original partition bootsector */
419     Status = ReadBootCodeByHandle(&OrigBootSector,
420                                   RootPartition,
421                                   FAT_BOOTSECTOR_SIZE);
422     if (!NT_SUCCESS(Status))
423         return Status;
424 
425     /* Allocate and read the new bootsector from SrcPath */
426     RtlInitUnicodeString(&Name, SrcPath);
427     Status = ReadBootCodeFromFile(&NewBootSector,
428                                   &Name,
429                                   FAT_BOOTSECTOR_SIZE);
430     if (!NT_SUCCESS(Status))
431     {
432         FreeBootCode(&OrigBootSector);
433         return Status;
434     }
435 
436     /* Adjust the bootsector (copy a part of the FAT12/16 BPB) */
437     RtlCopyMemory(&((PFAT_BOOTSECTOR)NewBootSector.BootCode)->OemName,
438                   &((PFAT_BOOTSECTOR)OrigBootSector.BootCode)->OemName,
439                   FIELD_OFFSET(FAT_BOOTSECTOR, BootCodeAndData) -
440                   FIELD_OFFSET(FAT_BOOTSECTOR, OemName));
441 
442     /* Free the original bootsector */
443     FreeBootCode(&OrigBootSector);
444 
445     /* Write the new bootsector to DstPath */
446     FileOffset.QuadPart = 0ULL;
447     Status = NtWriteFile(DstPath,
448                          NULL,
449                          NULL,
450                          NULL,
451                          &IoStatusBlock,
452                          NewBootSector.BootCode,
453                          NewBootSector.Length,
454                          &FileOffset,
455                          NULL);
456 
457     /* Free the new bootsector */
458     FreeBootCode(&NewBootSector);
459 
460     return Status;
461 }
462 
463 NTSTATUS
InstallFat32BootCode(IN PCWSTR SrcPath,IN HANDLE DstPath,IN HANDLE RootPartition)464 InstallFat32BootCode(
465     IN PCWSTR SrcPath,          // FAT32 bootsector source file (on the installation medium)
466     IN HANDLE DstPath,          // Where to save the bootsector built from the source + partition information
467     IN HANDLE RootPartition)    // Partition holding the (old) FAT32 information
468 {
469     NTSTATUS Status;
470     UNICODE_STRING Name;
471     IO_STATUS_BLOCK IoStatusBlock;
472     LARGE_INTEGER FileOffset;
473     USHORT BackupBootSector = 0;
474     BOOTCODE OrigBootSector = {0};
475     BOOTCODE NewBootSector  = {0};
476 
477     /* Allocate and read the current original partition bootsector */
478     Status = ReadBootCodeByHandle(&OrigBootSector,
479                                   RootPartition,
480                                   FAT32_BOOTSECTOR_SIZE);
481     if (!NT_SUCCESS(Status))
482         return Status;
483 
484     /* Allocate and read the new bootsector (2 sectors) from SrcPath */
485     RtlInitUnicodeString(&Name, SrcPath);
486     Status = ReadBootCodeFromFile(&NewBootSector,
487                                   &Name,
488                                   2 * FAT32_BOOTSECTOR_SIZE);
489     if (!NT_SUCCESS(Status))
490     {
491         FreeBootCode(&OrigBootSector);
492         return Status;
493     }
494 
495     /* Adjust the bootsector (copy a part of the FAT32 BPB) */
496     RtlCopyMemory(&((PFAT32_BOOTSECTOR)NewBootSector.BootCode)->OemName,
497                   &((PFAT32_BOOTSECTOR)OrigBootSector.BootCode)->OemName,
498                   FIELD_OFFSET(FAT32_BOOTSECTOR, BootCodeAndData) -
499                   FIELD_OFFSET(FAT32_BOOTSECTOR, OemName));
500 
501     /*
502      * We know we copy the boot code to a file only when DstPath != RootPartition,
503      * otherwise the boot code is copied to the specified root partition.
504      */
505     if (DstPath != RootPartition)
506     {
507         /* Copy to a file: Disable the backup bootsector */
508         ((PFAT32_BOOTSECTOR)NewBootSector.BootCode)->BackupBootSector = 0;
509     }
510     else
511     {
512         /* Copy to a disk: Get the location of the backup bootsector */
513         BackupBootSector = ((PFAT32_BOOTSECTOR)OrigBootSector.BootCode)->BackupBootSector;
514     }
515 
516     /* Free the original bootsector */
517     FreeBootCode(&OrigBootSector);
518 
519     /* Write the first sector of the new bootcode to DstPath sector 0 */
520     FileOffset.QuadPart = 0ULL;
521     Status = NtWriteFile(DstPath,
522                          NULL,
523                          NULL,
524                          NULL,
525                          &IoStatusBlock,
526                          NewBootSector.BootCode,
527                          FAT32_BOOTSECTOR_SIZE,
528                          &FileOffset,
529                          NULL);
530     if (!NT_SUCCESS(Status))
531     {
532         DPRINT1("NtWriteFile() failed (Status %lx)\n", Status);
533         FreeBootCode(&NewBootSector);
534         return Status;
535     }
536 
537     if (DstPath == RootPartition)
538     {
539         /* Copy to a disk: Write the backup bootsector */
540         if ((BackupBootSector != 0x0000) && (BackupBootSector != 0xFFFF))
541         {
542             FileOffset.QuadPart = (ULONGLONG)((ULONG)BackupBootSector * FAT32_BOOTSECTOR_SIZE);
543             Status = NtWriteFile(DstPath,
544                                  NULL,
545                                  NULL,
546                                  NULL,
547                                  &IoStatusBlock,
548                                  NewBootSector.BootCode,
549                                  FAT32_BOOTSECTOR_SIZE,
550                                  &FileOffset,
551                                  NULL);
552             if (!NT_SUCCESS(Status))
553             {
554                 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status);
555                 FreeBootCode(&NewBootSector);
556                 return Status;
557             }
558         }
559     }
560 
561     /* Write the second sector of the new bootcode to boot disk sector 14 */
562     // FileOffset.QuadPart = (ULONGLONG)(14 * FAT32_BOOTSECTOR_SIZE);
563     FileOffset.QuadPart = 14 * FAT32_BOOTSECTOR_SIZE;
564     Status = NtWriteFile(DstPath,   // or really RootPartition ???
565                          NULL,
566                          NULL,
567                          NULL,
568                          &IoStatusBlock,
569                          ((PUCHAR)NewBootSector.BootCode + FAT32_BOOTSECTOR_SIZE),
570                          FAT32_BOOTSECTOR_SIZE,
571                          &FileOffset,
572                          NULL);
573     if (!NT_SUCCESS(Status))
574     {
575         DPRINT1("NtWriteFile() failed (Status %lx)\n", Status);
576     }
577 
578     /* Free the new bootsector */
579     FreeBootCode(&NewBootSector);
580 
581     return Status;
582 }
583 
584 NTSTATUS
InstallBtrfsBootCode(IN PCWSTR SrcPath,IN HANDLE DstPath,IN HANDLE RootPartition)585 InstallBtrfsBootCode(
586     IN PCWSTR SrcPath,          // BTRFS bootsector source file (on the installation medium)
587     IN HANDLE DstPath,          // Where to save the bootsector built from the source + partition information
588     IN HANDLE RootPartition)    // Partition holding the (old) BTRFS information
589 {
590     NTSTATUS Status;
591     NTSTATUS LockStatus;
592     UNICODE_STRING Name;
593     IO_STATUS_BLOCK IoStatusBlock;
594     LARGE_INTEGER FileOffset;
595     PARTITION_INFORMATION_EX PartInfo;
596     BOOTCODE NewBootSector = {0};
597 
598     /* Allocate and read the new bootsector from SrcPath */
599     RtlInitUnicodeString(&Name, SrcPath);
600     Status = ReadBootCodeFromFile(&NewBootSector,
601                                   &Name,
602                                   BTRFS_BOOTSECTOR_SIZE);
603     if (!NT_SUCCESS(Status))
604         return Status;
605 
606     /*
607      * The BTRFS driver requires the volume to be locked in order to modify
608      * the first sectors of the partition, even though they are outside the
609      * file-system space / in the reserved area (they are situated before
610      * the super-block at 0x1000) and is in principle allowed by the NT
611      * storage stack.
612      * So we lock here in order to write the bootsector at sector 0.
613      * If locking fails, we ignore and continue nonetheless.
614      */
615     LockStatus = NtFsControlFile(DstPath,
616                                  NULL,
617                                  NULL,
618                                  NULL,
619                                  &IoStatusBlock,
620                                  FSCTL_LOCK_VOLUME,
621                                  NULL,
622                                  0,
623                                  NULL,
624                                  0);
625     if (!NT_SUCCESS(LockStatus))
626     {
627         DPRINT1("WARNING: Failed to lock BTRFS volume for writing bootsector! Operations may fail! (Status 0x%lx)\n", LockStatus);
628     }
629 
630     /* Obtain partition info and write it to the bootsector */
631     Status = NtDeviceIoControlFile(RootPartition,
632                                    NULL,
633                                    NULL,
634                                    NULL,
635                                    &IoStatusBlock,
636                                    IOCTL_DISK_GET_PARTITION_INFO_EX,
637                                    NULL,
638                                    0,
639                                    &PartInfo,
640                                    sizeof(PartInfo));
641     if (!NT_SUCCESS(Status))
642     {
643         DPRINT1("IOCTL_DISK_GET_PARTITION_INFO_EX failed (Status %lx)\n", Status);
644         goto Quit;
645     }
646 
647     /* Write new bootsector to RootPath */
648     ((PBTRFS_BOOTSECTOR)NewBootSector.BootCode)->PartitionStartLBA =
649         PartInfo.StartingOffset.QuadPart / SECTORSIZE;
650 
651     /* Write sector 0 */
652     FileOffset.QuadPart = 0ULL;
653     Status = NtWriteFile(DstPath,
654                          NULL,
655                          NULL,
656                          NULL,
657                          &IoStatusBlock,
658                          NewBootSector.BootCode,
659                          NewBootSector.Length,
660                          &FileOffset,
661                          NULL);
662     if (!NT_SUCCESS(Status))
663     {
664         DPRINT1("NtWriteFile() failed (Status %lx)\n", Status);
665         goto Quit;
666     }
667 
668 Quit:
669     /* Unlock the volume */
670     LockStatus = NtFsControlFile(DstPath,
671                                  NULL,
672                                  NULL,
673                                  NULL,
674                                  &IoStatusBlock,
675                                  FSCTL_UNLOCK_VOLUME,
676                                  NULL,
677                                  0,
678                                  NULL,
679                                  0);
680     if (!NT_SUCCESS(LockStatus))
681     {
682         DPRINT1("Failed to unlock BTRFS volume (Status 0x%lx)\n", LockStatus);
683     }
684 
685     /* Free the new bootsector */
686     FreeBootCode(&NewBootSector);
687 
688     return Status;
689 }
690 
691 NTSTATUS
InstallNtfsBootCode(IN PCWSTR SrcPath,IN HANDLE DstPath,IN HANDLE RootPartition)692 InstallNtfsBootCode(
693     IN PCWSTR SrcPath,          // NTFS bootsector source file (on the installation medium)
694     IN HANDLE DstPath,          // Where to save the bootsector built from the source + partition information
695     IN HANDLE RootPartition)    // Partition holding the (old) NTFS information
696 {
697     NTSTATUS Status;
698     UNICODE_STRING Name;
699     IO_STATUS_BLOCK IoStatusBlock;
700     LARGE_INTEGER FileOffset;
701     BOOTCODE OrigBootSector = {0};
702     BOOTCODE NewBootSector  = {0};
703 
704     /* Allocate and read the current original partition bootsector */
705     Status = ReadBootCodeByHandle(&OrigBootSector, RootPartition, NTFS_BOOTSECTOR_SIZE);
706     if (!NT_SUCCESS(Status))
707     {
708         DPRINT1("InstallNtfsBootCode: Status %lx\n", Status);
709         return Status;
710     }
711 
712     /* Allocate and read the new bootsector (16 sectors) from SrcPath */
713     RtlInitUnicodeString(&Name, SrcPath);
714     Status = ReadBootCodeFromFile(&NewBootSector, &Name, NTFS_BOOTSECTOR_SIZE);
715     if (!NT_SUCCESS(Status))
716     {
717         DPRINT1("InstallNtfsBootCode: Status %lx\n", Status);
718         FreeBootCode(&OrigBootSector);
719         return Status;
720     }
721 
722     /* Adjust the bootsector (copy a part of the NTFS BPB) */
723     RtlCopyMemory(&((PNTFS_BOOTSECTOR)NewBootSector.BootCode)->OEMID,
724                   &((PNTFS_BOOTSECTOR)OrigBootSector.BootCode)->OEMID,
725                   FIELD_OFFSET(NTFS_BOOTSECTOR, BootStrap) - FIELD_OFFSET(NTFS_BOOTSECTOR, OEMID));
726 
727     /* Write sector 0 */
728     FileOffset.QuadPart = 0ULL;
729     Status = NtWriteFile(DstPath,
730                          NULL,
731                          NULL,
732                          NULL,
733                          &IoStatusBlock,
734                          NewBootSector.BootCode,
735                          NewBootSector.Length,
736                          &FileOffset,
737                          NULL);
738     if (!NT_SUCCESS(Status))
739     {
740         DPRINT1("NtWriteFile() failed (Status %lx)\n", Status);
741         goto Quit;
742     }
743 
744 Quit:
745     /* Free the new bootsector */
746     FreeBootCode(&NewBootSector);
747 
748     return Status;
749 }
750 
751 
752 //
753 // Formatting routines
754 //
755 
756 NTSTATUS
ChkdskVolume(_In_ PVOLINFO Volume,_In_ BOOLEAN FixErrors,_In_ BOOLEAN Verbose,_In_ BOOLEAN CheckOnlyIfDirty,_In_ BOOLEAN ScanDrive,_In_opt_ PFMIFSCALLBACK Callback)757 ChkdskVolume(
758     _In_ PVOLINFO Volume,
759     _In_ BOOLEAN FixErrors,
760     _In_ BOOLEAN Verbose,
761     _In_ BOOLEAN CheckOnlyIfDirty,
762     _In_ BOOLEAN ScanDrive,
763     _In_opt_ PFMIFSCALLBACK Callback)
764 {
765     /* Do not check a volume with an unknown file system */
766     if (!*Volume->FileSystem)
767         return STATUS_UNRECOGNIZED_VOLUME;
768 
769     /* Check the volume */
770     DPRINT("Volume->DeviceName: %S\n", Volume->DeviceName);
771     return ChkdskFileSystem(Volume->DeviceName,
772                             Volume->FileSystem,
773                             FixErrors,
774                             Verbose,
775                             CheckOnlyIfDirty,
776                             ScanDrive,
777                             Callback);
778 }
779 
780 NTSTATUS
ChkdskPartition(_In_ PPARTENTRY PartEntry,_In_ BOOLEAN FixErrors,_In_ BOOLEAN Verbose,_In_ BOOLEAN CheckOnlyIfDirty,_In_ BOOLEAN ScanDrive,_In_opt_ PFMIFSCALLBACK Callback)781 ChkdskPartition(
782     _In_ PPARTENTRY PartEntry,
783     _In_ BOOLEAN FixErrors,
784     _In_ BOOLEAN Verbose,
785     _In_ BOOLEAN CheckOnlyIfDirty,
786     _In_ BOOLEAN ScanDrive,
787     _In_opt_ PFMIFSCALLBACK Callback)
788 {
789     ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0);
790     ASSERT(PartEntry->Volume);
791 
792     // if (!PartEntry->Volume) { check_raw_sectors(); } else { check_FS(); }
793 
794     /* Check the associated volume */
795     return ChkdskVolume(&PartEntry->Volume->Info,
796                         FixErrors,
797                         Verbose,
798                         CheckOnlyIfDirty,
799                         ScanDrive,
800                         Callback);
801 }
802 
803 NTSTATUS
FormatVolume(_In_ PVOLINFO Volume,_In_ PCWSTR FileSystemName,_In_ FMIFS_MEDIA_FLAG MediaFlag,_In_opt_ PCWSTR Label,_In_ BOOLEAN QuickFormat,_In_ ULONG ClusterSize,_In_opt_ PFMIFSCALLBACK Callback)804 FormatVolume(
805     _In_ PVOLINFO Volume,
806     _In_ PCWSTR FileSystemName,
807     _In_ FMIFS_MEDIA_FLAG MediaFlag,
808     _In_opt_ PCWSTR Label,
809     _In_ BOOLEAN QuickFormat,
810     _In_ ULONG ClusterSize,
811     _In_opt_ PFMIFSCALLBACK Callback)
812 {
813     NTSTATUS Status;
814 
815     if (!FileSystemName || !*FileSystemName)
816     {
817         DPRINT1("No file system specified\n");
818         return STATUS_UNRECOGNIZED_VOLUME;
819     }
820 
821     /* Format the volume */
822     DPRINT("Volume->DeviceName: %S\n", Volume->DeviceName);
823     Status = FormatFileSystem(Volume->DeviceName,
824                               FileSystemName,
825                               MediaFlag,
826                               Label,
827                               QuickFormat,
828                               ClusterSize,
829                               Callback);
830     if (!NT_SUCCESS(Status))
831         return Status;
832 
833     /* Set the new volume's file system and label */
834     RtlStringCbCopyW(Volume->FileSystem, sizeof(Volume->FileSystem), FileSystemName);
835     if (!Label) Label = L"";
836     RtlStringCbCopyW(Volume->VolumeLabel, sizeof(Volume->VolumeLabel), Label);
837 
838     return STATUS_SUCCESS;
839 }
840 
841 NTSTATUS
FormatPartition(_In_ PPARTENTRY PartEntry,_In_ PCWSTR FileSystemName,_In_ FMIFS_MEDIA_FLAG MediaFlag,_In_opt_ PCWSTR Label,_In_ BOOLEAN QuickFormat,_In_ ULONG ClusterSize,_In_opt_ PFMIFSCALLBACK Callback)842 FormatPartition(
843     _In_ PPARTENTRY PartEntry,
844     _In_ PCWSTR FileSystemName,
845     _In_ FMIFS_MEDIA_FLAG MediaFlag,
846     _In_opt_ PCWSTR Label,
847     _In_ BOOLEAN QuickFormat,
848     _In_ ULONG ClusterSize,
849     _In_opt_ PFMIFSCALLBACK Callback)
850 {
851     NTSTATUS Status;
852     PDISKENTRY DiskEntry = PartEntry->DiskEntry;
853     UCHAR PartitionType;
854 
855     ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0);
856 
857     if (!FileSystemName || !*FileSystemName)
858     {
859         DPRINT1("No file system specified\n");
860         return STATUS_UNRECOGNIZED_VOLUME;
861     }
862 
863     /*
864      * Prepare the partition for formatting (for MBR disks, reset the
865      * partition type), and adjust the file system name in case of FAT
866      * vs. FAT32, depending on the geometry of the partition.
867      */
868 
869 // FIXME: Do this only if QuickFormat == FALSE? What about FAT handling?
870 
871     /*
872      * Retrieve a partition type as a hint only. It will be used to determine
873      * whether to actually use FAT12/16 or FAT32 file system, depending on the
874      * geometry of the partition. If the partition resides on an MBR disk,
875      * the partition style will be reset to this value as well, unless the
876      * partition is OEM.
877      */
878     PartitionType = FileSystemToMBRPartitionType(FileSystemName,
879                                                  PartEntry->StartSector.QuadPart,
880                                                  PartEntry->SectorCount.QuadPart);
881     if (PartitionType == PARTITION_ENTRY_UNUSED)
882     {
883         /* Unknown file system */
884         DPRINT1("Unknown file system '%S'\n", FileSystemName);
885         return STATUS_UNRECOGNIZED_VOLUME;
886     }
887 
888     /* Reset the MBR partition type, unless this is an OEM partition */
889     if (DiskEntry->DiskStyle == PARTITION_STYLE_MBR)
890     {
891         if (!IsOEMPartition(PartEntry->PartitionType))
892             SetMBRPartitionType(PartEntry, PartitionType);
893     }
894 
895     /*
896      * Adjust the file system name in case of FAT vs. FAT32, according to
897      * the type of partition returned by FileSystemToMBRPartitionType().
898      */
899     if (_wcsicmp(FileSystemName, L"FAT") == 0)
900     {
901         if ((PartitionType == PARTITION_FAT32) ||
902             (PartitionType == PARTITION_FAT32_XINT13))
903         {
904             FileSystemName = L"FAT32";
905         }
906     }
907 
908     /* Commit the partition changes to the disk */
909     Status = WritePartitions(DiskEntry);
910     if (!NT_SUCCESS(Status))
911     {
912         DPRINT1("WritePartitions(disk %lu) failed, Status 0x%08lx\n",
913                 DiskEntry->DiskNumber, Status);
914         return STATUS_PARTITION_FAILURE;
915     }
916 
917     /* We must have an associated volume now */
918     ASSERT(PartEntry->Volume);
919 
920     /* Format the associated volume */
921     Status = FormatVolume(&PartEntry->Volume->Info,
922                           FileSystemName,
923                           MediaFlag,
924                           Label,
925                           QuickFormat,
926                           ClusterSize,
927                           Callback);
928     if (!NT_SUCCESS(Status))
929         return Status;
930 
931     PartEntry->Volume->FormatState = Formatted;
932     PartEntry->Volume->New = FALSE;
933     return STATUS_SUCCESS;
934 }
935 
936 
937 //
938 // FileSystem Volume Operations Queue
939 //
940 
941 static FSVOL_OP
DoFormatting(_In_ PVOLENTRY Volume,_In_opt_ PVOID Context,_In_opt_ PFSVOL_CALLBACK FsVolCallback)942 DoFormatting(
943     _In_ PVOLENTRY Volume,
944     _In_opt_ PVOID Context,
945     _In_opt_ PFSVOL_CALLBACK FsVolCallback)
946 {
947     FSVOL_OP Result;
948     NTSTATUS Status = STATUS_SUCCESS;
949     PPARTENTRY PartEntry;
950     FORMAT_VOLUME_INFO FmtInfo = {0};
951 
952     PartEntry = Volume->PartEntry;
953     ASSERT(PartEntry && (PartEntry->Volume == Volume));
954 
955     FmtInfo.Volume = Volume;
956 
957 RetryFormat:
958     Result = FsVolCallback(Context,
959                            FSVOLNOTIFY_STARTFORMAT,
960                            (ULONG_PTR)&FmtInfo,
961                            FSVOL_FORMAT);
962     if (Result != FSVOL_DOIT)
963         goto EndFormat;
964 
965     ASSERT(FmtInfo.FileSystemName && *FmtInfo.FileSystemName);
966 
967     /* Format the partition */
968     Status = FormatPartition(PartEntry,
969                              FmtInfo.FileSystemName,
970                              FmtInfo.MediaFlag,
971                              FmtInfo.Label,
972                              FmtInfo.QuickFormat,
973                              FmtInfo.ClusterSize,
974                              FmtInfo.Callback);
975     if (!NT_SUCCESS(Status))
976     {
977         // FmtInfo.NtPathPartition = PathBuffer;
978         FmtInfo.ErrorStatus = Status;
979 
980         Result = FsVolCallback(Context,
981                                FSVOLNOTIFY_FORMATERROR,
982                                (ULONG_PTR)&FmtInfo,
983                                0);
984         if (Result == FSVOL_RETRY)
985             goto RetryFormat;
986         // else if (Result == FSVOL_ABORT || Result == FSVOL_SKIP), stop.
987     }
988 
989 EndFormat:
990     /* This notification is always sent, even in case of error or abort */
991     FmtInfo.ErrorStatus = Status;
992     FsVolCallback(Context,
993                   FSVOLNOTIFY_ENDFORMAT,
994                   (ULONG_PTR)&FmtInfo,
995                   0);
996     return Result;
997 }
998 
999 static FSVOL_OP
DoChecking(_In_ PVOLENTRY Volume,_In_opt_ PVOID Context,_In_opt_ PFSVOL_CALLBACK FsVolCallback)1000 DoChecking(
1001     _In_ PVOLENTRY Volume,
1002     _In_opt_ PVOID Context,
1003     _In_opt_ PFSVOL_CALLBACK FsVolCallback)
1004 {
1005     FSVOL_OP Result;
1006     NTSTATUS Status = STATUS_SUCCESS;
1007     CHECK_VOLUME_INFO ChkInfo = {0};
1008 
1009     ASSERT(*Volume->Info.FileSystem);
1010 
1011     ChkInfo.Volume = Volume;
1012 
1013 RetryCheck:
1014     Result = FsVolCallback(Context,
1015                            FSVOLNOTIFY_STARTCHECK,
1016                            (ULONG_PTR)&ChkInfo,
1017                            FSVOL_CHECK);
1018     if (Result != FSVOL_DOIT)
1019         goto EndCheck;
1020 
1021     /* Check the volume */
1022     Status = ChkdskVolume(&Volume->Info,
1023                           ChkInfo.FixErrors,
1024                           ChkInfo.Verbose,
1025                           ChkInfo.CheckOnlyIfDirty,
1026                           ChkInfo.ScanDrive,
1027                           ChkInfo.Callback);
1028 
1029     /* If volume checking succeeded, or if it is not supported
1030      * with the current file system, disable checks on the volume */
1031     if (NT_SUCCESS(Status) || (Status == STATUS_NOT_SUPPORTED))
1032         Volume->NeedsCheck = FALSE;
1033 
1034     if (!NT_SUCCESS(Status))
1035     {
1036         // ChkInfo.NtPathPartition = PathBuffer;
1037         ChkInfo.ErrorStatus = Status;
1038 
1039         Result = FsVolCallback(Context,
1040                                FSVOLNOTIFY_CHECKERROR,
1041                                (ULONG_PTR)&ChkInfo,
1042                                0);
1043         if (Result == FSVOL_RETRY)
1044             goto RetryCheck;
1045         // else if (Result == FSVOL_ABORT || Result == FSVOL_SKIP), stop.
1046 
1047         // Volume->NeedsCheck = FALSE;
1048     }
1049 
1050 EndCheck:
1051     /* This notification is always sent, even in case of error or abort */
1052     ChkInfo.ErrorStatus = Status;
1053     FsVolCallback(Context,
1054                   FSVOLNOTIFY_ENDCHECK,
1055                   (ULONG_PTR)&ChkInfo,
1056                   0);
1057     return Result;
1058 }
1059 
1060 static
1061 PVOLENTRY
GetNextUnformattedVolume(_In_ PPARTLIST List,_In_opt_ PVOLENTRY Volume)1062 GetNextUnformattedVolume(
1063     _In_ PPARTLIST List,
1064     _In_opt_ PVOLENTRY Volume)
1065 {
1066     PLIST_ENTRY Entry;
1067 
1068     for (;;)
1069     {
1070         /* If we have a current volume, get the next one, otherwise get the first */
1071         Entry = (Volume ? &Volume->ListEntry : &List->VolumesList);
1072         Entry = Entry->Flink;
1073 
1074         if (Entry == &List->VolumesList)
1075             return NULL;
1076 
1077         Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry);
1078         if (Volume->New && (Volume->FormatState == Unformatted))
1079         {
1080             /* Found a candidate, return it */
1081             return Volume;
1082         }
1083     }
1084 }
1085 
1086 BOOLEAN
FsVolCommitOpsQueue(_In_ PPARTLIST PartitionList,_In_ PVOLENTRY SystemVolume,_In_ PVOLENTRY InstallVolume,_In_opt_ PFSVOL_CALLBACK FsVolCallback,_In_opt_ PVOID Context)1087 FsVolCommitOpsQueue(
1088     _In_ PPARTLIST PartitionList,
1089     _In_ PVOLENTRY SystemVolume,
1090     _In_ PVOLENTRY InstallVolume,
1091     _In_opt_ PFSVOL_CALLBACK FsVolCallback,
1092     _In_opt_ PVOID Context)
1093 {
1094     BOOLEAN Success = TRUE; // Suppose success originally.
1095     FSVOL_OP Result;
1096     PLIST_ENTRY Entry;
1097     PVOLENTRY Volume;
1098 
1099     /* Machine state for the format step */
1100     typedef enum _FORMATMACHINESTATE
1101     {
1102         Start,
1103         FormatSystemVolume,
1104         FormatInstallVolume,
1105         FormatOtherVolume,
1106         FormatDone
1107     } FORMATMACHINESTATE;
1108     FORMATMACHINESTATE FormatState, OldFormatState;
1109     static const PCSTR FormatStateNames[] = {
1110         "Start",
1111         "FormatSystemVolume",
1112         "FormatInstallVolume",
1113         "FormatOtherVolume",
1114         "FormatDone"
1115     };
1116 
1117     ASSERT(PartitionList && SystemVolume && InstallVolume);
1118 
1119     /* Commit all partition changes to all the disks */
1120     if (!WritePartitionsToDisk(PartitionList))
1121     {
1122         DPRINT("WritePartitionsToDisk() failed\n");
1123         /* Result = */ FsVolCallback(Context,
1124                             FSVOLNOTIFY_PARTITIONERROR,
1125                             STATUS_PARTITION_FAILURE, // FIXME
1126                             0);
1127         return FALSE;
1128     }
1129 
1130 //
1131 // FIXME: Should we do the following here, or in the caller?
1132 //
1133     /*
1134      * In all cases, whether or not we are going to perform a formatting,
1135      * we must perform a file system check of both the system and the
1136      * installation volumes.
1137      */
1138     SystemVolume->NeedsCheck = TRUE;
1139     InstallVolume->NeedsCheck = TRUE;
1140 
1141     Result = FsVolCallback(Context,
1142                            FSVOLNOTIFY_STARTQUEUE,
1143                            0, 0);
1144     if (Result == FSVOL_ABORT)
1145         return FALSE;
1146 
1147     /*
1148      * Commit the Format queue
1149      */
1150 
1151     Result = FsVolCallback(Context,
1152                            FSVOLNOTIFY_STARTSUBQUEUE,
1153                            FSVOL_FORMAT,
1154                            0);
1155     if (Result == FSVOL_ABORT)
1156         return FALSE;
1157     /** HACK!! **/
1158     if (Result == FSVOL_SKIP)
1159         goto StartCheckQueue;
1160     /** END HACK!! **/
1161 
1162     /* Reset the formatter machine state */
1163     FormatState = Start;
1164     Volume = NULL;
1165 NextFormat:
1166     OldFormatState = FormatState;
1167     switch (FormatState)
1168     {
1169         case Start:
1170         {
1171             /*
1172              * We start by formatting the system volume in case it is new
1173              * (it didn't exist before) and is not the same as the installation
1174              * volume. Otherwise we just require a file system check on it,
1175              * and start by formatting the installation volume instead.
1176              */
1177             if (SystemVolume != InstallVolume)
1178             {
1179                 Volume = SystemVolume;
1180 
1181                 if (Volume->FormatState == Unformatted)
1182                 {
1183                     // TODO: Should we let the user use a custom file system,
1184                     // or should we always use FAT(32) for it?
1185                     // For "compatibility", FAT(32) would be best indeed.
1186 
1187                     FormatState = FormatSystemVolume;
1188                     DPRINT1("FormatState: %s --> %s\n",
1189                             FormatStateNames[OldFormatState], FormatStateNames[FormatState]);
1190                     break;
1191                 }
1192 
1193                 /* The system volume is separate, so it had better be formatted! */
1194                 ASSERT(Volume->FormatState == Formatted);
1195 
1196                 /* Require a file system check on the system volume too */
1197                 Volume->NeedsCheck = TRUE;
1198             }
1199             __fallthrough;
1200         }
1201 
1202         case FormatSystemVolume:
1203         {
1204             Volume = InstallVolume;
1205 
1206             FormatState = FormatInstallVolume;
1207             DPRINT1("FormatState: %s --> %s\n",
1208                     FormatStateNames[OldFormatState], FormatStateNames[FormatState]);
1209             break;
1210         }
1211 
1212         case FormatInstallVolume:
1213             /* Restart volume enumeration */
1214             Volume = NULL;
1215         case FormatOtherVolume:
1216         {
1217             Volume = GetNextUnformattedVolume(PartitionList, Volume);
1218 
1219             FormatState = (Volume ? FormatOtherVolume : FormatDone);
1220             DPRINT1("FormatState: %s --> %s\n",
1221                     FormatStateNames[OldFormatState], FormatStateNames[FormatState]);
1222             if (Volume)
1223                 break;
1224             __fallthrough;
1225         }
1226 
1227         case FormatDone:
1228         {
1229             DPRINT1("FormatState: FormatDone\n");
1230             Success = TRUE;
1231             goto EndFormat;
1232         }
1233         DEFAULT_UNREACHABLE;
1234     }
1235 
1236     Result = DoFormatting(Volume, Context, FsVolCallback);
1237     if (Result == FSVOL_ABORT)
1238     {
1239         Success = FALSE;
1240         goto Quit;
1241     }
1242     /* Schedule a check for this volume */
1243     Volume->NeedsCheck = TRUE;
1244     /* Go to the next volume to be formatted */
1245     goto NextFormat;
1246 
1247 EndFormat:
1248     FsVolCallback(Context,
1249                   FSVOLNOTIFY_ENDSUBQUEUE,
1250                   FSVOL_FORMAT,
1251                   0);
1252 
1253 
1254     /*
1255      * Commit the CheckFS queue
1256      */
1257 
1258 StartCheckQueue:
1259     Result = FsVolCallback(Context,
1260                            FSVOLNOTIFY_STARTSUBQUEUE,
1261                            FSVOL_CHECK,
1262                            0);
1263     if (Result == FSVOL_ABORT)
1264         return FALSE;
1265 
1266     /* Loop through each unchecked volume and do the check */
1267     for (Entry = PartitionList->VolumesList.Flink;
1268          Entry != &PartitionList->VolumesList;
1269          Entry = Entry->Flink)
1270     {
1271         Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry);
1272         if (!Volume->NeedsCheck)
1273             continue;
1274 
1275         /* Found a candidate */
1276         ASSERT(Volume->FormatState == Formatted);
1277         Result = DoChecking(Volume, Context, FsVolCallback);
1278         if (Result == FSVOL_ABORT)
1279         {
1280             Success = FALSE;
1281             goto Quit;
1282         }
1283         /* Go to the next volume to be checked */
1284     }
1285     Success = TRUE;
1286 
1287     FsVolCallback(Context,
1288                   FSVOLNOTIFY_ENDSUBQUEUE,
1289                   FSVOL_CHECK,
1290                   0);
1291 
1292 
1293 Quit:
1294     /* All the queues have been committed */
1295     FsVolCallback(Context,
1296                   FSVOLNOTIFY_ENDQUEUE,
1297                   Success,
1298                   0);
1299     return Success;
1300 }
1301 
1302 /* EOF */
1303