1 /* 2 * PROJECT: ReactOS AutoChk 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: FileSystem checker in Native mode. 5 * COPYRIGHT: Copyright 2002-2018 Eric Kohl 6 * Copyright 2006-2018 Aleksey Bragin 7 * Copyright 2006-2018 Hervé Poussineau 8 * Copyright 2008-2018 Pierre Schweitzer 9 * Copyright 2018 Hermes Belusca-Maito 10 */ 11 12 /* INCLUDES *****************************************************************/ 13 14 #include <stdio.h> 15 16 #define WIN32_NO_STATUS 17 #include <windef.h> 18 #include <winbase.h> 19 #include <ntddkbd.h> 20 21 #define NTOS_MODE_USER 22 #include <ndk/exfuncs.h> 23 #include <ndk/iofuncs.h> 24 #include <ndk/obfuncs.h> 25 #include <ndk/psfuncs.h> 26 #include <ndk/rtlfuncs.h> 27 #include <fmifs/fmifs.h> 28 29 #include <fslib/vfatlib.h> 30 #include <fslib/ntfslib.h> 31 #include <fslib/ext2lib.h> 32 #include <fslib/btrfslib.h> 33 #include <fslib/reiserfslib.h> 34 #include <fslib/ffslib.h> 35 #include <fslib/cdfslib.h> 36 37 #define NDEBUG 38 #include <debug.h> 39 40 /* DEFINES ******************************************************************/ 41 42 typedef struct _FILESYSTEM_CHKDSK 43 { 44 WCHAR Name[10]; 45 CHKDSKEX ChkdskFunc; 46 } FILESYSTEM_CHKDSK, *PFILESYSTEM_CHKDSK; 47 48 FILESYSTEM_CHKDSK FileSystems[10] = 49 { 50 { L"FAT", VfatChkdsk }, 51 { L"FAT32", VfatChkdsk }, 52 { L"NTFS", NtfsChkdsk }, 53 { L"EXT2", Ext2Chkdsk }, 54 { L"EXT3", Ext2Chkdsk }, 55 { L"EXT4", Ext2Chkdsk }, 56 { L"Btrfs", BtrfsChkdskEx }, 57 { L"RFSD", ReiserfsChkdsk }, 58 { L"FFS", FfsChkdsk }, 59 { L"CDFS", CdfsChkdsk }, 60 }; 61 62 HANDLE KeyboardHandle = NULL; 63 64 /* FUNCTIONS ****************************************************************/ 65 66 // 67 // FMIFS function 68 // 69 70 static INT 71 PrintString(char* fmt,...) 72 { 73 INT Len; 74 char buffer[512]; 75 va_list ap; 76 UNICODE_STRING UnicodeString; 77 ANSI_STRING AnsiString; 78 79 va_start(ap, fmt); 80 Len = vsprintf(buffer, fmt, ap); 81 va_end(ap); 82 83 RtlInitAnsiString(&AnsiString, buffer); 84 RtlAnsiStringToUnicodeString(&UnicodeString, 85 &AnsiString, 86 TRUE); 87 NtDisplayString(&UnicodeString); 88 RtlFreeUnicodeString(&UnicodeString); 89 90 return Len; 91 } 92 93 static VOID 94 EraseLine( 95 IN INT LineLength) 96 { 97 INT Len; 98 UNICODE_STRING UnicodeString; 99 WCHAR buffer[512]; 100 101 if (LineLength <= 0) 102 return; 103 104 /* Go to the beginning of the line */ 105 RtlInitUnicodeString(&UnicodeString, L"\r"); 106 NtDisplayString(&UnicodeString); 107 108 /* Fill the buffer chunk with spaces */ 109 Len = min(LineLength, ARRAYSIZE(buffer)); 110 while (Len > 0) buffer[--Len] = L' '; 111 112 RtlInitEmptyUnicodeString(&UnicodeString, buffer, sizeof(buffer)); 113 while (LineLength > 0) 114 { 115 /* Display the buffer */ 116 Len = min(LineLength, ARRAYSIZE(buffer)); 117 LineLength -= Len; 118 UnicodeString.Length = Len * sizeof(WCHAR); 119 NtDisplayString(&UnicodeString); 120 } 121 122 /* Go to the beginning of the line */ 123 RtlInitUnicodeString(&UnicodeString, L"\r"); 124 NtDisplayString(&UnicodeString); 125 } 126 127 static NTSTATUS 128 OpenKeyboard( 129 OUT PHANDLE KeyboardHandle) 130 { 131 OBJECT_ATTRIBUTES ObjectAttributes; 132 IO_STATUS_BLOCK IoStatusBlock; 133 UNICODE_STRING KeyboardName = RTL_CONSTANT_STRING(L"\\Device\\KeyboardClass0"); 134 135 /* Just open the class driver */ 136 InitializeObjectAttributes(&ObjectAttributes, 137 &KeyboardName, 138 0, 139 NULL, 140 NULL); 141 return NtOpenFile(KeyboardHandle, 142 FILE_ALL_ACCESS, 143 &ObjectAttributes, 144 &IoStatusBlock, 145 FILE_OPEN, 146 0); 147 } 148 149 static NTSTATUS 150 WaitForKeyboard( 151 IN HANDLE KeyboardHandle, 152 IN LONG TimeOut) 153 { 154 NTSTATUS Status; 155 IO_STATUS_BLOCK IoStatusBlock; 156 LARGE_INTEGER Offset, Timeout; 157 KEYBOARD_INPUT_DATA InputData; 158 INT Len = 0; 159 160 /* Skip the wait if there is no timeout */ 161 if (TimeOut <= 0) 162 return STATUS_TIMEOUT; 163 164 /* Attempt to read a down key-press from the keyboard */ 165 do 166 { 167 Offset.QuadPart = 0; 168 Status = NtReadFile(KeyboardHandle, 169 NULL, 170 NULL, 171 NULL, 172 &IoStatusBlock, 173 &InputData, 174 sizeof(KEYBOARD_INPUT_DATA), 175 &Offset, 176 NULL); 177 if (!NT_SUCCESS(Status)) 178 { 179 /* Something failed, bail out */ 180 return Status; 181 } 182 if ((Status != STATUS_PENDING) && !(InputData.Flags & KEY_BREAK)) 183 { 184 /* We have a down key-press, return */ 185 return Status; 186 } 187 } while (Status != STATUS_PENDING); 188 189 /* Perform the countdown of TimeOut seconds */ 190 Status = STATUS_TIMEOUT; 191 while (TimeOut > 0) 192 { 193 /* 194 * Display the countdown string. 195 * Note that we only do a carriage return to go back to the 196 * beginning of the line to possibly erase it. Note also that 197 * we display a trailing space to erase any last character 198 * when the string length decreases. 199 */ 200 Len = PrintString("Press any key within %d second(s) to cancel and resume startup. \r", TimeOut); 201 202 /* Decrease the countdown */ 203 --TimeOut; 204 205 /* Wait one second for a key press */ 206 Timeout.QuadPart = -10000000; 207 Status = NtWaitForSingleObject(KeyboardHandle, FALSE, &Timeout); 208 if (Status != STATUS_TIMEOUT) 209 break; 210 } 211 212 /* Erase the countdown string */ 213 EraseLine(Len); 214 215 if (Status == STATUS_TIMEOUT) 216 { 217 /* The user did not press any key, cancel the read */ 218 NtCancelIoFile(KeyboardHandle, &IoStatusBlock); 219 } 220 else 221 { 222 /* Otherwise, return some status */ 223 Status = IoStatusBlock.Status; 224 } 225 226 return Status; 227 } 228 229 static NTSTATUS 230 GetFileSystem( 231 IN PUNICODE_STRING VolumePathU, 232 IN OUT PWSTR FileSystemName, 233 IN SIZE_T FileSystemNameSize) 234 { 235 NTSTATUS Status; 236 OBJECT_ATTRIBUTES ObjectAttributes; 237 HANDLE FileHandle; 238 IO_STATUS_BLOCK IoStatusBlock; 239 PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute; 240 UCHAR Buffer[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(WCHAR)]; 241 242 FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer; 243 244 InitializeObjectAttributes(&ObjectAttributes, 245 VolumePathU, 246 OBJ_CASE_INSENSITIVE, 247 NULL, 248 NULL); 249 250 Status = NtCreateFile(&FileHandle, 251 FILE_GENERIC_READ, 252 &ObjectAttributes, 253 &IoStatusBlock, 254 NULL, 255 0, 256 FILE_SHARE_READ | FILE_SHARE_WRITE, 257 FILE_OPEN, 258 0, 259 NULL, 260 0); 261 if (!NT_SUCCESS(Status)) 262 { 263 DPRINT1("Could not open volume '%wZ' to obtain its file system, Status 0x%08lx\n", 264 VolumePathU, Status); 265 return Status; 266 } 267 268 Status = NtQueryVolumeInformationFile(FileHandle, 269 &IoStatusBlock, 270 FileFsAttribute, 271 sizeof(Buffer), 272 FileFsAttributeInformation); 273 NtClose(FileHandle); 274 275 if (!NT_SUCCESS(Status)) 276 { 277 DPRINT1("NtQueryVolumeInformationFile() failed, Status 0x%08lx\n", Status); 278 return Status; 279 } 280 281 if (FileSystemNameSize >= FileFsAttribute->FileSystemNameLength + sizeof(WCHAR)) 282 { 283 RtlCopyMemory(FileSystemName, 284 FileFsAttribute->FileSystemName, 285 FileFsAttribute->FileSystemNameLength); 286 FileSystemName[FileFsAttribute->FileSystemNameLength / sizeof(WCHAR)] = UNICODE_NULL; 287 } 288 else 289 { 290 return STATUS_BUFFER_TOO_SMALL; 291 } 292 293 return STATUS_SUCCESS; 294 } 295 296 /* This is based on SysInternals' ChkDsk application */ 297 static BOOLEAN 298 NTAPI 299 ChkdskCallback( 300 IN CALLBACKCOMMAND Command, 301 IN ULONG Modifier, 302 IN PVOID Argument) 303 { 304 PDWORD Percent; 305 PBOOLEAN Status; 306 PTEXTOUTPUT Output; 307 308 // 309 // We get other types of commands, 310 // but we don't have to pay attention to them 311 // 312 switch(Command) 313 { 314 case UNKNOWN2: 315 DPRINT("UNKNOWN2\n"); 316 break; 317 318 case UNKNOWN3: 319 DPRINT("UNKNOWN3\n"); 320 break; 321 322 case UNKNOWN4: 323 DPRINT("UNKNOWN4\n"); 324 break; 325 326 case UNKNOWN5: 327 DPRINT("UNKNOWN5\n"); 328 break; 329 330 case UNKNOWN9: 331 DPRINT("UNKNOWN9\n"); 332 break; 333 334 case UNKNOWNA: 335 DPRINT("UNKNOWNA\n"); 336 break; 337 338 case UNKNOWNC: 339 DPRINT("UNKNOWNC\n"); 340 break; 341 342 case UNKNOWND: 343 DPRINT("UNKNOWND\n"); 344 break; 345 346 case INSUFFICIENTRIGHTS: 347 DPRINT("INSUFFICIENTRIGHTS\n"); 348 break; 349 350 case FSNOTSUPPORTED: 351 DPRINT("FSNOTSUPPORTED\n"); 352 break; 353 354 case VOLUMEINUSE: 355 DPRINT("VOLUMEINUSE\n"); 356 break; 357 358 case STRUCTUREPROGRESS: 359 DPRINT("STRUCTUREPROGRESS\n"); 360 break; 361 362 case DONEWITHSTRUCTURE: 363 DPRINT("DONEWITHSTRUCTURE\n"); 364 break; 365 366 case CLUSTERSIZETOOSMALL: 367 DPRINT("CLUSTERSIZETOOSMALL\n"); 368 break; 369 370 case PROGRESS: 371 Percent = (PDWORD) Argument; 372 PrintString("%d percent completed.\r", *Percent); 373 break; 374 375 case OUTPUT: 376 Output = (PTEXTOUTPUT) Argument; 377 PrintString("%s", Output->Output); 378 break; 379 380 case DONE: 381 Status = (PBOOLEAN)Argument; 382 if (*Status != FALSE) 383 { 384 PrintString("The file system check was unable to complete successfully.\r\n\r\n"); 385 // Error = TRUE; 386 } 387 break; 388 } 389 return TRUE; 390 } 391 392 static NTSTATUS 393 CheckVolume( 394 IN PCWSTR VolumePath, 395 IN LONG TimeOut, 396 IN BOOLEAN CheckOnlyIfDirty) 397 { 398 NTSTATUS Status; 399 PCWSTR DisplayName; 400 UNICODE_STRING VolumePathU; 401 ULONG Count; 402 WCHAR FileSystem[128]; 403 404 RtlInitUnicodeString(&VolumePathU, VolumePath); 405 406 /* Get a drive string for display purposes only */ 407 if (wcslen(VolumePath) == 6 && 408 VolumePath[0] == L'\\' && 409 VolumePath[1] == L'?' && 410 VolumePath[2] == L'?' && 411 VolumePath[3] == L'\\' && 412 VolumePath[5] == L':') 413 { 414 /* DOS drive */ 415 DisplayName = &VolumePath[4]; 416 } 417 else 418 { 419 DisplayName = VolumePath; 420 } 421 422 DPRINT1("AUTOCHK: Checking %wZ\n", &VolumePathU); 423 PrintString("Verifying the file system on %S\r\n", DisplayName); 424 425 /* Get the file system */ 426 Status = GetFileSystem(&VolumePathU, 427 FileSystem, 428 sizeof(FileSystem)); 429 if (!NT_SUCCESS(Status)) 430 { 431 DPRINT1("GetFileSystem() failed, Status 0x%08lx\n", Status); 432 PrintString(" Unable to detect the file system of volume %S\r\n", DisplayName); 433 goto Quit; 434 } 435 436 PrintString(" The file system type is %S.\r\n\r\n", FileSystem); 437 438 /* Find a suitable file system provider */ 439 for (Count = 0; Count < RTL_NUMBER_OF(FileSystems); ++Count) 440 { 441 if (wcscmp(FileSystem, FileSystems[Count].Name) == 0) 442 break; 443 } 444 if (Count >= RTL_NUMBER_OF(FileSystems)) 445 { 446 DPRINT1("File system %S not supported\n", FileSystem); 447 PrintString(" Unable to verify the volume. The %S file system is not supported.\r\n", FileSystem); 448 Status = STATUS_DLL_NOT_FOUND; 449 goto Quit; 450 } 451 452 /* Check whether the volume is dirty */ 453 Status = FileSystems[Count].ChkdskFunc(&VolumePathU, 454 FALSE, // FixErrors 455 TRUE, // Verbose 456 TRUE, // CheckOnlyIfDirty 457 FALSE, // ScanDrive 458 ChkdskCallback); 459 460 /* Perform the check either when the volume is dirty or a check is forced */ 461 if ((Status == STATUS_DISK_CORRUPT_ERROR) || !CheckOnlyIfDirty) 462 { 463 /* Let the user decide whether to repair */ 464 if (Status == STATUS_DISK_CORRUPT_ERROR) 465 { 466 PrintString("The file system on volume %S needs to be checked for problems.\r\n", DisplayName); 467 PrintString("You may cancel this check, but it is recommended that you continue.\r\n"); 468 } 469 else 470 { 471 PrintString("A volume check has been scheduled.\r\n"); 472 } 473 474 if (!KeyboardHandle || WaitForKeyboard(KeyboardHandle, TimeOut) == STATUS_TIMEOUT) 475 { 476 PrintString("The system will now check the file system.\r\n\r\n"); 477 Status = FileSystems[Count].ChkdskFunc(&VolumePathU, 478 TRUE, // FixErrors 479 TRUE, // Verbose 480 CheckOnlyIfDirty, 481 FALSE, // ScanDrive 482 ChkdskCallback); 483 } 484 else 485 { 486 PrintString("The file system check has been skipped.\r\n"); 487 } 488 } 489 490 Quit: 491 PrintString("\r\n\r\n"); 492 return Status; 493 } 494 495 static VOID 496 QueryTimeout( 497 IN OUT PLONG TimeOut) 498 { 499 RTL_QUERY_REGISTRY_TABLE QueryTable[2]; 500 501 RtlZeroMemory(QueryTable, sizeof(QueryTable)); 502 QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; 503 QueryTable[0].Name = L"AutoChkTimeOut"; 504 QueryTable[0].EntryContext = TimeOut; 505 506 RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, L"Session Manager", QueryTable, NULL, NULL); 507 /* See: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/autochk */ 508 *TimeOut = min(max(*TimeOut, 0), 259200); 509 } 510 511 INT 512 __cdecl 513 _main( 514 IN INT argc, 515 IN PCHAR argv[], 516 IN PCHAR envp[], 517 IN ULONG DebugFlag) 518 { 519 NTSTATUS Status; 520 LONG TimeOut; 521 ULONG i; 522 BOOLEAN CheckAllVolumes = FALSE; 523 BOOLEAN CheckOnlyIfDirty = TRUE; 524 PROCESS_DEVICEMAP_INFORMATION DeviceMap; 525 PCHAR SkipDrives = NULL; 526 ANSI_STRING VolumePathA; 527 UNICODE_STRING VolumePathU; 528 WCHAR VolumePath[128] = L""; 529 530 /* 531 * Parse the command-line: optional command switches, 532 * then the NT volume name (or drive letter) to be analysed. 533 * If "*" is passed this means that we check all the volumes. 534 */ 535 if (argc <= 1) 536 { 537 /* Only one parameter (the program name), bail out */ 538 return 1; 539 } 540 for (i = 1; i < argc; ++i) 541 { 542 if (argv[i][0] == '/' || argv[i][0] == '-') 543 { 544 DPRINT("Parameter %d: %s\n", i, argv[i]); 545 switch (toupper(argv[i][1])) 546 { 547 case 'K': /* List of drive letters to skip */ 548 { 549 /* Check for the separator */ 550 if (argv[i][2] != ':') 551 goto Default; 552 553 SkipDrives = &argv[i][3]; 554 break; 555 } 556 557 case 'R': /* Repair the errors, implies /P */ 558 case 'P': /* Check even if not dirty */ 559 { 560 if (argv[i][2] != ANSI_NULL) 561 goto Default; 562 563 CheckOnlyIfDirty = FALSE; 564 break; 565 } 566 567 default: Default: 568 DPRINT1("Unknown switch %d: %s\n", i, argv[i]); 569 break; 570 } 571 } 572 else 573 { 574 DPRINT("Parameter %d - Volume specification: %s\n", i, argv[i]); 575 if (strcmp(argv[i], "*") == 0) 576 { 577 CheckAllVolumes = TRUE; 578 } 579 else 580 { 581 RtlInitEmptyUnicodeString(&VolumePathU, 582 VolumePath, 583 sizeof(VolumePath)); 584 RtlInitAnsiString(&VolumePathA, argv[i]); 585 Status = RtlAnsiStringToUnicodeString(&VolumePathU, 586 &VolumePathA, 587 FALSE); 588 } 589 590 /* Stop the parsing now */ 591 break; 592 } 593 } 594 595 /* 596 * FIXME: We should probably use here the mount manager to be 597 * able to check volumes which don't have a drive letter. 598 */ 599 Status = NtQueryInformationProcess(NtCurrentProcess(), 600 ProcessDeviceMap, 601 &DeviceMap.Query, 602 sizeof(DeviceMap.Query), 603 NULL); 604 if (!NT_SUCCESS(Status)) 605 { 606 DPRINT1("NtQueryInformationProcess() failed, Status 0x%08lx\n", Status); 607 return 1; 608 } 609 610 /* Filter out the skipped drives from the map */ 611 if (SkipDrives && *SkipDrives) 612 { 613 DPRINT1("Skipping drives:"); 614 while (*SkipDrives) 615 { 616 #if DBG 617 DbgPrint(" %c:", *SkipDrives); 618 #endif 619 /* Retrieve the index and filter the drive out */ 620 i = toupper(*SkipDrives) - 'A'; 621 if (0 <= i && i <= 'Z'-'A') 622 DeviceMap.Query.DriveMap &= ~(1 << i); 623 624 /* Go to the next drive letter */ 625 ++SkipDrives; 626 } 627 DbgPrint("\n"); 628 } 629 630 /* Query the timeout */ 631 TimeOut = 3; 632 QueryTimeout(&TimeOut); 633 634 /* Open the keyboard */ 635 Status = OpenKeyboard(&KeyboardHandle); 636 if (!NT_SUCCESS(Status)) 637 { 638 DPRINT1("OpenKeyboard() failed, Status 0x%08lx\n", Status); 639 /* Ignore keyboard interaction */ 640 } 641 642 if (CheckAllVolumes) 643 { 644 /* Enumerate and check each of the available volumes */ 645 for (i = 0; i <= 'Z'-'A'; i++) 646 { 647 if ((DeviceMap.Query.DriveMap & (1 << i)) && 648 (DeviceMap.Query.DriveType[i] == DOSDEVICE_DRIVE_FIXED)) 649 { 650 swprintf(VolumePath, L"\\??\\%c:", L'A' + i); 651 CheckVolume(VolumePath, TimeOut, CheckOnlyIfDirty); 652 } 653 } 654 } 655 else 656 { 657 /* Retrieve our index and analyse the volume */ 658 if (wcslen(VolumePath) == 6 && 659 VolumePath[0] == L'\\' && 660 VolumePath[1] == L'?' && 661 VolumePath[2] == L'?' && 662 VolumePath[3] == L'\\' && 663 VolumePath[5] == L':') 664 { 665 i = toupper(VolumePath[4]) - 'A'; 666 if ((DeviceMap.Query.DriveMap & (1 << i)) && 667 (DeviceMap.Query.DriveType[i] == DOSDEVICE_DRIVE_FIXED)) 668 { 669 CheckVolume(VolumePath, TimeOut, CheckOnlyIfDirty); 670 } 671 } 672 else 673 { 674 /* Just perform the check on the specified volume */ 675 // TODO: Check volume type using NtQueryVolumeInformationFile(FileFsDeviceInformation) 676 CheckVolume(VolumePath, TimeOut, CheckOnlyIfDirty); 677 } 678 } 679 680 /* Close the keyboard */ 681 if (KeyboardHandle) 682 NtClose(KeyboardHandle); 683 684 // PrintString("Done\r\n\r\n"); 685 return 0; 686 } 687 688 /* EOF */ 689