/*++ Copyright (c) 1991-2001 Microsoft Corporation Module Name: autoconv.cxx Abstract: This is the main program for the autoconv version of convert. Author: Ramon J. San Andres (ramonsa) 04-Dec-91 --*/ #include "ulib.hxx" #include "wstring.hxx" #include "achkmsg.hxx" #include "spackmsg.hxx" #include "ifssys.hxx" #include "rtmsg.h" #if defined(FE_SB) && defined(_X86_) #include "machine.hxx" #endif #include "ifsentry.hxx" #include "convfat.hxx" #include "fatvol.hxx" #include "autoreg.hxx" #include "autoentr.hxx" #include "arg.hxx" #include "rcache.hxx" #if INCLUDE_OFS==1 #include "fatofs.hxx" //#include "initexcp.hxx" #endif // INCLUDE_OFS extern "C" BOOLEAN InitializeUfat( PVOID DllHandle, ULONG Reason, PCONTEXT Context ); extern "C" BOOLEAN InitializeUntfs( PVOID DllHandle, ULONG Reason, PCONTEXT Context ); extern "C" BOOLEAN InitializeIfsUtil( PVOID DllHandle, ULONG Reason, PCONTEXT Context ); BOOLEAN DeRegister( int argc, char** argv ); BOOLEAN SaveMessageLog( IN OUT PMESSAGE Message, IN PCWSTRING DriveName ); BOOLEAN FileDelete( IN PCWSTRING DriveName, IN PCWSTRING FileName ); #if INCLUDE_OFS==1 BOOLEAN IsRestartFatToOfs( WSTRING const & DriveName, WSTRING const & CurrentFsName, WSTRING const & TargetFileSystem ) { DSTRING OfsName; DSTRING FatName; if ( !OfsName.Initialize( L"OFS" ) ) return FALSE; if ( CurrentFsName != OfsName || TargetFileSystem != OfsName ) return FALSE; PWSTR pwszDriveName = DriveName.QueryWSTR(); BOOLEAN fIsRestart = IsFatToOfsRestart( pwszDriveName ); DELETE( pwszDriveName ); return fIsRestart; } BOOLEAN IsFatToOfs( WSTRING const & CurrentFsName, WSTRING const & TargetFsName ) { DSTRING FatName; DSTRING OfsName; if ( !FatName.Initialize( L"FAT" ) ) return FALSE; if ( !OfsName.Initialize( L"OFS" ) ) return FALSE; return 0 == CurrentFsName.Stricmp(&FatName) && 0 == TargetFsName.Stricmp(&OfsName); } BOOLEAN FatToOfs( IN PCWSTRING NtDriveName, IN OUT PMESSAGE Message, IN BOOLEAN Verbose, IN BOOLEAN fInSetup, OUT PCONVERT_STATUS Status ) { PWSTR pwszNtDriveName = NtDriveName->QueryWSTR(); FAT_OFS_CONVERT_STATUS cnvStatus; BOOLEAN fResult= ConvertFatToOfs( pwszNtDriveName, Message, Verbose, fInSetup, &cnvStatus ); DELETE( pwszNtDriveName ); if ( FAT_OFS_CONVERT_SUCCESS == cnvStatus ) { *Status = CONVERT_STATUS_CONVERTED; } else { *Status = CONVERT_STATUS_ERROR; } return fResult; } #else BOOLEAN IsRestartFatToOfs( WSTRING const & DriveName, WSTRING const & CurrentFsName, WSTRING const & TargetFileSystem ) { return FALSE; } BOOLEAN IsFatToOfs( WSTRING const & CurrentFsName, WSTRING const & TargetFsName ) { return FALSE; } BOOLEAN FatToOfs( IN PCWSTRING NtDriveName, IN OUT PMESSAGE Message, IN BOOLEAN Verbose, IN BOOLEAN fInSetup, OUT PCONVERT_STATUS Status ) { *Status = CONVERT_STATUS_CONVERSION_NOT_AVAILABLE; return FALSE; } #endif // INCLUDE_OFS int __cdecl main( int argc, char** argv, char** envp, ULONG DebugParameter ) /*++ Routine Description: This routine is the main program for AutoConv Arguments: argc, argv - Supplies the fully qualified NT path name of the the drive to check. The syntax of the autoconv command line is: AUTOCONV drive-name /FS:target-file-system [/v] [/s] [/o] [/cvtarea:filename] /v -- verbose output /s -- run from setup /o -- pause before the final reboot (oem setup) /cvtarea:filename -- convert zone file name /nochkdsk -- skips chkdsk and go straight to conversion Return Value: 0 - Success. 1 - Failure. --*/ { #if INCLUDE_OFS==1 InitExceptionSystem(); #endif // INCLUDE_OFS==1 if (!InitializeUlib( NULL, ! DLL_PROCESS_DETACH, NULL ) || !InitializeIfsUtil(NULL, ! DLL_PROCESS_DETACH, NULL) || !InitializeUfat(NULL, ! DLL_PROCESS_DETACH, NULL) || !InitializeUntfs(NULL, ! DLL_PROCESS_DETACH, NULL) ) { DebugPrintTrace(( "Failed to initialize U* Dlls" )); return 1; } #if defined(FE_SB) && defined(_X86_) InitializeMachineId(); #endif PAUTOCHECK_MESSAGE message; DSTRING DriveName; DSTRING FileSystemName; DSTRING CvtZoneFileName; DSTRING CurrentFsName; DSTRING FatName; DSTRING Fat32Name; DSTRING QualifiedName; FSTRING Backslash; BOOLEAN Converted; BOOLEAN Verbose = FALSE; BOOLEAN NoChkdsk = FALSE; BOOLEAN NoSecurity = FALSE; BOOLEAN Error; CONVERT_STATUS Status; int i; BOOLEAN fInSetup = FALSE; BOOLEAN Pause = FALSE; LARGE_INTEGER DelayInterval; DSTRING StrippedDriveName; BOOLEAN enabled; NTSTATUS status; UNICODE_STRING driverName; ULONG flags; status = RtlAdjustPrivilege(SE_LOAD_DRIVER_PRIVILEGE, TRUE, FALSE, &enabled); RtlInitUnicodeString( &driverName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Ntfs" ); status = NtLoadDriver( &driverName ); status = RtlAdjustPrivilege(SE_LOAD_DRIVER_PRIVILEGE, enabled, FALSE, &enabled); DebugPrintTrace(( "Entering autoconv argc=%ld\n", argc )); for ( i = 0; i < argc; i++ ) { DebugPrintTrace((" argv[%d] = %s\n", i, argv[i] )); } // // Parse the arguments. The accepted arguments are: // // autoconv NtDrive /fs: [/v] // if ( argc < 3 ) { return 1; } // // First argument is drive // if ( !DriveName.Initialize( argv[1] ) || !StrippedDriveName.Initialize( argv[1] ) ) { DebugPrintTrace(( "Failed to intialize DriveName \n" )); return 1; } if (!IFS_SYSTEM::NtDriveNameToDosDriveName(&DriveName, &StrippedDriveName)) { if (!StrippedDriveName.Initialize(&DriveName)) { DebugPrint( "Out of memory.\n" ); return 1; } } DebugPrintTrace(("drive name: %ws\n", StrippedDriveName.GetWSTR())); // // The rest of the arguments are flags. // for ( i = 2; i < argc; i++ ) { if ( (strlen(argv[i]) >= 5) && (argv[i][0] == '/' || argv[i][0] == '-') && (argv[i][1] == 'f' || argv[i][1] == 'F') && (argv[i][2] == 's' || argv[i][2] == 'S') && (argv[i][3] == ':') ) { if ( 0 != FileSystemName.QueryChCount() || !FileSystemName.Initialize( &(argv[i][4]) ) ) { DebugPrintTrace(( "Failed to initialize FileSystemName \n" )); return 1; } } if (0 == _stricmp( argv[i], "/V" ) || 0 == _stricmp( argv[i], "-V" )) { Verbose = TRUE; } if (0 == _stricmp(argv[i], "/S") || 0 == _stricmp(argv[i], "-S")) { DebugPrintTrace(("Found /s option\n")); fInSetup = TRUE; } if (0 == _stricmp(argv[i], "/O") || 0 == _stricmp(argv[i], "-O")) { DebugPrintTrace(("Found /o option\n")); Pause = TRUE; } if ( (strlen(argv[i]) >= 10) && (argv[i][0] == '/' || argv[i][0] == '-') && (argv[i][1] == 'c' || argv[i][1] == 'C') && (argv[i][2] == 'v' || argv[i][2] == 'V') && (argv[i][3] == 't' || argv[i][3] == 'T') && (argv[i][4] == 'a' || argv[i][4] == 'A') && (argv[i][5] == 'r' || argv[i][5] == 'R') && (argv[i][6] == 'e' || argv[i][6] == 'E') && (argv[i][7] == 'a' || argv[i][7] == 'A') && (argv[i][8] == ':') ) { if ( 0 != CvtZoneFileName.QueryChCount() || !CvtZoneFileName.Initialize( &(argv[i][9]) ) ) { DebugPrintTrace(( "Failed to initialize CvtZoneFileName \n" )); return 1; } } if ( (strlen(argv[i]) >= 9) && (argv[i][0] == '/' || argv[i][0] == '-') && (argv[i][1] == 'n' || argv[i][1] == 'N') && (argv[i][2] == 'o' || argv[i][2] == 'O') && (argv[i][3] == 'c' || argv[i][3] == 'C') && (argv[i][4] == 'h' || argv[i][4] == 'H') && (argv[i][5] == 'k' || argv[i][5] == 'K') && (argv[i][6] == 'd' || argv[i][6] == 'D') && (argv[i][7] == 's' || argv[i][7] == 'S') && (argv[i][8] == 'k' || argv[i][8] == 'K') ) { NoChkdsk = TRUE; } if ( (strlen(argv[i]) >= 11) && (argv[i][0] == '/' || argv[i][0] == '-') && (argv[i][1] == 'n' || argv[i][1] == 'N') && (argv[i][2] == 'o' || argv[i][2] == 'O') && (argv[i][3] == 's' || argv[i][3] == 'S') && (argv[i][4] == 'e' || argv[i][4] == 'E') && (argv[i][5] == 'c' || argv[i][5] == 'C') && (argv[i][6] == 'u' || argv[i][6] == 'U') && (argv[i][7] == 'r' || argv[i][7] == 'R') && (argv[i][8] == 'i' || argv[i][8] == 'I') && (argv[i][9] == 't' || argv[i][9] == 'T') && (argv[i][10] == 'y' || argv[i][10] == 'Y') ) { NoSecurity = TRUE; } } if ( 0 == FileSystemName.QueryChCount() ) { DebugPrintTrace(( "No FileSystem name specified\n" )); return 1; } DebugPrintTrace(("AUTOCONV: TargetFileSystem=%ws\n", FileSystemName.GetWSTR() )); if (fInSetup) { message = NEW SP_AUTOCHECK_MESSAGE; DebugPrintTrace(("Using setup output\n")); } else { DebugPrintTrace(("Not using setup output\n")); message = NEW AUTOCHECK_MESSAGE; } if (NULL == message || !message->Initialize()) { DebugPrintTrace(( "Failed to intitialize message structure\n" )); return 1; } message->SetLoggingEnabled(TRUE); if (!FatName.Initialize("FAT") || !Fat32Name.Initialize("FAT32")) { message->Set(MSG_CONV_NO_MEMORY); message->Display(); SaveMessageLog( message, &DriveName ); return 1; } // If this is the System Partition of an ARC machine, don't // convert it. // if( IFS_SYSTEM::IsArcSystemPartition( &DriveName, &Error ) ) { message->Set( MSG_CONV_ARC_SYSTEM_PARTITION ); message->Display( ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } if (!IFS_SYSTEM::QueryFileSystemName( &DriveName, &CurrentFsName )) { message->Set( MSG_FS_NOT_DETERMINED ); message->Display( "%W", &StrippedDriveName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } #if defined(FE_SB) && defined(_X86_) if( IsPC98_N() ) { DP_DRIVE dpdrive2; if( !dpdrive2.Initialize(&DriveName, message) ) { message->Set( MSG_CONV_CANNOT_AUTOCHK ); message->Display( "%W%W", &DriveName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } if( CurrentFsName == FatName ) { //***FAT*** if((dpdrive2.QuerySectorSize() != dpdrive2.QueryPhysicalSectorSize())|| (dpdrive2.QueryPhysicalSectorSize() == 2048)) { message->Set( MSG_CONV_CONVERSION_FAILED ); message->Display( "%W%W", &DriveName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } } else { //***HPFS/NTFS** if( dpdrive2.QueryPhysicalSectorSize() == 2048 ) { message->Set( MSG_CONV_CONVERSION_FAILED ); message->Display( "%W%W", &DriveName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } } } #endif CurrentFsName.Strupr(); FileSystemName.Strupr(); if ( CurrentFsName == FileSystemName ) { int iReturn = 0; if ( IsRestartFatToOfs( DriveName, CurrentFsName, FileSystemName ) ) { if ( !FatToOfs( &DriveName, message, Verbose, fInSetup, &Status ) ) { iReturn = 1; } } else { // // The drive is already in the desired file system, our // job is done. Delete the name conversion table (if // specified) and take ourselves out of the registry. // Do not save the message log--there's nothing interesting // in it. // // If we're doing oem pre-install (Pause is TRUE) we don't // want to print this "already converted" message. // message->SetLoggingEnabled(FALSE); } SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return iReturn; } message->Set( MSG_FILE_SYSTEM_TYPE ); message->Display( "%W", &CurrentFsName ); // Determine whether the target file-system is enabled // in the registry. If it is not, refuse to convert // the drive. // if( !IFS_SYSTEM::IsFileSystemEnabled( &FileSystemName ) ) { message->Set( MSG_CONVERT_FILE_SYSTEM_NOT_ENABLED ); message->Display( "%W", &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } // Since autoconvert will often be put in place by Setup // to run after AutoSetp, delay for 3 seconds to give the // file system time to clean up detritus of deleted files. // DelayInterval = RtlConvertLongToLargeInteger( -30000000 ); NtDelayExecution( TRUE, &DelayInterval ); // Open a volume of the appropriate type. The volume is // opened for exclusive write access. // if( CurrentFsName == FatName || CurrentFsName == Fat32Name) { if (IsConversionAvailable(&FileSystemName)) { message->Set( MSG_CONV_CONVERSION_NOT_AVAILABLE ); message->Display( "%W%W", &CurrentFsName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } { DP_DRIVE dpdrive; if( !dpdrive.Initialize(&DriveName, message) ) { message->Set( MSG_CONV_CONVERSION_FAILED ); message->Display( "%W%W", &DriveName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } switch (dpdrive.QueryDriveType()) { case UnknownDrive: // it probably won't get this far message->Set( MSG_CONV_INVALID_DRIVE_SPEC ); message->Display(); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; case CdRomDrive: message->Set( MSG_CONV_CANT_CDROM ); message->Display(); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; case RemoteDrive: // it probably won't get this far message->Set( MSG_CONV_CANT_NETWORK ); message->Display(); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; default: break; } } if (!NoChkdsk) { PFAT_VOL FatVolume; if( !(FatVolume = NEW FAT_VOL) || !FatVolume->Initialize( message, &DriveName) || !FatVolume->ChkDsk( TotalFix, message, 0, 0 ) ) { DELETE (FatVolume); message->Set( MSG_CONV_CANNOT_AUTOCHK ); message->Display( "%W%W", &StrippedDriveName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } DELETE (FatVolume); } message->Set( MSG_BLANK_LINE ); message->Display(); if ( IsFatToOfs( CurrentFsName, FileSystemName ) ) { message->Set( MSG_CONV_CONVERTING ); message->Display( "%W%W", &StrippedDriveName, &FileSystemName ); Converted = FatToOfs( &DriveName, message, Verbose, fInSetup, &Status ); } else { PLOG_IO_DP_DRIVE pDrive; pDrive = NEW LOG_IO_DP_DRIVE; if (pDrive == NULL) { message->Set(MSG_CONV_NO_MEMORY); message->Display(); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } if ( !pDrive->Initialize(&DriveName, message) ) { message->Set( MSG_CONV_CONVERSION_FAILED ); message->Display( "%W%W", &DriveName, &FileSystemName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); DELETE( pDrive ); return 1; } message->Set( MSG_CONV_CONVERTING ); message->Display( "%W%W", &StrippedDriveName, &FileSystemName ); // // there is no need to pass in /NoChkdsk // flags = Verbose ? CONVERT_VERBOSE_FLAG : 0; flags |= Pause ? CONVERT_PAUSE_FLAG : 0; flags |= NoSecurity ? CONVERT_NOSECURITY_FLAG : 0; Converted = ConvertFATVolume( pDrive, &FileSystemName, &CvtZoneFileName, message, flags, &Status ); DELETE( pDrive ); } } else { message->Set( MSG_FS_NOT_SUPPORTED ); message->Display( "%s%W", "AUTOCONV", &CurrentFsName ); SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); return 1; } if ( Converted ) { message->Set( MSG_CONV_CONVERSION_COMPLETE ); message->Display(); } else { // // The conversion was not successful. Determine what the problem was // and return the appropriate CONVERT exit code. // switch ( Status ) { case CONVERT_STATUS_CONVERTED: // // This is an inconsistent state, Convert should return // TRUE if the conversion was successful! // message->Set( MSG_CONV_CONVERSION_MAYHAVE_FAILED ); message->Display( "%W%W", &StrippedDriveName, &FileSystemName ); break; case CONVERT_STATUS_INVALID_FILESYSTEM: // // The conversion DLL does not recognize the target file system. // message->Set( MSG_CONV_INVALID_FILESYSTEM ); message->Display( "%W", &FileSystemName ); break; case CONVERT_STATUS_CONVERSION_NOT_AVAILABLE: // // The target file system is valid, but the conversion is not // available. // message->Set( MSG_CONV_CONVERSION_NOT_AVAILABLE ); message->Display( "%W%W", &CurrentFsName, &FileSystemName ); break; case CONVERT_STATUS_NTFS_RESERVED_NAMES: message->Set( MSG_CONV_NTFS_RESERVED_NAMES ); message->Display( "%W", &StrippedDriveName ); break; case CONVERT_STATUS_WRITE_PROTECTED: message->Set( MSG_CONV_WRITE_PROTECTED ); message->Display( "%W", &StrippedDriveName ); break; case CONVERT_STATUS_INSUFFICIENT_FREE_SPACE: case CONVERT_STATUS_CANNOT_LOCK_DRIVE: case CONVERT_STATUS_DRIVE_IS_DIRTY: case CONVERT_STATUS_ERROR: // // The conversion failed. // default: // // Invalid status code // message->Set( MSG_CONV_CONVERSION_FAILED ); message->Display( "%W%W", &StrippedDriveName, &FileSystemName ); DebugPrintTrace(("AUTOCONV: Failed %x\n", Status)); break; } } SaveMessageLog( message, &DriveName ); DeRegister( argc, argv ); #if INCLUDE_OFS==1 CleanupExceptionSystem(); #endif // INCLUDE_OFS==1 return ( Converted ? 0 : 1 ); } BOOLEAN DeRegister( int argc, char** argv ) /*++ Routine Description: This function removes the registry entry which triggered autoconvert. Arguments: argc -- Supplies the number of arguments given to autoconv argv -- supplies the arguments given to autoconv Return Value: TRUE upon successful completion. --*/ { DSTRING CommandLineString1, CommandLineString2, CurrentArgString, OneSpace; int i; // Reconstruct the command line and remove it from // the registry. First, reconstruct the primary // string, which is "autoconv arg1 arg2...". // if( !CommandLineString1.Initialize( L"autoconv" ) || !OneSpace.Initialize( L" " ) ) { return FALSE; } for( i = 1; i < argc; i++ ) { if( !CurrentArgString.Initialize( argv[i] ) || !CommandLineString1.Strcat( &OneSpace ) || !CommandLineString1.Strcat( &CurrentArgString ) ) { return FALSE; } } // Now construct the secondary string, which is // "autocheck arg0 arg1 arg2..." // if( !CommandLineString2.Initialize( "autocheck " ) || !CommandLineString2.Strcat( &CommandLineString1 ) ) { return FALSE; } return( AUTOREG::DeleteEntry( &CommandLineString1 ) && AUTOREG::DeleteEntry( &CommandLineString2 ) ); } BOOLEAN SaveMessageLog( IN OUT PMESSAGE Message, IN PCWSTRING DriveName ) /*++ Routine Description: This function writes the logged messages from the supplied message object to the file "BOOTEX.LOG" in the root of the specified drive. Arguments: Message -- Supplies the message object. DriveName -- Supplies the name of the drive. Return Value: TRUE upon successful completion. --*/ { DSTRING QualifiedName; FSTRING BootExString; HMEM Mem; ULONG Length; if( !Message->IsLoggingEnabled() ) { return TRUE; } return( QualifiedName.Initialize( DriveName ) && BootExString.Initialize( L"\\BOOTEX.LOG" ) && QualifiedName.Strcat( &BootExString ) && Mem.Initialize() && Message->QueryPackedLog( &Mem, &Length ) && IFS_SYSTEM::WriteToFile( &QualifiedName, Mem.GetBuf(), Length, TRUE ) ); } BOOLEAN FileDelete( IN PCWSTRING DriveName, IN PCWSTRING FileName ) /*++ Routine Description: This function deletes a file. It is used to clean up the name translation table. Arguments: DriveName -- Supplies the drive on which the file resides. FileName -- Supplies the file name. Note that the file should be in the root directory. Return Value: TRUE upon successful completion. --*/ { DSTRING QualifiedName; FSTRING Backslash; IO_STATUS_BLOCK IoStatusBlock; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING UnicodeString; FILE_DISPOSITION_INFORMATION DispositionInfo; HANDLE FileHandle; NTSTATUS Status; if( !Backslash.Initialize( L"\\" ) || !QualifiedName.Initialize( DriveName ) || !QualifiedName.Strcat( &Backslash ) || !QualifiedName.Strcat( FileName ) ) { return FALSE; } UnicodeString.Buffer = (PWSTR)QualifiedName.GetWSTR(); UnicodeString.Length = (USHORT)( QualifiedName.QueryChCount() * sizeof( WCHAR ) ); UnicodeString.MaximumLength = UnicodeString.Length; InitializeObjectAttributes( &ObjectAttributes, &UnicodeString, OBJ_CASE_INSENSITIVE, 0, 0 ); Status = NtOpenFile( &FileHandle, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &ObjectAttributes, &IoStatusBlock, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE ); if( NT_SUCCESS( Status ) ) { DispositionInfo.DeleteFile = TRUE; Status = NtSetInformationFile( FileHandle, &IoStatusBlock, &DispositionInfo, sizeof( DispositionInfo ), FileDispositionInformation ); } if( !NT_SUCCESS( Status ) ) { return FALSE; } NtClose( FileHandle ); return TRUE; }