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