[VFATLIB] Make Chkdsk handle volume opening locking failures.
[reactos.git] / sdk / lib / fslib / vfatlib / vfatlib.c
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
432 read_boot(&fs);
433
434 if (verify)
435 VfatPrint("Starting check/repair pass.\n");
436
437 while (read_fat(&fs), scan_root(&fs))
438 qfree(&FsCheckMemQueue);
439
440 if (ScanDrive)
441 fix_bad(&fs);
442
443 if (salvage_files)
444 reclaim_file(&fs);
445 else
446 reclaim_free(&fs);
447
448 free_clusters = update_free(&fs);
449 file_unused();
450 qfree(&FsCheckMemQueue);
451 if (verify)
452 {
453 FsCheckTotalFiles = 0;
454 VfatPrint("Starting verification pass.\n");
455 read_fat(&fs);
456 scan_root(&fs);
457 reclaim_free(&fs);
458 qfree(&FsCheckMemQueue);
459 }
460
461 if (fs_changed())
462 {
463 if (FsCheckFlags & FSCHECK_READ_WRITE)
464 {
465 if (FsCheckFlags & FSCHECK_INTERACTIVE)
466 {
467 FixErrors = get_key("yn", "Perform changes ? (y/n)") == 'y';
468 if (FixErrors)
469 FsCheckFlags |= FSCHECK_READ_WRITE;
470 else
471 FsCheckFlags &= ~FSCHECK_READ_WRITE;
472 }
473 else
474 VfatPrint("Performing changes.\n");
475 }
476 else
477 {
478 VfatPrint("Leaving filesystem unchanged.\n");
479 }
480 }
481
482 VfatPrint("%wZ: %u files, %lu/%lu clusters\n", DriveRoot,
483 FsCheckTotalFiles, fs.data_clusters - free_clusters, fs.data_clusters);
484
485 if (FsCheckFlags & FSCHECK_READ_WRITE)
486 {
487 /* Dismount the volume */
488 fs_dismount();
489
490 /* Unlock the volume */
491 fs_lock(FALSE);
492 }
493
494 // https://technet.microsoft.com/en-us/library/cc730714.aspx
495 // https://support.microsoft.com/en-us/kb/265533
496
497 /* Close the volume */
498 return (fs_close(FsCheckFlags & FSCHECK_READ_WRITE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
499 }
500
501 /* EOF */