[AUTOCHK] Let the timeout for disk repair to be configured
[reactos.git] / base / system / autochk / autochk.c
1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: base/system/autochk/autochk.c
5 * PURPOSE: Filesystem checker
6 * PROGRAMMERS: Aleksey Bragin
7 * Eric Kohl
8 * Hervé Poussineau
9 * Pierre Schweitzer
10 */
11
12 /* INCLUDES *****************************************************************/
13
14 #include <stdio.h>
15 #define WIN32_NO_STATUS
16 #include <windef.h>
17 #include <winbase.h>
18 #include <ntddkbd.h>
19 #define NTOS_MODE_USER
20 #include <ndk/exfuncs.h>
21 #include <ndk/iofuncs.h>
22 #include <ndk/obfuncs.h>
23 #include <ndk/psfuncs.h>
24 #include <ndk/rtlfuncs.h>
25 #include <ndk/umfuncs.h>
26 #include <fmifs/fmifs.h>
27
28 #include <fslib/vfatlib.h>
29 #include <fslib/ext2lib.h>
30 #include <fslib/ntfslib.h>
31 #include <fslib/cdfslib.h>
32 #include <fslib/btrfslib.h>
33 #include <fslib/ffslib.h>
34 #include <fslib/reiserfslib.h>
35
36 #define NDEBUG
37 #include <debug.h>
38
39 /* DEFINES ******************************************************************/
40
41 #define FS_ATTRIBUTE_BUFFER_SIZE (MAX_PATH * sizeof(WCHAR) + sizeof(FILE_FS_ATTRIBUTE_INFORMATION))
42
43 typedef struct _FILESYSTEM_CHKDSK
44 {
45 WCHAR Name[10];
46 CHKDSKEX ChkdskFunc;
47 } FILESYSTEM_CHKDSK, *PFILESYSTEM_CHKDSK;
48
49 FILESYSTEM_CHKDSK FileSystems[10] =
50 {
51 { L"FAT", VfatChkdsk },
52 { L"FAT32", VfatChkdsk },
53 { L"NTFS", NtfsChkdsk },
54 { L"EXT2", Ext2Chkdsk },
55 { L"EXT3", Ext2Chkdsk },
56 { L"EXT4", Ext2Chkdsk },
57 { L"Btrfs", BtrfsChkdskEx },
58 { L"RFSD", ReiserfsChkdsk },
59 { L"FFS", FfsChkdsk },
60 { L"CDFS", CdfsChkdsk },
61 };
62
63 HANDLE KeyboardHandle;
64
65 /* FUNCTIONS ****************************************************************/
66 //
67 // FMIFS function
68 //
69
70 static VOID
71 PrintString(char* fmt,...)
72 {
73 char buffer[512];
74 va_list ap;
75 UNICODE_STRING UnicodeString;
76 ANSI_STRING AnsiString;
77
78 va_start(ap, fmt);
79 vsprintf(buffer, fmt, ap);
80 va_end(ap);
81
82 RtlInitAnsiString(&AnsiString, buffer);
83 RtlAnsiStringToUnicodeString(&UnicodeString,
84 &AnsiString,
85 TRUE);
86 NtDisplayString(&UnicodeString);
87 RtlFreeUnicodeString(&UnicodeString);
88 }
89
90 // this func is taken from kernel32/file/volume.c
91 static HANDLE
92 OpenDirectory(
93 IN LPCWSTR DirName,
94 IN BOOLEAN Write)
95 {
96 UNICODE_STRING NtPathU;
97 OBJECT_ATTRIBUTES ObjectAttributes;
98 NTSTATUS Status;
99 IO_STATUS_BLOCK IoStatusBlock;
100 HANDLE hFile;
101
102 if (!RtlDosPathNameToNtPathName_U(DirName,
103 &NtPathU,
104 NULL,
105 NULL))
106 {
107 DPRINT1("Invalid path!\n");
108 return INVALID_HANDLE_VALUE;
109 }
110
111 InitializeObjectAttributes(
112 &ObjectAttributes,
113 &NtPathU,
114 OBJ_CASE_INSENSITIVE,
115 NULL,
116 NULL);
117
118 Status = NtCreateFile(
119 &hFile,
120 Write ? FILE_GENERIC_WRITE : FILE_GENERIC_READ,
121 &ObjectAttributes,
122 &IoStatusBlock,
123 NULL,
124 0,
125 FILE_SHARE_READ|FILE_SHARE_WRITE,
126 FILE_OPEN,
127 0,
128 NULL,
129 0);
130
131 RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathU.Buffer);
132
133 if (!NT_SUCCESS(Status))
134 {
135 return INVALID_HANDLE_VALUE;
136 }
137
138 return hFile;
139 }
140
141 static NTSTATUS
142 OpenKeyboard(VOID)
143 {
144 OBJECT_ATTRIBUTES ObjectAttributes;
145 IO_STATUS_BLOCK IoStatusBlock;
146 UNICODE_STRING KeyboardName = RTL_CONSTANT_STRING(L"\\Device\\KeyboardClass0");
147
148 /* Just open the class driver */
149 InitializeObjectAttributes(&ObjectAttributes,
150 &KeyboardName,
151 0,
152 NULL,
153 NULL);
154 return NtOpenFile(&KeyboardHandle,
155 FILE_ALL_ACCESS,
156 &ObjectAttributes,
157 &IoStatusBlock,
158 FILE_OPEN,
159 0);
160 }
161
162 static NTSTATUS
163 WaitForKeyboard(
164 IN LONG TimeOut)
165 {
166 NTSTATUS Status;
167 IO_STATUS_BLOCK IoStatusBlock;
168 LARGE_INTEGER Offset, Timeout;
169 KEYBOARD_INPUT_DATA InputData;
170
171 /* Attempt to read from the keyboard */
172 Offset.QuadPart = 0;
173 Status = NtReadFile(KeyboardHandle,
174 NULL,
175 NULL,
176 NULL,
177 &IoStatusBlock,
178 &InputData,
179 sizeof(KEYBOARD_INPUT_DATA),
180 &Offset,
181 NULL);
182 if (Status == STATUS_PENDING)
183 {
184 /* Wait TimeOut seconds */
185 Timeout.QuadPart = TimeOut * -10000000;
186 Status = NtWaitForSingleObject(KeyboardHandle, FALSE, &Timeout);
187 /* The user didn't enter anything, cancel the read */
188 if (Status == STATUS_TIMEOUT)
189 {
190 NtCancelIoFile(KeyboardHandle, &IoStatusBlock);
191 }
192 /* Else, return some status */
193 else
194 {
195 Status = IoStatusBlock.Status;
196 }
197 }
198
199 return Status;
200 }
201
202 static NTSTATUS
203 GetFileSystem(
204 IN LPCWSTR Drive,
205 IN OUT LPWSTR FileSystemName,
206 IN SIZE_T FileSystemNameSize)
207 {
208 HANDLE FileHandle;
209 NTSTATUS Status;
210 IO_STATUS_BLOCK IoStatusBlock;
211 PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute;
212 UCHAR Buffer[FS_ATTRIBUTE_BUFFER_SIZE];
213
214 FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer;
215
216 FileHandle = OpenDirectory(Drive, FALSE);
217 if (FileHandle == INVALID_HANDLE_VALUE)
218 return STATUS_INVALID_PARAMETER;
219
220 Status = NtQueryVolumeInformationFile(FileHandle,
221 &IoStatusBlock,
222 FileFsAttribute,
223 FS_ATTRIBUTE_BUFFER_SIZE,
224 FileFsAttributeInformation);
225 NtClose(FileHandle);
226
227 if (NT_SUCCESS(Status))
228 {
229 if (FileSystemNameSize * sizeof(WCHAR) >= FileFsAttribute->FileSystemNameLength + sizeof(WCHAR))
230 {
231 CopyMemory(FileSystemName,
232 FileFsAttribute->FileSystemName,
233 FileFsAttribute->FileSystemNameLength);
234 FileSystemName[FileFsAttribute->FileSystemNameLength / sizeof(WCHAR)] = 0;
235 }
236 else
237 return STATUS_BUFFER_TOO_SMALL;
238 }
239 else
240 return Status;
241
242 return STATUS_SUCCESS;
243 }
244
245 // This is based on SysInternal's ChkDsk app
246 static BOOLEAN NTAPI
247 ChkdskCallback(
248 IN CALLBACKCOMMAND Command,
249 IN ULONG Modifier,
250 IN PVOID Argument)
251 {
252 PDWORD Percent;
253 PBOOLEAN Status;
254 PTEXTOUTPUT Output;
255
256 //
257 // We get other types of commands,
258 // but we don't have to pay attention to them
259 //
260 switch(Command)
261 {
262 case UNKNOWN2:
263 DPRINT("UNKNOWN2\n");
264 break;
265
266 case UNKNOWN3:
267 DPRINT("UNKNOWN3\n");
268 break;
269
270 case UNKNOWN4:
271 DPRINT("UNKNOWN4\n");
272 break;
273
274 case UNKNOWN5:
275 DPRINT("UNKNOWN5\n");
276 break;
277
278 case UNKNOWN9:
279 DPRINT("UNKNOWN9\n");
280 break;
281
282 case UNKNOWNA:
283 DPRINT("UNKNOWNA\n");
284 break;
285
286 case UNKNOWNC:
287 DPRINT("UNKNOWNC\n");
288 break;
289
290 case UNKNOWND:
291 DPRINT("UNKNOWND\n");
292 break;
293
294 case INSUFFICIENTRIGHTS:
295 DPRINT("INSUFFICIENTRIGHTS\n");
296 break;
297
298 case FSNOTSUPPORTED:
299 DPRINT("FSNOTSUPPORTED\n");
300 break;
301
302 case VOLUMEINUSE:
303 DPRINT("VOLUMEINUSE\n");
304 break;
305
306 case STRUCTUREPROGRESS:
307 DPRINT("STRUCTUREPROGRESS\n");
308 break;
309
310 case DONEWITHSTRUCTURE:
311 DPRINT("DONEWITHSTRUCTURE\n");
312 break;
313
314 case CLUSTERSIZETOOSMALL:
315 DPRINT("CLUSTERSIZETOOSMALL\n");
316 break;
317
318 case PROGRESS:
319 Percent = (PDWORD) Argument;
320 PrintString("%d percent completed.\r", *Percent);
321 break;
322
323 case OUTPUT:
324 Output = (PTEXTOUTPUT) Argument;
325 PrintString("%s", Output->Output);
326 break;
327
328 case DONE:
329 Status = (PBOOLEAN)Argument;
330 if (*Status != FALSE)
331 {
332 PrintString("Autochk was unable to complete successfully.\r\n\r\n");
333 // Error = TRUE;
334 }
335 break;
336 }
337 return TRUE;
338 }
339
340 static NTSTATUS
341 CheckVolume(
342 IN PWCHAR DrivePath,
343 IN LONG TimeOut)
344 {
345 WCHAR FileSystem[128];
346 WCHAR NtDrivePath[64];
347 UNICODE_STRING DrivePathU;
348 NTSTATUS Status;
349 DWORD Count;
350
351 PrintString(" Verifying volume %S\r\n", DrivePath);
352
353 /* Get the file system */
354 Status = GetFileSystem(DrivePath,
355 FileSystem,
356 ARRAYSIZE(FileSystem));
357 if (!NT_SUCCESS(Status))
358 {
359 DPRINT1("GetFileSystem() failed with status 0x%08lx\n", Status);
360 PrintString(" Unable to get file system of %S\r\n", DrivePath);
361 return Status;
362 }
363
364 PrintString(" Its filesystem type is %S\r\n", FileSystem);
365
366 /* Call provider */
367 for (Count = 0; Count < sizeof(FileSystems) / sizeof(FileSystems[0]); ++Count)
368 {
369 if (wcscmp(FileSystem, FileSystems[Count].Name) != 0)
370 {
371 continue;
372 }
373
374 swprintf(NtDrivePath, L"\\??\\");
375 wcscat(NtDrivePath, DrivePath);
376 NtDrivePath[wcslen(NtDrivePath)-1] = 0;
377 RtlInitUnicodeString(&DrivePathU, NtDrivePath);
378
379 DPRINT1("AUTOCHK: Checking %wZ\n", &DrivePathU);
380 /* First, check whether the volume is dirty */
381 Status = FileSystems[Count].ChkdskFunc(&DrivePathU,
382 FALSE, // FixErrors
383 TRUE, // Verbose
384 TRUE, // CheckOnlyIfDirty
385 FALSE,// ScanDrive
386 ChkdskCallback);
387 /* It is */
388 if (Status == STATUS_DISK_CORRUPT_ERROR)
389 {
390 NTSTATUS WaitStatus;
391
392 /* Let the user decide whether to repair */
393 PrintString(" %S needs to be checked\r\n", DrivePath);
394 PrintString(" You can skip it, but be advised it is not recommanded\r\n");
395 PrintString(" To skip disk checking press any key in %d second(s)\r\n", TimeOut);
396
397 /* Timeout == fix it! */
398 WaitStatus = WaitForKeyboard(TimeOut);
399 if (WaitStatus == STATUS_TIMEOUT)
400 {
401 Status = FileSystems[Count].ChkdskFunc(&DrivePathU,
402 TRUE, // FixErrors
403 TRUE, // Verbose
404 TRUE, // CheckOnlyIfDirty
405 FALSE,// ScanDrive
406 ChkdskCallback);
407 }
408 else
409 {
410 PrintString(" %S checking has been skipped\r\n", DrivePath);
411 }
412 }
413 break;
414 }
415
416 if (Count == sizeof(FileSystems) / sizeof(FileSystems[0]))
417 {
418 DPRINT1("File system not supported\n");
419 PrintString(" Unable to verify a %S volume\r\n", FileSystem);
420 return STATUS_DLL_NOT_FOUND;
421 }
422
423 return Status;
424 }
425
426 static VOID
427 QueryTimeout(
428 IN OUT PLONG TimeOut)
429 {
430 RTL_QUERY_REGISTRY_TABLE QueryTable[2];
431
432 RtlZeroMemory(QueryTable, sizeof(QueryTable));
433 QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
434 QueryTable[0].Name = L"AutoChkTimeOut";
435 QueryTable[0].EntryContext = TimeOut;
436
437 RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, L"Session Manager", QueryTable, NULL, NULL);
438 /* See: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/autochk */
439 if (*TimeOut > 259200)
440 {
441 *TimeOut = 259200;
442 }
443 else if (*TimeOut < 0)
444 {
445 *TimeOut = 0;
446 }
447 }
448
449 /* Native image's entry point */
450 int
451 _cdecl
452 _main(int argc,
453 char *argv[],
454 char *envp[],
455 int DebugFlag)
456 {
457 PROCESS_DEVICEMAP_INFORMATION DeviceMap;
458 ULONG i;
459 NTSTATUS Status;
460 WCHAR DrivePath[128];
461 LONG TimeOut;
462
463 // Win2003 passes the only param - "*". Probably means to check all drives
464 /*
465 DPRINT("Got %d params\n", argc);
466 for (i=0; i<argc; i++)
467 DPRINT("Param %d: %s\n", i, argv[i]);
468 */
469
470 /* Query timeout */
471 TimeOut = 3;
472 QueryTimeout(&TimeOut);
473
474 /* FIXME: We should probably use here the mount manager to be
475 * able to check volumes which don't have a drive letter.
476 */
477
478 Status = NtQueryInformationProcess(NtCurrentProcess(),
479 ProcessDeviceMap,
480 &DeviceMap.Query,
481 sizeof(DeviceMap.Query),
482 NULL);
483 if (!NT_SUCCESS(Status))
484 {
485 DPRINT1("NtQueryInformationProcess() failed with status 0x%08lx\n",
486 Status);
487 return 1;
488 }
489
490 /* Open keyboard */
491 Status = OpenKeyboard();
492 if (!NT_SUCCESS(Status))
493 {
494 DPRINT1("OpenKeyboard() failed with status 0x%08lx\n", Status);
495 return 1;
496 }
497
498 for (i = 0; i < 26; i++)
499 {
500 if ((DeviceMap.Query.DriveMap & (1 << i))
501 && (DeviceMap.Query.DriveType[i] == DOSDEVICE_DRIVE_FIXED))
502 {
503 swprintf(DrivePath, L"%c:\\", L'A'+i);
504 CheckVolume(DrivePath, TimeOut);
505 }
506 }
507
508 /* Close keyboard */
509 NtClose(KeyboardHandle);
510
511 // PrintString(" Done\r\n\r\n");
512 return 0;
513 }
514
515 /* EOF */