/*++ Copyright (c) 1991-2000 Microsoft Corporation Module Name: chkdsk.cxx Abstract: Chkdsk is a program that checks your disk for corruption and/or bad sectors. Author: Bill McJohn (billmc) 12-April-91 Revision History: --*/ #define _NTAPI_ULIB_ #include "ulib.hxx" #include "arg.hxx" #include "chkmsg.hxx" #include "rtmsg.h" #include "wstring.hxx" #include "path.hxx" #include "system.hxx" #include "ifssys.hxx" #include "substrng.hxx" #include "ulibcl.hxx" #include "ifsentry.hxx" #include "keyboard.hxx" #include "supera.hxx" // for CHKDSK_EXIT_* int __cdecl main( ) /*++ Routine Description: Entry point for chkdsk.exe. This function parses the arguments, determines the appropriate file system (by querying the volume), and invokes the appropriate version of chkdsk. The arguments accepted by Chkdsk are: /f Fix errors on drive /v Verbose operation drive: drive to check file-name files to check for contiguity (Note that HPFS ignores file-name parameters). /x Force the volume to dismount first if necessary (implied /f) /i include index entries checking during index verification /c include cycles checking during index verification /r locate bad sectors and recover readable information /l[:size] change or display log file size --*/ { DSTRING CurrentDirectory; DSTRING FsName; DSTRING FsNameAndVersion; DSTRING LibraryName; DSTRING DosDriveName; DSTRING CurrentDrive; DSTRING NtDriveName; PWSTRING p; HANDLE FsUtilityHandle; DSTRING ChkdskString; DSTRING Colon; CHKDSKEX_FN ChkdskEx = NULL; PWSTRING pwstring; BOOLEAN fix; BOOLEAN resize_logfile; ULONG logfile_size; FSTRING acolon, bcolon; ULONG exit_status; ARGUMENT_LEXEMIZER Lexemizer; ARRAY EmptyArray; ARRAY ArgumentArray; FLAG_ARGUMENT ArgumentHelp; FLAG_ARGUMENT ArgumentForce; FLAG_ARGUMENT ArgumentFix; FLAG_ARGUMENT ArgumentVerbose; FLAG_ARGUMENT ArgumentRecover; LONG_ARGUMENT ArgumentAlgorithm; FLAG_ARGUMENT ArgumentSkipIndexScan; FLAG_ARGUMENT ArgumentSkipCycleScan; FLAG_ARGUMENT ArgumentResize; LONG_ARGUMENT ArgumentResizeLong; STRING_ARGUMENT ArgumentProgramName; PATH_ARGUMENT ArgumentPath; CHKDSK_MESSAGE Message; NTSTATUS Status; DWORD oldErrorMode; PATH_ANALYZE_CODE rst; PPATH ppath; PATH fullpath; PATH drivepath; DSTRING drivename; DSTRING drive_path_string; BOOLEAN is_drivepath_invalid = TRUE; DSTRING ntfs_name; USHORT algorithm; CHKDSKEX_FN_PARAM param; if( !Message.Initialize( Get_Standard_Output_Stream(), Get_Standard_Input_Stream() ) ) { return CHKDSK_EXIT_COULD_NOT_CHK; } #if defined(PRE_RELEASE_NOTICE) Message.Set(MSG_CHK_PRE_RELEASE_NOTICE); Message.Display(); #endif // Initialize the colon string in case we need it later: if( !Colon.Initialize( ":" ) || !ntfs_name.Initialize( "NTFS" )) { Message.Set( MSG_CHK_NO_MEMORY ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } // Parse the arguments. First, initialize all the // parsing machinery. Then put the potential arguments // into the argument array, if( !ArgumentArray.Initialize( 5, 1 ) || !EmptyArray.Initialize( 5, 1 ) || !Lexemizer.Initialize( &EmptyArray ) || !ArgumentHelp.Initialize( "/?" ) || !ArgumentForce.Initialize( "/X" ) || !ArgumentFix.Initialize( "/F" ) || !ArgumentVerbose.Initialize( "/V" ) || !ArgumentRecover.Initialize( "/R" ) || !ArgumentAlgorithm.Initialize( "/I:*" ) || !ArgumentSkipIndexScan.Initialize( "/I" ) || !ArgumentSkipCycleScan.Initialize( "/C" ) || !ArgumentResize.Initialize( "/L" ) || !ArgumentResizeLong.Initialize( "/L:*" ) || !ArgumentProgramName.Initialize( "*" ) || !ArgumentPath.Initialize( "*", FALSE ) ) { Message.Set( MSG_CHK_NO_MEMORY ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } // CHKDSK is not case sensitive. Lexemizer.SetCaseSensitive( FALSE ); if( !ArgumentArray.Put( &ArgumentProgramName ) || !ArgumentArray.Put( &ArgumentHelp ) || !ArgumentArray.Put( &ArgumentForce ) || !ArgumentArray.Put( &ArgumentFix ) || !ArgumentArray.Put( &ArgumentVerbose ) || !ArgumentArray.Put( &ArgumentRecover ) || !ArgumentArray.Put( &ArgumentAlgorithm ) || !ArgumentArray.Put( &ArgumentSkipIndexScan )|| !ArgumentArray.Put( &ArgumentSkipCycleScan )|| !ArgumentArray.Put( &ArgumentResize ) || !ArgumentArray.Put( &ArgumentResizeLong ) || !ArgumentArray.Put( &ArgumentPath ) ) { Message.Set( MSG_CHK_NO_MEMORY ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } // Parse. Note that PrepareToParse will, by default, pick // up the command line. if( !Lexemizer.PrepareToParse() ) { Message.Set( MSG_CHK_NO_MEMORY ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } // If the parsing failed, display a helpful command line summary. if( !Lexemizer.DoParsing( &ArgumentArray ) ) { Message.Set(MSG_INVALID_PARAMETER); Message.Display("%W", pwstring = Lexemizer.QueryInvalidArgument()); DELETE(pwstring); return CHKDSK_EXIT_COULD_NOT_CHK; } // If the user requested help, give it. if( ArgumentHelp.QueryFlag() ) { Message.Set( MSG_CHK_USAGE_HEADER ); Message.Display( "" ); Message.Set( MSG_BLANK_LINE ); Message.Display( "" ); Message.Set( MSG_CHK_COMMAND_LINE ); Message.Display( "" ); Message.Set( MSG_BLANK_LINE ); Message.Display( "" ); Message.Set( MSG_CHK_DRIVE ); Message.Display( "" ); Message.Set( MSG_CHK_USG_FILENAME ); Message.Display( "" ); Message.Set( MSG_CHK_F_SWITCH ); Message.Display( "" ); Message.Set( MSG_CHK_V_SWITCH ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } if (!ArgumentPath.IsValueSet()) { if (!SYSTEM::QueryCurrentDosDriveName(&DosDriveName) || !drivepath.Initialize(&DosDriveName)) { return CHKDSK_EXIT_COULD_NOT_CHK; } ppath = &drivepath; } else { ppath = ArgumentPath.GetPath(); #if defined(RUN_ON_NT4) if (!DosDriveName.Initialize(ppath->GetPathString())) return CHKDSK_EXIT_COULD_NOT_CHK; #endif } #if !defined(RUN_ON_NT4) rst = ppath->AnalyzePath(&DosDriveName, &fullpath, &drive_path_string); switch (rst) { case PATH_OK: case PATH_COULD_BE_FLOPPY: is_drivepath_invalid = fullpath.IsDrive() || (fullpath.GetPathString()->QueryChCount() == 0); if (ppath->IsGuidVolName()) { if (!drivename.Initialize(&DosDriveName)) return CHKDSK_EXIT_COULD_NOT_CHK; } else { if (!drivename.Initialize(fullpath.GetPathString())) return CHKDSK_EXIT_COULD_NOT_CHK; } if (fullpath.GetPathString()->QueryChCount() == 2 && fullpath.GetPathString()->QueryChAt(1) == (WCHAR)':') { // if there is a drive letter for this drive, use it // instead of the guid volume name if (!DosDriveName.Initialize(fullpath.GetPathString())) { return CHKDSK_EXIT_COULD_NOT_CHK; } } if (!fullpath.AppendString(&drive_path_string) || !drivepath.Initialize(&drive_path_string)) return CHKDSK_EXIT_COULD_NOT_CHK; break; case PATH_OUT_OF_MEMORY: DebugPrint("Out of memory.\n"); return CHKDSK_EXIT_COULD_NOT_CHK; case PATH_NO_MOUNT_POINT_FOR_VOLUME_NAME_PATH: Message.Set(MSG_CHK_NO_MOUNT_POINT_FOR_GUID_VOLNAME_PATH); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; default: Message.Set(MSG_CHK_BAD_DRIVE_PATH_FILENAME); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } #endif if (!DosDriveName.Strupr()) { return CHKDSK_EXIT_COULD_NOT_CHK; } // disable popups while we determine the drive type oldErrorMode = SetErrorMode( SEM_FAILCRITICALERRORS ); // Make sure that drive is of a correct type. switch (SYSTEM::QueryDriveType(&DosDriveName)) { case RemoteDrive: SetErrorMode( oldErrorMode ); Message.Set(MSG_CHK_CANT_NETWORK); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; #if 0 case CdRomDrive: SetErrorMode( oldErrorMode ); Message.Set(MSG_CHK_CANT_CDROM); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; #endif default: break; } SetErrorMode( oldErrorMode ); if (!SYSTEM::QueryCurrentDosDriveName(&CurrentDrive)) { return CHKDSK_EXIT_COULD_NOT_CHK; } // /R ==> /F // /X ==> /F fix = ArgumentFix.QueryFlag() || ArgumentForce.QueryFlag() || ArgumentRecover.QueryFlag(); // From here on we want to deal with an NT drive name: if (!IFS_SYSTEM::DosDriveNameToNtDriveName(&DosDriveName, &NtDriveName)) { return CHKDSK_EXIT_COULD_NOT_CHK; } // disable popups while we determine the file system name and version oldErrorMode = SetErrorMode( SEM_FAILCRITICALERRORS ); // Determine the type of the file system. // Ask the volume what file system it has. The // IFS utilities for file system xxxx are in Uxxxx.DLL. // if (!IFS_SYSTEM::QueryFileSystemName(&NtDriveName, &FsName, &Status, &FsNameAndVersion )) { SetErrorMode( oldErrorMode ); if( Status == STATUS_ACCESS_DENIED ) { Message.Set( MSG_DASD_ACCESS_DENIED ); Message.Display( "" ); } else if( Status != STATUS_SUCCESS ) { Message.Set( MSG_CANT_DASD ); Message.Display( "" ); } else { Message.Set( MSG_FS_NOT_DETERMINED ); Message.Display( "%W", &drivename ); } return CHKDSK_EXIT_COULD_NOT_CHK; } // re-enable hardware popups SetErrorMode( oldErrorMode ); if (FsName == ntfs_name && drive_path_string.QueryChCount()) { Message.Set(MSG_CHK_BAD_DRIVE_PATH_FILENAME); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } Message.SetLoggingEnabled(); Message.Set( MSG_CHK_RUNNING ); Message.Log( "%W", &drivename ); Message.Set( MSG_FILE_SYSTEM_TYPE ); Message.Display( "%W", &FsName ); if ( !FsName.Strupr() ) { return CHKDSK_EXIT_COULD_NOT_CHK; } DSTRING fat32_name; if ( !fat32_name.Initialize("FAT32") ) { return CHKDSK_EXIT_COULD_NOT_CHK; } if ( FsName == fat32_name ) { FsName.Initialize("FAT"); } if ( !LibraryName.Initialize( "U" ) || !LibraryName.Strcat( &FsName ) || !ChkdskString.Initialize( "ChkdskEx" ) ) { Message.Set( MSG_CHK_NO_MEMORY ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } if (fix && (CurrentDrive == DosDriveName)) { Message.Set(MSG_CANT_LOCK_CURRENT_DRIVE); Message.Display(); if (IsNEC_98) { DP_DRIVE dpdrive; dpdrive.Initialize(&NtDriveName, &Message); if (dpdrive.IsFloppy()) { return CHKDSK_EXIT_COULD_NOT_CHK; } } else { acolon.Initialize((PWSTR) L"A:"); bcolon.Initialize((PWSTR) L"B:"); if (!DosDriveName.Stricmp(&acolon) || !DosDriveName.Stricmp(&bcolon)) { return CHKDSK_EXIT_COULD_NOT_CHK; } } // Fall through so that the lock fails and then the // run autochk on reboot logic kicks in. // } if (ArgumentAlgorithm.IsValueSet() && ArgumentSkipIndexScan.QueryFlag()) { Message.Set(MSG_CHK_ALGORITHM_AND_SKIP_INDEX_SPECIFIED); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } if (ArgumentAlgorithm.IsValueSet()) { if (ArgumentAlgorithm.QueryLong() < 0 || ArgumentAlgorithm.QueryLong() > CHKDSK_MAX_ALGORITHM_VALUE) { Message.Set(MSG_CHK_INCORRECT_ALGORITHM_VALUE); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } else algorithm = (USHORT)ArgumentAlgorithm.QueryLong(); } else algorithm = 0; if (ArgumentSkipIndexScan.QueryFlag() || ArgumentAlgorithm.IsValueSet()) { if (0 != FsName.Stricmp( &ntfs_name )) { Message.Set(MSG_CHK_SKIP_INDEX_NOT_NTFS); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } } if (ArgumentSkipCycleScan.QueryFlag()) { if (0 != FsName.Stricmp( &ntfs_name )) { Message.Set(MSG_CHK_SKIP_CYCLE_NOT_NTFS); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } } // Does the user want to resize the logfile? This is only sensible // for NTFS. If she specified a size of zero, print an error message // because that's a poor choice and will confuse the untfs code, // which assumes that zero means resize to the default size. // resize_logfile = ArgumentResize.IsValueSet() || ArgumentResizeLong.IsValueSet(); if (resize_logfile) { if (0 != FsName.Stricmp( &ntfs_name )) { Message.Set(MSG_CHK_LOGFILE_NOT_NTFS); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } if (ArgumentResizeLong.IsValueSet()) { if (ArgumentResizeLong.QueryLong() <= 0) { Message.Set(MSG_CHK_WONT_ZERO_LOGFILE); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } if (ArgumentResizeLong.QueryLong() > MAXULONG/1024) { Message.Set(MSG_CHK_NTFS_SPECIFIED_LOGFILE_SIZE_TOO_BIG); Message.Display(); return CHKDSK_EXIT_COULD_NOT_CHK; } logfile_size = ArgumentResizeLong.QueryLong() * 1024; } else { logfile_size = 0; } } if ((ChkdskEx = (CHKDSKEX_FN)SYSTEM::QueryLibraryEntryPoint( &LibraryName, &ChkdskString, &FsUtilityHandle )) != NULL ) { if (fix && !KEYBOARD::EnableBreakHandling()) { return CHKDSK_EXIT_COULD_NOT_CHK; } // // setup parameter block v1.0 to be passed to ChkdskEx // param.Major = 1; param.Minor = 1; param.Flags = (ArgumentVerbose.QueryFlag() ? CHKDSK_VERBOSE : 0); param.Flags |= (ArgumentRecover.QueryFlag() ? CHKDSK_RECOVER : 0); param.Flags |= (ArgumentForce.QueryFlag() ? CHKDSK_FORCE : 0); param.Flags |= (resize_logfile ? CHKDSK_RESIZE_LOGFILE : 0); param.Flags |= (ArgumentSkipIndexScan.QueryFlag() ? CHKDSK_SKIP_INDEX_SCAN : 0); param.Flags |= (ArgumentSkipCycleScan.QueryFlag() ? CHKDSK_SKIP_CYCLE_SCAN : 0); param.Flags |= (ArgumentAlgorithm.IsValueSet() ? CHKDSK_ALGORITHM_SPECIFIED : 0); param.LogFileSize = logfile_size; param.PathToCheck = &fullpath; param.RootPath = (is_drivepath_invalid) ? NULL : &drivepath; param.Algorithm = algorithm; if (fix) { ChkdskEx( &NtDriveName, &Message, fix, ¶m, &exit_status ); } else { //disable C4509 warning about nonstandard ext: SEH + destructor #pragma warning(disable:4509) __try { ChkdskEx( &NtDriveName, &Message, fix, ¶m, &exit_status ); } __except (EXCEPTION_EXECUTE_HANDLER) { // If we get an access violation during read-only mode // CHKDSK then it's because the file system is partying // on the volume while we are. Message.Set(MSG_CHK_NTFS_ERRORS_FOUND); Message.Display(); exit_status = CHKDSK_EXIT_ERRS_NOT_FIXED; } } if (CHKDSK_EXIT_ERRS_FIXED == exit_status && !fix) { exit_status = CHKDSK_EXIT_ERRS_NOT_FIXED; } SYSTEM::FreeLibraryHandle( FsUtilityHandle ); if (fix && !KEYBOARD::DisableBreakHandling()) { return 1; } } else { Message.Set( MSG_FS_NOT_SUPPORTED ); Message.Display( "%s%W", "CHKDSK", &FsName ); Message.Set( MSG_BLANK_LINE ); Message.Display( "" ); return CHKDSK_EXIT_COULD_NOT_CHK; } // Message.Set(MSG_CHK_NTFS_MESSAGE); // Message.Display("%s%d", "Exit Status ", exit_status); return exit_status; }