xref: /reactos/sdk/lib/fslib/vfatlib/vfatlib.c (revision ebaf247c)
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 NTSTATUS
49 NTAPI
50 VfatFormat(IN PUNICODE_STRING DriveRoot,
51            IN FMIFS_MEDIA_FLAG MediaFlag,
52            IN PUNICODE_STRING Label,
53            IN BOOLEAN QuickFormat,
54            IN ULONG ClusterSize,
55            IN PFMIFSCALLBACK Callback)
56 {
57     OBJECT_ATTRIBUTES ObjectAttributes;
58     DISK_GEOMETRY DiskGeometry;
59     IO_STATUS_BLOCK Iosb;
60     HANDLE FileHandle;
61     PARTITION_INFORMATION PartitionInfo;
62     FORMAT_CONTEXT Context;
63     NTSTATUS Status, LockStatus;
64 
65     DPRINT("VfatFormat(DriveRoot '%wZ')\n", DriveRoot);
66 
67     Context.TotalSectorCount = 0;
68     Context.CurrentSectorCount = 0;
69     Context.Callback = Callback;
70     Context.Success = FALSE;
71     Context.Percent = 0;
72 
73     InitializeObjectAttributes(&ObjectAttributes,
74                                DriveRoot,
75                                0,
76                                NULL,
77                                NULL);
78 
79     Status = NtOpenFile(&FileHandle,
80                         FILE_GENERIC_READ | FILE_GENERIC_WRITE | SYNCHRONIZE,
81                         &ObjectAttributes,
82                         &Iosb,
83                         FILE_SHARE_READ,
84                         FILE_SYNCHRONOUS_IO_ALERT);
85     if (!NT_SUCCESS(Status))
86     {
87         DPRINT1("NtOpenFile() failed with status 0x%.08x\n", Status);
88         return Status;
89     }
90 
91     Status = NtDeviceIoControlFile(FileHandle,
92                                    NULL,
93                                    NULL,
94                                    NULL,
95                                    &Iosb,
96                                    IOCTL_DISK_GET_DRIVE_GEOMETRY,
97                                    NULL,
98                                    0,
99                                    &DiskGeometry,
100                                    sizeof(DISK_GEOMETRY));
101     if (!NT_SUCCESS(Status))
102     {
103         DPRINT("IOCTL_DISK_GET_DRIVE_GEOMETRY failed with status 0x%.08x\n", Status);
104         NtClose(FileHandle);
105         return Status;
106     }
107 
108     if (DiskGeometry.MediaType == FixedMedia)
109     {
110         DPRINT("Cylinders %I64d\n", DiskGeometry.Cylinders.QuadPart);
111         DPRINT("TracksPerCylinder %ld\n", DiskGeometry.TracksPerCylinder);
112         DPRINT("SectorsPerTrack %ld\n", DiskGeometry.SectorsPerTrack);
113         DPRINT("BytesPerSector %ld\n", DiskGeometry.BytesPerSector);
114         DPRINT("DiskSize %I64d\n",
115                DiskGeometry.Cylinders.QuadPart *
116                (ULONGLONG)DiskGeometry.TracksPerCylinder *
117                (ULONGLONG)DiskGeometry.SectorsPerTrack *
118                (ULONGLONG)DiskGeometry.BytesPerSector);
119 
120         Status = NtDeviceIoControlFile(FileHandle,
121                                        NULL,
122                                        NULL,
123                                        NULL,
124                                        &Iosb,
125                                        IOCTL_DISK_GET_PARTITION_INFO,
126                                        NULL,
127                                        0,
128                                        &PartitionInfo,
129                                        sizeof(PARTITION_INFORMATION));
130         if (!NT_SUCCESS(Status))
131         {
132             DPRINT("IOCTL_DISK_GET_PARTITION_INFO failed with status 0x%.08x\n", Status);
133             NtClose(FileHandle);
134             return Status;
135         }
136     }
137     else
138     {
139         PartitionInfo.PartitionType = 0;
140         PartitionInfo.StartingOffset.QuadPart = 0ULL;
141         PartitionInfo.PartitionLength.QuadPart =
142             DiskGeometry.Cylinders.QuadPart *
143             (ULONGLONG)DiskGeometry.TracksPerCylinder *
144             (ULONGLONG)DiskGeometry.SectorsPerTrack *
145             (ULONGLONG)DiskGeometry.BytesPerSector;
146         PartitionInfo.HiddenSectors = 0;
147         PartitionInfo.PartitionNumber = 0;
148         PartitionInfo.BootIndicator = FALSE;
149         PartitionInfo.RewritePartition = FALSE;
150         PartitionInfo.RecognizedPartition = FALSE;
151     }
152 
153     /* If it already has a FAT FS, we'll use that type.
154      * If it doesn't, we will determine the FAT type based on size and offset */
155     if (PartitionInfo.PartitionType != PARTITION_FAT_12 &&
156         PartitionInfo.PartitionType != PARTITION_FAT_16 &&
157         PartitionInfo.PartitionType != PARTITION_HUGE &&
158         PartitionInfo.PartitionType != PARTITION_XINT13 &&
159         PartitionInfo.PartitionType != PARTITION_FAT32 &&
160         PartitionInfo.PartitionType != PARTITION_FAT32_XINT13)
161     {
162         /* Determine the correct type based upon size and offset (copied from usetup) */
163         if (PartitionInfo.PartitionLength.QuadPart < (4200LL * 1024LL))
164         {
165             /* FAT12 CHS partition (disk is smaller than 4.1MB) */
166             PartitionInfo.PartitionType = PARTITION_FAT_12;
167         }
168         else if (PartitionInfo.StartingOffset.QuadPart < (1024LL * 255LL * 63LL * 512LL))
169         {
170             /* Partition starts below the 8.4GB boundary ==> CHS partition */
171 
172             if (PartitionInfo.PartitionLength.QuadPart < (32LL * 1024LL * 1024LL))
173             {
174                 /* FAT16 CHS partition (partition size < 32MB) */
175                 PartitionInfo.PartitionType = PARTITION_FAT_16;
176             }
177             else if (PartitionInfo.PartitionLength.QuadPart < (512LL * 1024LL * 1024LL))
178             {
179                 /* FAT16 CHS partition (partition size < 512MB) */
180                 PartitionInfo.PartitionType = PARTITION_HUGE;
181             }
182             else
183             {
184                 /* FAT32 CHS partition (partition size >= 512MB) */
185                 PartitionInfo.PartitionType = PARTITION_FAT32;
186             }
187         }
188         else
189         {
190             /* Partition starts above the 8.4GB boundary ==> LBA partition */
191 
192             if (PartitionInfo.PartitionLength.QuadPart < (512LL * 1024LL * 1024LL))
193             {
194                 /* FAT16 LBA partition (partition size < 512MB) */
195                 PartitionInfo.PartitionType = PARTITION_XINT13;
196             }
197             else
198             {
199                 /* FAT32 LBA partition (partition size >= 512MB) */
200                 PartitionInfo.PartitionType = PARTITION_FAT32_XINT13;
201             }
202         }
203     }
204 
205     DPRINT("PartitionType 0x%x\n", PartitionInfo.PartitionType);
206     DPRINT("StartingOffset %I64d\n", PartitionInfo.StartingOffset.QuadPart);
207     DPRINT("PartitionLength %I64d\n", PartitionInfo.PartitionLength.QuadPart);
208     DPRINT("HiddenSectors %lu\n", PartitionInfo.HiddenSectors);
209     DPRINT("PartitionNumber %d\n", PartitionInfo.PartitionNumber);
210     DPRINT("BootIndicator 0x%x\n", PartitionInfo.BootIndicator);
211     DPRINT("RewritePartition %d\n", PartitionInfo.RewritePartition);
212     DPRINT("RecognizedPartition %d\n", PartitionInfo.RecognizedPartition);
213 
214     if (Callback != NULL)
215     {
216         Context.Percent = 0;
217         Callback (PROGRESS, 0, (PVOID)&Context.Percent);
218     }
219 
220     LockStatus = NtFsControlFile(FileHandle,
221                                  NULL,
222                                  NULL,
223                                  NULL,
224                                  &Iosb,
225                                  FSCTL_LOCK_VOLUME,
226                                  NULL,
227                                  0,
228                                  NULL,
229                                  0);
230     if (!NT_SUCCESS(LockStatus))
231     {
232         DPRINT1("WARNING: Failed to lock volume for formatting! Format may fail! (Status: 0x%x)\n", LockStatus);
233     }
234 
235     if (PartitionInfo.PartitionType == PARTITION_FAT_12)
236     {
237         /* FAT12 */
238         Status = Fat12Format(FileHandle,
239                              &PartitionInfo,
240                              &DiskGeometry,
241                              Label,
242                              QuickFormat,
243                              ClusterSize,
244                              &Context);
245     }
246     else if (PartitionInfo.PartitionType == PARTITION_FAT_16 ||
247              PartitionInfo.PartitionType == PARTITION_HUGE ||
248              PartitionInfo.PartitionType == PARTITION_XINT13)
249     {
250         /* FAT16 */
251         Status = Fat16Format(FileHandle,
252                              &PartitionInfo,
253                              &DiskGeometry,
254                              Label,
255                              QuickFormat,
256                              ClusterSize,
257                              &Context);
258     }
259     else if (PartitionInfo.PartitionType == PARTITION_FAT32 ||
260              PartitionInfo.PartitionType == PARTITION_FAT32_XINT13)
261     {
262         /* FAT32 */
263         Status = Fat32Format(FileHandle,
264                              &PartitionInfo,
265                              &DiskGeometry,
266                              Label,
267                              QuickFormat,
268                              ClusterSize,
269                              &Context);
270     }
271     else
272     {
273         Status = STATUS_INVALID_PARAMETER;
274     }
275 
276     /* Attempt to dismount formatted volume */
277     LockStatus = NtFsControlFile(FileHandle,
278                                  NULL,
279                                  NULL,
280                                  NULL,
281                                  &Iosb,
282                                  FSCTL_DISMOUNT_VOLUME,
283                                  NULL,
284                                  0,
285                                  NULL,
286                                  0);
287     if (!NT_SUCCESS(LockStatus))
288     {
289         DPRINT1("Failed to umount volume (Status: 0x%x)\n", LockStatus);
290     }
291 
292     LockStatus = NtFsControlFile(FileHandle,
293                                  NULL,
294                                  NULL,
295                                  NULL,
296                                  &Iosb,
297                                  FSCTL_UNLOCK_VOLUME,
298                                  NULL,
299                                  0,
300                                  NULL,
301                                  0);
302     if (!NT_SUCCESS(LockStatus))
303     {
304         DPRINT1("Failed to unlock volume (Status: 0x%x)\n", LockStatus);
305     }
306 
307     NtClose(FileHandle);
308 
309     if (Callback != NULL)
310     {
311         Context.Success = (BOOLEAN)(NT_SUCCESS(Status));
312         Callback(DONE, 0, (PVOID)&Context.Success);
313     }
314 
315     DPRINT("VfatFormat() done. Status 0x%.08x\n", Status);
316 
317     return Status;
318 }
319 
320 
321 VOID
322 UpdateProgress(PFORMAT_CONTEXT Context,
323                ULONG Increment)
324 {
325     ULONG NewPercent;
326 
327     Context->CurrentSectorCount += (ULONGLONG)Increment;
328 
329     NewPercent = (Context->CurrentSectorCount * 100ULL) / Context->TotalSectorCount;
330 
331     if (NewPercent > Context->Percent)
332     {
333         Context->Percent = NewPercent;
334         if (Context->Callback != NULL)
335         {
336             Context->Callback(PROGRESS, 0, &Context->Percent);
337         }
338     }
339 }
340 
341 
342 VOID
343 VfatPrintV(PCHAR Format, va_list args)
344 {
345     TEXTOUTPUT TextOut;
346     CHAR TextBuf[512];
347 
348     _vsnprintf(TextBuf, sizeof(TextBuf), Format, args);
349 
350     /* Prepare parameters */
351     TextOut.Lines = 1;
352     TextOut.Output = TextBuf;
353 
354     DPRINT1("VfatPrint -- %s", TextBuf);
355 
356     /* Do the callback */
357     if (ChkdskCallback)
358         ChkdskCallback(OUTPUT, 0, &TextOut);
359 }
360 
361 
362 VOID
363 VfatPrint(PCHAR Format, ...)
364 {
365     va_list args;
366 
367     va_start(args, Format);
368     VfatPrintV(Format, args);
369     va_end(args);
370 }
371 
372 
373 NTSTATUS
374 NTAPI
375 VfatChkdsk(IN PUNICODE_STRING DriveRoot,
376            IN BOOLEAN FixErrors,
377            IN BOOLEAN Verbose,
378            IN BOOLEAN CheckOnlyIfDirty,
379            IN BOOLEAN ScanDrive,
380            IN PFMIFSCALLBACK Callback)
381 {
382     BOOLEAN verify;
383     BOOLEAN salvage_files;
384     ULONG free_clusters;
385     DOS_FS fs;
386     NTSTATUS Status;
387 
388     RtlZeroMemory(&fs, sizeof(fs));
389 
390     /* Store callback pointer */
391     ChkdskCallback = Callback;
392     FsCheckMemQueue = NULL;
393 
394     /* Set parameters */
395     FsCheckFlags = 0;
396     if (Verbose)
397         FsCheckFlags |= FSCHECK_VERBOSE;
398     if (FixErrors)
399         FsCheckFlags |= FSCHECK_READ_WRITE;
400 
401     FsCheckTotalFiles = 0;
402 
403     verify = TRUE;
404     salvage_files = TRUE;
405 
406     /* Open filesystem and lock it */
407     Status = fs_open(DriveRoot, FsCheckFlags & FSCHECK_READ_WRITE);
408     if (Status == STATUS_ACCESS_DENIED)
409     {
410         /* We failed to lock, ask the caller whether we should continue */
411         if (Callback(VOLUMEINUSE, 0, NULL))
412         {
413             Status = STATUS_SUCCESS;
414         }
415     }
416     if (!NT_SUCCESS(Status))
417     {
418         fs_close(FALSE);
419         return STATUS_DISK_CORRUPT_ERROR;
420     }
421 
422     if (CheckOnlyIfDirty && !fs_isdirty())
423     {
424         /* Unlock volume if required */
425         if (FsCheckFlags & FSCHECK_READ_WRITE)
426             fs_lock(FALSE);
427 
428         /* No need to check FS */
429         return (fs_close(FALSE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
430     }
431     else if (CheckOnlyIfDirty && fs_isdirty())
432     {
433         if (!(FsCheckFlags & FSCHECK_READ_WRITE) && !(FsCheckFlags & FSCHECK_INTERACTIVE))
434         {
435             fs_close(FALSE);
436             return STATUS_DISK_CORRUPT_ERROR;
437         }
438     }
439 
440     read_boot(&fs);
441 
442     if (verify)
443         VfatPrint("Starting check/repair pass.\n");
444 
445     while (read_fat(&fs), scan_root(&fs))
446         qfree(&FsCheckMemQueue);
447 
448     if (ScanDrive)
449         fix_bad(&fs);
450 
451     if (salvage_files)
452         reclaim_file(&fs);
453     else
454         reclaim_free(&fs);
455 
456     free_clusters = update_free(&fs);
457     file_unused();
458     qfree(&FsCheckMemQueue);
459     if (verify)
460     {
461         FsCheckTotalFiles = 0;
462         VfatPrint("Starting verification pass.\n");
463         read_fat(&fs);
464         scan_root(&fs);
465         reclaim_free(&fs);
466         qfree(&FsCheckMemQueue);
467     }
468 
469     if (fs_changed())
470     {
471         if (FsCheckFlags & FSCHECK_READ_WRITE)
472         {
473             if (FsCheckFlags & FSCHECK_INTERACTIVE)
474             {
475                 FixErrors = get_key("yn", "Perform changes ? (y/n)") == 'y';
476                 if (FixErrors)
477                     FsCheckFlags |= FSCHECK_READ_WRITE;
478                 else
479                     FsCheckFlags &= ~FSCHECK_READ_WRITE;
480             }
481             else
482                 VfatPrint("Performing changes.\n");
483         }
484         else
485         {
486             VfatPrint("Leaving filesystem unchanged.\n");
487         }
488     }
489 
490     VfatPrint("%wZ: %u files, %lu/%lu clusters\n", DriveRoot,
491         FsCheckTotalFiles, fs.data_clusters - free_clusters, fs.data_clusters);
492 
493     if (FsCheckFlags & FSCHECK_READ_WRITE)
494     {
495         /* Dismount the volume */
496         fs_dismount();
497 
498         /* Unlock the volume */
499         fs_lock(FALSE);
500     }
501 
502     // https://technet.microsoft.com/en-us/library/cc730714.aspx
503     // https://support.microsoft.com/en-us/kb/265533
504 
505     /* Close the volume */
506     return (fs_close(FsCheckFlags & FSCHECK_READ_WRITE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
507 }
508 
509 /* EOF */
510