/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ #define UNICODE #define WIN32_LEAN_AND_MEAN #ifdef _MSC_VER #pragma warning(push,1) // disable warnings within system headers #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #define _UNICODE #include #include #include #include #include #ifdef UNOPKG DWORD passOutputToConsole(HANDLE readPipe, HANDLE console) { BYTE aBuffer[1024]; DWORD dwRead = 0; HANDLE hReadPipe = readPipe; BOOL fSuccess; DWORD dwWritten; //Indicates that we read an odd number of bytes. That is, we only read half of the last //wchar_t bool bIncompleteWchar = false; //fprintf, fwprintf will both send char data without the terminating zero. //fwprintf converts the unicode string first. //We expect here to receive unicode without the terminating zero. //unopkg and the extension manager code MUST //use dp_misc::writeConsole instead of using fprintf, etc. DWORD dwToRead = sizeof(aBuffer); BYTE * pBuffer = aBuffer; while ( ReadFile( hReadPipe, pBuffer, dwToRead, &dwRead, NULL ) ) { //If the previous ReadFile call read an odd number of bytes, then the last one was //put at the front of the buffer. We increase the number of read bytes by one to reflect //that one byte. if (bIncompleteWchar) dwRead++; //We must make sure that only complete wchar_t|s are written. WriteConsolse takes //the number of wchar_t|s as argument. ReadFile, however, reads bytes. bIncompleteWchar = dwRead % 2 ? true : false; if (bIncompleteWchar) { //To test this case, give aBuffer a small odd size, e.g. aBuffer[3] //The last byte, which is the incomplete wchar_t (half of it), will not be written. fSuccess = WriteConsoleW( console, aBuffer, (dwRead - 1) / 2, &dwWritten, NULL ); //Move the last byte to the front of the buffer, so that it is the start of the //next string aBuffer[0] = aBuffer[dwRead - 1]; //Make sure that ReadFile does not overwrite the first byte the next time dwToRead = sizeof(aBuffer) - 1; pBuffer = aBuffer + 1; } else { //We have read an even number of bytes. Therefore, we do not put the last incomplete //wchar_t at the front of the buffer. We will use the complete buffer the next time //when ReadFile is called. dwToRead = sizeof(aBuffer); pBuffer = aBuffer; fSuccess = WriteConsoleW( console, aBuffer, dwRead / 2, &dwWritten, NULL ); } } return 0; } #endif #ifdef UNOPKG DWORD WINAPI OutputThread( LPVOID pParam ) { return passOutputToConsole((HANDLE)pParam, GetStdHandle( STD_OUTPUT_HANDLE )); } #else DWORD WINAPI OutputThread( LPVOID pParam ) { BYTE aBuffer[256]; DWORD dwRead = 0; HANDLE hReadPipe = (HANDLE)pParam; while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) ) { BOOL fSuccess; DWORD dwWritten; fSuccess = WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), aBuffer, dwRead, &dwWritten, NULL ); } return 0; } #endif //--------------------------------------------------------------------------- // Thread that reads from child process standard error pipe //--------------------------------------------------------------------------- #ifdef UNOPKG DWORD WINAPI ErrorThread( LPVOID pParam ) { return passOutputToConsole((HANDLE)pParam, GetStdHandle( STD_ERROR_HANDLE )); } #else DWORD WINAPI ErrorThread( LPVOID pParam ) { BYTE aBuffer[256]; DWORD dwRead = 0; HANDLE hReadPipe = (HANDLE)pParam; while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) ) { BOOL fSuccess; DWORD dwWritten; fSuccess = WriteFile( GetStdHandle( STD_ERROR_HANDLE ), aBuffer, dwRead, &dwWritten, NULL ); } return 0; } #endif //--------------------------------------------------------------------------- // Thread that writes to child process standard input pipe //--------------------------------------------------------------------------- #ifdef UNOPKG DWORD WINAPI InputThread( LPVOID pParam ) { DWORD dwRead = 0; HANDLE hWritePipe = (HANDLE)pParam; //We need to read in the complete input until we encounter a new line before //converting to Unicode. This is necessary because the input string can use //characters of one, two, and more bytes. If the last character is not //complete, then it will not be converted properly. //Find out how a new line (0xd 0xa) looks like with the used code page. //Characters may have one or multiple bytes and different byte ordering //can be used (little and big endian); int cNewLine = WideCharToMultiByte( GetConsoleCP(), 0, L"\r\n", 2, NULL, 0, NULL, NULL); char * mbBuff = new char[cNewLine]; WideCharToMultiByte( GetConsoleCP(), 0, L"\r\n", 2, mbBuff, cNewLine, NULL, NULL); const size_t dwBufferSize = 256; char* readBuf = (char*) malloc(dwBufferSize); int readAll = 0; size_t curBufSize = dwBufferSize; while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ), readBuf + readAll, curBufSize - readAll, &dwRead, NULL ) ) { readAll += dwRead; int lastBufSize = curBufSize; //Grow the buffer if necessary if (readAll > curBufSize * 0.7) { curBufSize *= 2; readBuf = (char *) realloc(readBuf, curBufSize); } //If the buffer was filled completely then //there could be more input coming. But if we read from the console //and the console input fits exactly in the buffer, then the next //ReadFile would block until the users presses return, etc. //Therefor we check if last character is a new line. //To test this, set dwBufferSize to 4 and enter "no". This should produce //4 bytes with most code pages. if ( readAll == lastBufSize && memcmp(readBuf + lastBufSize - cNewLine, mbBuff, cNewLine) != 0) { //The buffer was completely filled and the last byte(s) are no //new line, so there is more to come. continue; } //Obtain the size of the buffer for the converted string. int sizeWBuf = MultiByteToWideChar( GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, NULL, 0); wchar_t * wideBuf = new wchar_t[sizeWBuf]; //Do the conversion. MultiByteToWideChar( GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, wideBuf, sizeWBuf); BOOL fSuccess; DWORD dwWritten; fSuccess = WriteFile( hWritePipe, wideBuf, sizeWBuf * 2, &dwWritten, NULL ); delete[] wideBuf; readAll = 0; } delete[] mbBuff; free(readBuf); return 0; } #else DWORD WINAPI InputThread( LPVOID pParam ) { BYTE aBuffer[256]; DWORD dwRead = 0; HANDLE hWritePipe = (HANDLE)pParam; while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ), &aBuffer, sizeof(aBuffer), &dwRead, NULL ) ) { BOOL fSuccess; DWORD dwWritten; fSuccess = WriteFile( hWritePipe, aBuffer, dwRead, &dwWritten, NULL ); } return 0; } #endif //--------------------------------------------------------------------------- // Thread that waits until child process reached input idle //--------------------------------------------------------------------------- DWORD WINAPI WaitForUIThread( LPVOID pParam ) { HANDLE hProcess = (HANDLE)pParam; #ifndef UNOPKG if ( !_tgetenv( TEXT("UNOPKG") ) ) WaitForInputIdle( hProcess, INFINITE ); #endif return 0; } //--------------------------------------------------------------------------- // Ctrl-Break handler that terminates the child process if Ctrl-C was pressed //--------------------------------------------------------------------------- HANDLE hTargetProcess = INVALID_HANDLE_VALUE; BOOL WINAPI CtrlBreakHandler( DWORD // control signal type ) { TerminateProcess( hTargetProcess, 255 ); return TRUE; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #ifdef __MINGW32__ int main( int, char ** ) #else int _tmain( int, _TCHAR ** ) #endif { TCHAR szTargetFileName[MAX_PATH] = TEXT(""); STARTUPINFO aStartupInfo; PROCESS_INFORMATION aProcessInfo; ZeroMemory( &aStartupInfo, sizeof(aStartupInfo) ); aStartupInfo.cb = sizeof(aStartupInfo); aStartupInfo.dwFlags = STARTF_USESTDHANDLES; // Create an output pipe where the write end is inheritable HANDLE hOutputRead, hOutputWrite; if ( CreatePipe( &hOutputRead, &hOutputWrite, NULL, 0 ) ) { HANDLE hTemp; DuplicateHandle( GetCurrentProcess(), hOutputWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS ); CloseHandle( hOutputWrite ); hOutputWrite = hTemp; aStartupInfo.hStdOutput = hOutputWrite; } // Create an error pipe where the write end is inheritable HANDLE hErrorRead, hErrorWrite; if ( CreatePipe( &hErrorRead, &hErrorWrite, NULL, 0 ) ) { HANDLE hTemp; DuplicateHandle( GetCurrentProcess(), hErrorWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS ); CloseHandle( hErrorWrite ); hErrorWrite = hTemp; aStartupInfo.hStdError = hErrorWrite; } // Create an input pipe where the read end is inheritable HANDLE hInputRead, hInputWrite; if ( CreatePipe( &hInputRead, &hInputWrite, NULL, 0 ) ) { HANDLE hTemp; DuplicateHandle( GetCurrentProcess(), hInputRead, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS ); CloseHandle( hInputRead ); hInputRead = hTemp; aStartupInfo.hStdInput = hInputRead; } // Get image path with same name but with .exe extension TCHAR szModuleFileName[MAX_PATH]; GetModuleFileName( NULL, szModuleFileName, MAX_PATH ); _TCHAR *lpLastDot = _tcsrchr( szModuleFileName, '.' ); if ( lpLastDot && 0 == _tcsicmp( lpLastDot, _T(".COM") ) ) { size_t len = lpLastDot - szModuleFileName; _tcsncpy( szTargetFileName, szModuleFileName, len ); _tcsncpy( szTargetFileName + len, _T(".EXE"), sizeof(szTargetFileName)/sizeof(szTargetFileName[0]) - len ); } // Create process with same command line, environment and stdio handles which // are directed to the created pipes BOOL fSuccess = CreateProcess( szTargetFileName, GetCommandLine(), NULL, NULL, TRUE, 0, NULL, NULL, &aStartupInfo, &aProcessInfo ); if ( fSuccess ) { // These pipe ends are inherited by the child process and no longer used CloseHandle( hOutputWrite ); CloseHandle( hErrorWrite ); CloseHandle( hInputRead ); // Set the Ctrl-Break handler hTargetProcess = aProcessInfo.hProcess; SetConsoleCtrlHandler( CtrlBreakHandler, TRUE ); // Create threads that redirect remote pipe io to current process's console stdio DWORD dwOutputThreadId, dwErrorThreadId, dwInputThreadId; HANDLE hOutputThread = CreateThread( NULL, 0, OutputThread, (LPVOID)hOutputRead, 0, &dwOutputThreadId ); HANDLE hErrorThread = CreateThread( NULL, 0, OutputThread, (LPVOID)hErrorRead, 0, &dwErrorThreadId ); HANDLE hInputThread = CreateThread( NULL, 0, InputThread, (LPVOID)hInputWrite, 0, &dwInputThreadId ); // Create thread that wait until child process entered input idle DWORD dwWaitForUIThreadId; HANDLE hWaitForUIThread = CreateThread( NULL, 0, WaitForUIThread, (LPVOID)aProcessInfo.hProcess, 0, &dwWaitForUIThreadId ); DWORD dwWaitResult; HANDLE hObjects[] = { hTargetProcess, hWaitForUIThread, hOutputThread, hErrorThread }; #ifdef UNOPKG dwWaitResult = WaitForMultipleObjects( elementsof(hObjects), hObjects, TRUE, INFINITE ); #else bool bDetach = false; int nOpenPipes = 2; do { dwWaitResult = WaitForMultipleObjects( elementsof(hObjects), hObjects, FALSE, INFINITE ); switch ( dwWaitResult ) { case WAIT_OBJECT_0: // The child process has terminated case WAIT_OBJECT_0 + 1: // The child process entered input idle bDetach = true; break; case WAIT_OBJECT_0 + 2: // The remote end of stdout pipe was closed case WAIT_OBJECT_0 + 3: // The remote end of stderr pipe was closed bDetach = --nOpenPipes <= 0; break; default: // Something went wrong bDetach = true; break; } } while( !bDetach ); #endif CloseHandle( hOutputThread ); CloseHandle( hErrorThread ); CloseHandle( hInputThread ); CloseHandle( hWaitForUIThread ); DWORD dwExitCode = 0; GetExitCodeProcess( aProcessInfo.hProcess, &dwExitCode ); CloseHandle( aProcessInfo.hProcess ); CloseHandle( aProcessInfo.hThread ); return dwExitCode; } return -1; }