[AUTOCHK] Improvements: code + command-line handling.

- Add support for the following command-line arguments: /k, /p (and a little bit
  of /r), and add support for checking a particular volume.
  A description of these arguments can be found at:
  "Description of Enhanced Chkdsk, Autochk, and Chkntfs Tools in Windows 2000",
  https://web.archive.org/web/20150215210228/http://support.microsoft.com:80/kb/218461

- Slightly improve some messages.

- Get rid of legacy OpenDirectory() and simplify GetFileSystem().
This commit is contained in:
Hermès Bélusca-Maïto 2018-07-29 16:00:49 +02:00
parent 472d1d2f64
commit 3b6faba11a
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0

View file

@ -124,57 +124,6 @@ EraseLine(
NtDisplayString(&UnicodeString); NtDisplayString(&UnicodeString);
} }
// this func is taken from kernel32/file/volume.c
static HANDLE
OpenDirectory(
IN LPCWSTR DirName,
IN BOOLEAN Write)
{
UNICODE_STRING NtPathU;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE hFile;
if (!RtlDosPathNameToNtPathName_U(DirName,
&NtPathU,
NULL,
NULL))
{
DPRINT1("Invalid path!\n");
return INVALID_HANDLE_VALUE;
}
InitializeObjectAttributes(
&ObjectAttributes,
&NtPathU,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = NtCreateFile(
&hFile,
Write ? FILE_GENERIC_WRITE : FILE_GENERIC_READ,
&ObjectAttributes,
&IoStatusBlock,
NULL,
0,
FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OPEN,
0,
NULL,
0);
RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathU.Buffer);
if (!NT_SUCCESS(Status))
{
return INVALID_HANDLE_VALUE;
}
return hFile;
}
static NTSTATUS static NTSTATUS
OpenKeyboard( OpenKeyboard(
OUT PHANDLE KeyboardHandle) OUT PHANDLE KeyboardHandle)
@ -279,21 +228,42 @@ WaitForKeyboard(
static NTSTATUS static NTSTATUS
GetFileSystem( GetFileSystem(
IN LPCWSTR Drive, IN PUNICODE_STRING VolumePathU,
IN OUT LPWSTR FileSystemName, IN OUT PWSTR FileSystemName,
IN SIZE_T FileSystemNameSize) IN SIZE_T FileSystemNameSize)
{ {
HANDLE FileHandle;
NTSTATUS Status; NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE FileHandle;
IO_STATUS_BLOCK IoStatusBlock; IO_STATUS_BLOCK IoStatusBlock;
PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute; PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute;
UCHAR Buffer[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(WCHAR)]; UCHAR Buffer[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer; FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer;
FileHandle = OpenDirectory(Drive, FALSE); InitializeObjectAttributes(&ObjectAttributes,
if (FileHandle == INVALID_HANDLE_VALUE) VolumePathU,
return STATUS_INVALID_PARAMETER; OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = NtCreateFile(&FileHandle,
FILE_GENERIC_READ,
&ObjectAttributes,
&IoStatusBlock,
NULL,
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
0,
NULL,
0);
if (!NT_SUCCESS(Status))
{
DPRINT1("Could not open volume '%wZ' to obtain its file system, Status 0x%08lx\n",
VolumePathU, Status);
return Status;
}
Status = NtQueryVolumeInformationFile(FileHandle, Status = NtQueryVolumeInformationFile(FileHandle,
&IoStatusBlock, &IoStatusBlock,
@ -308,7 +278,7 @@ GetFileSystem(
return Status; return Status;
} }
if (FileSystemNameSize * sizeof(WCHAR) >= FileFsAttribute->FileSystemNameLength + sizeof(WCHAR)) if (FileSystemNameSize >= FileFsAttribute->FileSystemNameLength + sizeof(WCHAR))
{ {
RtlCopyMemory(FileSystemName, RtlCopyMemory(FileSystemName,
FileFsAttribute->FileSystemName, FileFsAttribute->FileSystemName,
@ -421,32 +391,46 @@ ChkdskCallback(
static NTSTATUS static NTSTATUS
CheckVolume( CheckVolume(
IN PWCHAR DrivePath, IN PCWSTR VolumePath,
IN LONG TimeOut) IN LONG TimeOut,
IN BOOLEAN CheckOnlyIfDirty)
{ {
WCHAR FileSystem[128];
WCHAR NtDrivePath[64];
UNICODE_STRING DrivePathU;
NTSTATUS Status; NTSTATUS Status;
DWORD Count; PCWSTR DisplayName;
UNICODE_STRING VolumePathU;
ULONG Count;
WCHAR FileSystem[128];
swprintf(NtDrivePath, L"\\??\\"); RtlInitUnicodeString(&VolumePathU, VolumePath);
wcscat(NtDrivePath, DrivePath);
NtDrivePath[wcslen(NtDrivePath)-1] = 0;
RtlInitUnicodeString(&DrivePathU, NtDrivePath);
DPRINT1("AUTOCHK: Checking %wZ\n", &DrivePathU); /* Get a drive string for display purposes only */
PrintString(" Checking file system on %S\r\n", DrivePath); if (wcslen(VolumePath) == 6 &&
VolumePath[0] == L'\\' &&
VolumePath[1] == L'?' &&
VolumePath[2] == L'?' &&
VolumePath[3] == L'\\' &&
VolumePath[5] == L':')
{
/* DOS drive */
DisplayName = &VolumePath[4];
}
else
{
DisplayName = VolumePath;
}
DPRINT1("AUTOCHK: Checking %wZ\n", &VolumePathU);
PrintString("Verifying the file system on %S\r\n", DisplayName);
/* Get the file system */ /* Get the file system */
Status = GetFileSystem(DrivePath, Status = GetFileSystem(&VolumePathU,
FileSystem, FileSystem,
ARRAYSIZE(FileSystem)); sizeof(FileSystem));
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
{ {
DPRINT1("GetFileSystem() failed, Status 0x%08lx\n", Status); DPRINT1("GetFileSystem() failed, Status 0x%08lx\n", Status);
PrintString(" Unable to detect file system of %S\r\n", DrivePath); PrintString(" Unable to detect the file system of volume %S\r\n", DisplayName);
return Status; goto Quit;
} }
PrintString(" The file system type is %S.\r\n\r\n", FileSystem); PrintString(" The file system type is %S.\r\n\r\n", FileSystem);
@ -459,41 +443,52 @@ CheckVolume(
} }
if (Count >= RTL_NUMBER_OF(FileSystems)) if (Count >= RTL_NUMBER_OF(FileSystems))
{ {
DPRINT1("File system not supported\n"); DPRINT1("File system %S not supported\n", FileSystem);
PrintString(" Unable to check the file system. %S is not supported.\r\n", FileSystem); PrintString(" Unable to verify the volume. The %S file system is not supported.\r\n", FileSystem);
return STATUS_DLL_NOT_FOUND; Status = STATUS_DLL_NOT_FOUND;
goto Quit;
} }
/* First, check whether the volume is dirty */ /* Check whether the volume is dirty */
Status = FileSystems[Count].ChkdskFunc(&DrivePathU, Status = FileSystems[Count].ChkdskFunc(&VolumePathU,
FALSE, // FixErrors FALSE, // FixErrors
TRUE, // Verbose TRUE, // Verbose
TRUE, // CheckOnlyIfDirty TRUE, // CheckOnlyIfDirty
FALSE, // ScanDrive FALSE, // ScanDrive
ChkdskCallback); ChkdskCallback);
/* It is */
if (Status == STATUS_DISK_CORRUPT_ERROR) /* Perform the check either when the volume is dirty or a check is forced */
if ((Status == STATUS_DISK_CORRUPT_ERROR) || !CheckOnlyIfDirty)
{ {
/* Let the user decide whether to repair */ /* Let the user decide whether to repair */
PrintString(" The file system on this volume needs to be checked for problems.\r\n"); if (Status == STATUS_DISK_CORRUPT_ERROR)
PrintString(" You may cancel this check, but it's recommended that you continue.\r\n\r\n"); {
PrintString("The file system on volume %S needs to be checked for problems.\r\n", DisplayName);
PrintString("You may cancel this check, but it is recommended that you continue.\r\n");
}
else
{
PrintString("A volume check has been scheduled.\r\n");
}
if (!KeyboardHandle || WaitForKeyboard(KeyboardHandle, TimeOut) == STATUS_TIMEOUT) if (!KeyboardHandle || WaitForKeyboard(KeyboardHandle, TimeOut) == STATUS_TIMEOUT)
{ {
PrintString(" The system will now check the file system.\r\n\r\n"); PrintString("The system will now check the file system.\r\n\r\n");
Status = FileSystems[Count].ChkdskFunc(&DrivePathU, Status = FileSystems[Count].ChkdskFunc(&VolumePathU,
TRUE, // FixErrors TRUE, // FixErrors
TRUE, // Verbose TRUE, // Verbose
TRUE, // CheckOnlyIfDirty CheckOnlyIfDirty,
FALSE, // ScanDrive FALSE, // ScanDrive
ChkdskCallback); ChkdskCallback);
} }
else else
{ {
PrintString(" File system check has been skipped.\r\n"); PrintString("The file system check has been skipped.\r\n");
} }
} }
Quit:
PrintString("\r\n\r\n");
return Status; return Status;
} }
@ -521,24 +516,84 @@ _main(
IN PCHAR envp[], IN PCHAR envp[],
IN ULONG DebugFlag) IN ULONG DebugFlag)
{ {
PROCESS_DEVICEMAP_INFORMATION DeviceMap;
ULONG i;
NTSTATUS Status; NTSTATUS Status;
WCHAR DrivePath[128];
LONG TimeOut; LONG TimeOut;
ULONG i;
BOOLEAN CheckAllVolumes = FALSE;
BOOLEAN CheckOnlyIfDirty = TRUE;
PROCESS_DEVICEMAP_INFORMATION DeviceMap;
PCHAR SkipDrives = NULL;
ANSI_STRING VolumePathA;
UNICODE_STRING VolumePathU;
WCHAR VolumePath[128] = L"";
// Win2003 passes the only param - "*". Probably means to check all drives
/* /*
DPRINT("Got %d params\n", argc); * Parse the command-line: optional command switches,
for (i=0; i<argc; i++) * then the NT volume name (or drive letter) to be analysed.
DPRINT("Param %d: %s\n", i, argv[i]); * If "*" is passed this means that we check all the volumes.
*/ */
if (argc <= 1)
{
/* Only one parameter (the program name), bail out */
return 1;
}
for (i = 1; i < argc; ++i)
{
if (argv[i][0] == '/' || argv[i][0] == '-')
{
DPRINT("Parameter %d: %s\n", i, argv[i]);
switch (toupper(argv[i][1]))
{
case 'K': /* List of drive letters to skip */
{
/* Check for the separator */
if (argv[i][2] != ':')
goto Default;
/* Query timeout */ SkipDrives = &argv[i][3];
TimeOut = 3; break;
QueryTimeout(&TimeOut); }
/* FIXME: We should probably use here the mount manager to be case 'R': /* Repair the errors, implies /P */
case 'P': /* Check even if not dirty */
{
if (argv[i][2] != ANSI_NULL)
goto Default;
CheckOnlyIfDirty = FALSE;
break;
}
default: Default:
DPRINT1("Unknown switch %d: %s\n", i, argv[i]);
break;
}
}
else
{
DPRINT("Parameter %d - Volume specification: %s\n", i, argv[i]);
if (strcmp(argv[i], "*") == 0)
{
CheckAllVolumes = TRUE;
}
else
{
RtlInitEmptyUnicodeString(&VolumePathU,
VolumePath,
sizeof(VolumePath));
RtlInitAnsiString(&VolumePathA, argv[i]);
Status = RtlAnsiStringToUnicodeString(&VolumePathU,
&VolumePathA,
FALSE);
}
/* Stop the parsing now */
break;
}
}
/*
* FIXME: We should probably use here the mount manager to be
* able to check volumes which don't have a drive letter. * able to check volumes which don't have a drive letter.
*/ */
Status = NtQueryInformationProcess(NtCurrentProcess(), Status = NtQueryInformationProcess(NtCurrentProcess(),
@ -552,6 +607,30 @@ _main(
return 1; return 1;
} }
/* Filter out the skipped drives from the map */
if (SkipDrives && *SkipDrives)
{
DPRINT1("Skipping drives:");
while (*SkipDrives)
{
#if DBG
DbgPrint(" %c:", *SkipDrives);
#endif
/* Retrieve the index and filter the drive out */
i = toupper(*SkipDrives) - 'A';
if (0 <= i && i <= 'Z'-'A')
DeviceMap.Query.DriveMap &= ~(1 << i);
/* Go to the next drive letter */
++SkipDrives;
}
DbgPrint("\n");
}
/* Query the timeout */
TimeOut = 3;
QueryTimeout(&TimeOut);
/* Open the keyboard */ /* Open the keyboard */
Status = OpenKeyboard(&KeyboardHandle); Status = OpenKeyboard(&KeyboardHandle);
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
@ -560,13 +639,41 @@ _main(
/* Ignore keyboard interaction */ /* Ignore keyboard interaction */
} }
for (i = 0; i < 26; i++) if (CheckAllVolumes)
{ {
if ((DeviceMap.Query.DriveMap & (1 << i)) /* Enumerate and check each of the available volumes */
&& (DeviceMap.Query.DriveType[i] == DOSDEVICE_DRIVE_FIXED)) for (i = 0; i <= 'Z'-'A'; i++)
{ {
swprintf(DrivePath, L"%c:\\", L'A'+i); if ((DeviceMap.Query.DriveMap & (1 << i)) &&
CheckVolume(DrivePath, TimeOut); (DeviceMap.Query.DriveType[i] == DOSDEVICE_DRIVE_FIXED))
{
swprintf(VolumePath, L"\\??\\%c:", L'A' + i);
CheckVolume(VolumePath, TimeOut, CheckOnlyIfDirty);
}
}
}
else
{
/* Retrieve our index and analyse the volume */
if (wcslen(VolumePath) == 6 &&
VolumePath[0] == L'\\' &&
VolumePath[1] == L'?' &&
VolumePath[2] == L'?' &&
VolumePath[3] == L'\\' &&
VolumePath[5] == L':')
{
i = toupper(VolumePath[4]) - 'A';
if ((DeviceMap.Query.DriveMap & (1 << i)) &&
(DeviceMap.Query.DriveType[i] == DOSDEVICE_DRIVE_FIXED))
{
CheckVolume(VolumePath, TimeOut, CheckOnlyIfDirty);
}
}
else
{
/* Just perform the check on the specified volume */
// TODO: Check volume type using NtQueryVolumeInformationFile(FileFsDeviceInformation)
CheckVolume(VolumePath, TimeOut, CheckOnlyIfDirty);
} }
} }