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