xref: /reactos/sdk/lib/fslib/vfatlib/vfatlib.c (revision 34593d93)
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS VFAT filesystem library
4  * FILE:        lib\fslib\vfatlib\vfatlib.c
5  * PURPOSE:     Main API
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  *              Aleksey Bragin (aleksey@reactos.org)
8  *              Hermes Belusca-Maito (hermes.belusca@sfr.fr)
9  */
10 /* fsck.fat.c - User interface
11 
12    Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
13    Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
14    Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
15 
16    This program is free software: you can redistribute it and/or modify
17    it under the terms of the GNU General Public License as published by
18    the Free Software Foundation, either version 3 of the License, or
19    (at your option) any later version.
20 
21    This program is distributed in the hope that it will be useful,
22    but WITHOUT ANY WARRANTY; without even the implied warranty of
23    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24    GNU General Public License for more details.
25 
26    You should have received a copy of the GNU General Public License
27    along with this program. If not, see <http://www.gnu.org/licenses/>.
28 
29    The complete text of the GNU General Public License
30    can be found in /usr/share/common-licenses/GPL-3 file.
31 */
32 
33 /* INCLUDES *******************************************************************/
34 
35 #include "vfatlib.h"
36 
37 #define NDEBUG
38 #include <debug.h>
39 
40 
41 /* GLOBALS & FUNCTIONS ********************************************************/
42 
43 PFMIFSCALLBACK ChkdskCallback = NULL;
44 ULONG FsCheckFlags;
45 PVOID FsCheckMemQueue;
46 ULONG FsCheckTotalFiles;
47 
48 BOOLEAN
49 NTAPI
VfatFormat(IN PUNICODE_STRING DriveRoot,IN PFMIFSCALLBACK Callback,IN BOOLEAN QuickFormat,IN BOOLEAN BackwardCompatible,IN MEDIA_TYPE MediaType,IN PUNICODE_STRING Label,IN ULONG ClusterSize)50 VfatFormat(
51     IN PUNICODE_STRING DriveRoot,
52     IN PFMIFSCALLBACK Callback,
53     IN BOOLEAN QuickFormat,
54     IN BOOLEAN BackwardCompatible,
55     IN MEDIA_TYPE MediaType,
56     IN PUNICODE_STRING Label,
57     IN ULONG ClusterSize)
58 {
59     OBJECT_ATTRIBUTES ObjectAttributes;
60     DISK_GEOMETRY DiskGeometry;
61     IO_STATUS_BLOCK Iosb;
62     HANDLE FileHandle;
63     PARTITION_INFORMATION PartitionInfo;
64     FORMAT_CONTEXT Context;
65     NTSTATUS Status, LockStatus;
66 
67     DPRINT("VfatFormat(DriveRoot '%wZ')\n", DriveRoot);
68 
69     // FIXME:
70     UNREFERENCED_PARAMETER(BackwardCompatible);
71     UNREFERENCED_PARAMETER(MediaType);
72 
73     Context.TotalSectorCount = 0;
74     Context.CurrentSectorCount = 0;
75     Context.Callback = Callback;
76     Context.Success = FALSE;
77     Context.Percent = 0;
78 
79     InitializeObjectAttributes(&ObjectAttributes,
80                                DriveRoot,
81                                0,
82                                NULL,
83                                NULL);
84 
85     Status = NtOpenFile(&FileHandle,
86                         FILE_GENERIC_READ | FILE_GENERIC_WRITE | SYNCHRONIZE,
87                         &ObjectAttributes,
88                         &Iosb,
89                         FILE_SHARE_READ,
90                         FILE_SYNCHRONOUS_IO_ALERT);
91     if (!NT_SUCCESS(Status))
92     {
93         DPRINT1("NtOpenFile() failed with status 0x%08x\n", Status);
94         return FALSE;
95     }
96 
97     Status = NtDeviceIoControlFile(FileHandle,
98                                    NULL,
99                                    NULL,
100                                    NULL,
101                                    &Iosb,
102                                    IOCTL_DISK_GET_DRIVE_GEOMETRY,
103                                    NULL,
104                                    0,
105                                    &DiskGeometry,
106                                    sizeof(DISK_GEOMETRY));
107     if (!NT_SUCCESS(Status))
108     {
109         DPRINT("IOCTL_DISK_GET_DRIVE_GEOMETRY failed with status 0x%08x\n", Status);
110         NtClose(FileHandle);
111         return FALSE;
112     }
113 
114     if (DiskGeometry.MediaType == FixedMedia)
115     {
116         DPRINT("Cylinders %I64d\n", DiskGeometry.Cylinders.QuadPart);
117         DPRINT("TracksPerCylinder %ld\n", DiskGeometry.TracksPerCylinder);
118         DPRINT("SectorsPerTrack %ld\n", DiskGeometry.SectorsPerTrack);
119         DPRINT("BytesPerSector %ld\n", DiskGeometry.BytesPerSector);
120         DPRINT("DiskSize %I64d\n",
121                DiskGeometry.Cylinders.QuadPart *
122                (ULONGLONG)DiskGeometry.TracksPerCylinder *
123                (ULONGLONG)DiskGeometry.SectorsPerTrack *
124                (ULONGLONG)DiskGeometry.BytesPerSector);
125 
126         Status = NtDeviceIoControlFile(FileHandle,
127                                        NULL,
128                                        NULL,
129                                        NULL,
130                                        &Iosb,
131                                        IOCTL_DISK_GET_PARTITION_INFO,
132                                        NULL,
133                                        0,
134                                        &PartitionInfo,
135                                        sizeof(PARTITION_INFORMATION));
136         if (!NT_SUCCESS(Status))
137         {
138             DPRINT("IOCTL_DISK_GET_PARTITION_INFO failed with status 0x%08x\n", Status);
139             NtClose(FileHandle);
140             return FALSE;
141         }
142     }
143     else
144     {
145         PartitionInfo.PartitionType = 0;
146         PartitionInfo.StartingOffset.QuadPart = 0ULL;
147         PartitionInfo.PartitionLength.QuadPart =
148             DiskGeometry.Cylinders.QuadPart *
149             (ULONGLONG)DiskGeometry.TracksPerCylinder *
150             (ULONGLONG)DiskGeometry.SectorsPerTrack *
151             (ULONGLONG)DiskGeometry.BytesPerSector;
152         PartitionInfo.HiddenSectors = 0;
153         PartitionInfo.PartitionNumber = 0;
154         PartitionInfo.BootIndicator = FALSE;
155         PartitionInfo.RewritePartition = FALSE;
156         PartitionInfo.RecognizedPartition = FALSE;
157     }
158 
159     /* If it already has a FAT FS, we'll use that type.
160      * If it doesn't, we will determine the FAT type based on size and offset */
161     if (PartitionInfo.PartitionType != PARTITION_FAT_12 &&
162         PartitionInfo.PartitionType != PARTITION_FAT_16 &&
163         PartitionInfo.PartitionType != PARTITION_HUGE &&
164         PartitionInfo.PartitionType != PARTITION_XINT13 &&
165         PartitionInfo.PartitionType != PARTITION_FAT32 &&
166         PartitionInfo.PartitionType != PARTITION_FAT32_XINT13)
167     {
168         /* Determine the correct type based upon size and offset (copied from usetup) */
169         if (PartitionInfo.PartitionLength.QuadPart < (4200LL * 1024LL))
170         {
171             /* FAT12 CHS partition (disk is smaller than 4.1MB) */
172             PartitionInfo.PartitionType = PARTITION_FAT_12;
173         }
174         else if (PartitionInfo.StartingOffset.QuadPart < (1024LL * 255LL * 63LL * 512LL))
175         {
176             /* Partition starts below the 8.4GB boundary ==> CHS partition */
177 
178             if (PartitionInfo.PartitionLength.QuadPart < (32LL * 1024LL * 1024LL))
179             {
180                 /* FAT16 CHS partition (partition size < 32MB) */
181                 PartitionInfo.PartitionType = PARTITION_FAT_16;
182             }
183             else if (PartitionInfo.PartitionLength.QuadPart < (512LL * 1024LL * 1024LL))
184             {
185                 /* FAT16 CHS partition (partition size < 512MB) */
186                 PartitionInfo.PartitionType = PARTITION_HUGE;
187             }
188             else
189             {
190                 /* FAT32 CHS partition (partition size >= 512MB) */
191                 PartitionInfo.PartitionType = PARTITION_FAT32;
192             }
193         }
194         else
195         {
196             /* Partition starts above the 8.4GB boundary ==> LBA partition */
197 
198             if (PartitionInfo.PartitionLength.QuadPart < (512LL * 1024LL * 1024LL))
199             {
200                 /* FAT16 LBA partition (partition size < 512MB) */
201                 PartitionInfo.PartitionType = PARTITION_XINT13;
202             }
203             else
204             {
205                 /* FAT32 LBA partition (partition size >= 512MB) */
206                 PartitionInfo.PartitionType = PARTITION_FAT32_XINT13;
207             }
208         }
209     }
210 
211     DPRINT("PartitionType 0x%x\n", PartitionInfo.PartitionType);
212     DPRINT("StartingOffset %I64d\n", PartitionInfo.StartingOffset.QuadPart);
213     DPRINT("PartitionLength %I64d\n", PartitionInfo.PartitionLength.QuadPart);
214     DPRINT("HiddenSectors %lu\n", PartitionInfo.HiddenSectors);
215     DPRINT("PartitionNumber %d\n", PartitionInfo.PartitionNumber);
216     DPRINT("BootIndicator 0x%x\n", PartitionInfo.BootIndicator);
217     DPRINT("RewritePartition %d\n", PartitionInfo.RewritePartition);
218     DPRINT("RecognizedPartition %d\n", PartitionInfo.RecognizedPartition);
219 
220     if (Callback != NULL)
221     {
222         Context.Percent = 0;
223         Callback(PROGRESS, 0, (PVOID)&Context.Percent);
224     }
225 
226     LockStatus = NtFsControlFile(FileHandle,
227                                  NULL,
228                                  NULL,
229                                  NULL,
230                                  &Iosb,
231                                  FSCTL_LOCK_VOLUME,
232                                  NULL,
233                                  0,
234                                  NULL,
235                                  0);
236     if (!NT_SUCCESS(LockStatus))
237     {
238         DPRINT1("WARNING: Failed to lock volume for formatting! Format may fail! (Status: 0x%x)\n", LockStatus);
239     }
240 
241     if (PartitionInfo.PartitionType == PARTITION_FAT_12)
242     {
243         /* FAT12 */
244         Status = Fat12Format(FileHandle,
245                              &PartitionInfo,
246                              &DiskGeometry,
247                              Label,
248                              QuickFormat,
249                              ClusterSize,
250                              &Context);
251     }
252     else if (PartitionInfo.PartitionType == PARTITION_FAT_16 ||
253              PartitionInfo.PartitionType == PARTITION_HUGE ||
254              PartitionInfo.PartitionType == PARTITION_XINT13)
255     {
256         /* FAT16 */
257         Status = Fat16Format(FileHandle,
258                              &PartitionInfo,
259                              &DiskGeometry,
260                              Label,
261                              QuickFormat,
262                              ClusterSize,
263                              &Context);
264     }
265     else if (PartitionInfo.PartitionType == PARTITION_FAT32 ||
266              PartitionInfo.PartitionType == PARTITION_FAT32_XINT13)
267     {
268         /* FAT32 */
269         Status = Fat32Format(FileHandle,
270                              &PartitionInfo,
271                              &DiskGeometry,
272                              Label,
273                              QuickFormat,
274                              ClusterSize,
275                              &Context);
276     }
277     else
278     {
279         Status = STATUS_INVALID_PARAMETER;
280     }
281 
282     /* Attempt to dismount formatted volume */
283     LockStatus = NtFsControlFile(FileHandle,
284                                  NULL,
285                                  NULL,
286                                  NULL,
287                                  &Iosb,
288                                  FSCTL_DISMOUNT_VOLUME,
289                                  NULL,
290                                  0,
291                                  NULL,
292                                  0);
293     if (!NT_SUCCESS(LockStatus))
294     {
295         DPRINT1("Failed to umount volume (Status: 0x%x)\n", LockStatus);
296     }
297 
298     LockStatus = NtFsControlFile(FileHandle,
299                                  NULL,
300                                  NULL,
301                                  NULL,
302                                  &Iosb,
303                                  FSCTL_UNLOCK_VOLUME,
304                                  NULL,
305                                  0,
306                                  NULL,
307                                  0);
308     if (!NT_SUCCESS(LockStatus))
309     {
310         DPRINT1("Failed to unlock volume (Status: 0x%x)\n", LockStatus);
311     }
312 
313     NtClose(FileHandle);
314 
315     DPRINT("VfatFormat() done. Status 0x%08x\n", Status);
316     return NT_SUCCESS(Status);
317 }
318 
319 
320 VOID
UpdateProgress(PFORMAT_CONTEXT Context,ULONG Increment)321 UpdateProgress(PFORMAT_CONTEXT Context,
322                ULONG Increment)
323 {
324     ULONG NewPercent;
325 
326     Context->CurrentSectorCount += (ULONGLONG)Increment;
327 
328     NewPercent = (Context->CurrentSectorCount * 100ULL) / Context->TotalSectorCount;
329 
330     if (NewPercent > Context->Percent)
331     {
332         Context->Percent = NewPercent;
333         if (Context->Callback != NULL)
334         {
335             Context->Callback(PROGRESS, 0, &Context->Percent);
336         }
337     }
338 }
339 
340 
341 VOID
VfatPrintV(PCHAR Format,va_list args)342 VfatPrintV(PCHAR Format, va_list args)
343 {
344     TEXTOUTPUT TextOut;
345     CHAR TextBuf[512];
346 
347     _vsnprintf(TextBuf, sizeof(TextBuf), Format, args);
348 
349     /* Prepare parameters */
350     TextOut.Lines = 1;
351     TextOut.Output = TextBuf;
352 
353     DPRINT1("VfatPrint -- %s", TextBuf);
354 
355     /* Do the callback */
356     if (ChkdskCallback)
357         ChkdskCallback(OUTPUT, 0, &TextOut);
358 }
359 
360 
361 VOID
VfatPrint(PCHAR Format,...)362 VfatPrint(PCHAR Format, ...)
363 {
364     va_list args;
365 
366     va_start(args, Format);
367     VfatPrintV(Format, args);
368     va_end(args);
369 }
370 
371 
372 BOOLEAN
373 NTAPI
VfatChkdsk(IN PUNICODE_STRING DriveRoot,IN PFMIFSCALLBACK Callback,IN BOOLEAN FixErrors,IN BOOLEAN Verbose,IN BOOLEAN CheckOnlyIfDirty,IN BOOLEAN ScanDrive,IN PVOID pUnknown1,IN PVOID pUnknown2,IN PVOID pUnknown3,IN PVOID pUnknown4,IN PULONG ExitStatus)374 VfatChkdsk(
375     IN PUNICODE_STRING DriveRoot,
376     IN PFMIFSCALLBACK Callback,
377     IN BOOLEAN FixErrors,
378     IN BOOLEAN Verbose,
379     IN BOOLEAN CheckOnlyIfDirty,
380     IN BOOLEAN ScanDrive,
381     IN PVOID pUnknown1,
382     IN PVOID pUnknown2,
383     IN PVOID pUnknown3,
384     IN PVOID pUnknown4,
385     IN PULONG ExitStatus)
386 {
387     BOOLEAN verify;
388     BOOLEAN salvage_files;
389     ULONG free_clusters;
390     DOS_FS fs;
391     NTSTATUS Status;
392 
393     UNREFERENCED_PARAMETER(pUnknown1);
394     UNREFERENCED_PARAMETER(pUnknown2);
395     UNREFERENCED_PARAMETER(pUnknown3);
396     UNREFERENCED_PARAMETER(pUnknown4);
397 
398     RtlZeroMemory(&fs, sizeof(fs));
399 
400     /* Store callback pointer */
401     ChkdskCallback = Callback;
402     FsCheckMemQueue = NULL;
403 
404     /* Set parameters */
405     FsCheckFlags = 0;
406     if (Verbose)
407         FsCheckFlags |= FSCHECK_VERBOSE;
408     if (FixErrors)
409         FsCheckFlags |= FSCHECK_READ_WRITE;
410 
411     FsCheckTotalFiles = 0;
412 
413     verify = TRUE;
414     salvage_files = TRUE;
415 
416     /* Open filesystem and lock it */
417     Status = fs_open(DriveRoot, FsCheckFlags & FSCHECK_READ_WRITE);
418     if (Status == STATUS_ACCESS_DENIED)
419     {
420         /* We failed to lock, ask the caller whether we should continue */
421         if (Callback(VOLUMEINUSE, 0, NULL))
422         {
423             Status = STATUS_SUCCESS;
424         }
425     }
426     if (!NT_SUCCESS(Status))
427     {
428         fs_close(FALSE);
429         *ExitStatus = (ULONG)STATUS_DISK_CORRUPT_ERROR;
430         return FALSE;
431     }
432 
433     if (CheckOnlyIfDirty && !fs_isdirty())
434     {
435         /* Unlock volume if required */
436         if (FsCheckFlags & FSCHECK_READ_WRITE)
437             fs_lock(FALSE);
438 
439         /* No need to check FS */
440         Status = (fs_close(FALSE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
441         *ExitStatus = (ULONG)Status;
442         return (Status == STATUS_SUCCESS);
443     }
444     else if (CheckOnlyIfDirty && fs_isdirty())
445     {
446         if (!(FsCheckFlags & FSCHECK_READ_WRITE) && !(FsCheckFlags & FSCHECK_INTERACTIVE))
447         {
448             fs_close(FALSE);
449             *ExitStatus = (ULONG)STATUS_DISK_CORRUPT_ERROR;
450             return FALSE;
451         }
452     }
453 
454     read_boot(&fs);
455 
456     if (verify)
457         VfatPrint("Starting check/repair pass.\n");
458 
459     while (read_fat(&fs), scan_root(&fs))
460         qfree(&FsCheckMemQueue);
461 
462     if (ScanDrive)
463         fix_bad(&fs);
464 
465     if (salvage_files)
466         reclaim_file(&fs);
467     else
468         reclaim_free(&fs);
469 
470     free_clusters = update_free(&fs);
471     file_unused();
472     qfree(&FsCheckMemQueue);
473     if (verify)
474     {
475         FsCheckTotalFiles = 0;
476         VfatPrint("Starting verification pass.\n");
477         read_fat(&fs);
478         scan_root(&fs);
479         reclaim_free(&fs);
480         qfree(&FsCheckMemQueue);
481     }
482 
483     if (fs_changed())
484     {
485         if (FsCheckFlags & FSCHECK_READ_WRITE)
486         {
487             if (FsCheckFlags & FSCHECK_INTERACTIVE)
488             {
489                 FixErrors = get_key("yn", "Perform changes ? (y/n)") == 'y';
490                 if (FixErrors)
491                     FsCheckFlags |= FSCHECK_READ_WRITE;
492                 else
493                     FsCheckFlags &= ~FSCHECK_READ_WRITE;
494             }
495             else
496                 VfatPrint("Performing changes.\n");
497         }
498         else
499         {
500             VfatPrint("Leaving filesystem unchanged.\n");
501         }
502     }
503 
504     VfatPrint("%wZ: %u files, %lu/%lu clusters\n", DriveRoot,
505         FsCheckTotalFiles, fs.data_clusters - free_clusters, fs.data_clusters);
506 
507     if (FsCheckFlags & FSCHECK_READ_WRITE)
508     {
509         /* Dismount the volume */
510         fs_dismount();
511 
512         /* Unlock the volume */
513         fs_lock(FALSE);
514     }
515 
516     // https://technet.microsoft.com/en-us/library/cc730714.aspx
517     // https://support.microsoft.com/en-us/kb/265533
518 
519     /* Close the volume */
520     Status = (fs_close(FsCheckFlags & FSCHECK_READ_WRITE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
521     *ExitStatus = (ULONG)Status;
522     return (Status == STATUS_SUCCESS);
523 }
524 
525 /* EOF */
526