// stop the background process and cleanup // DON'T USE THIS METHOD UNLESS YOU HAVE EXHAUSTED ALL OTHER POSSIBLE EXIT ROUTES // this is of limited value since main app can't tell if this class has already self-destructed! // if a cached pointer of this class is used, better use IsBadReadPtr to arse-cover yourself void CConsolePipe::Break() { assert( // assume normal "new" //_CrtIsMemoryBlock(this, sizeof(CConsolePipe), 0,0,0) && IsBadReadPtr(this, sizeof(CConsolePipe))==0); // unfortunately _CrtIsMemoryBlock don't exists in release builds so we can't protect here if(IsChildRunning()) { assert( !(CPF_REUSECMDPROC & m_dwFlags) ); // try a StopCmd() first! assert(m_hChildProcess); assert(m_bIsNT && _T("If you proceed further in win9x you'll crash windows! (likely)")); // acting like a bully again TerminateProcess(m_hChildProcess, 10); // on platforms tested, the killed process doesn't cleanup the pipes // i have to do a forced cleanup myself // an idea to terminate the listener thread cleanly would be CloseHandle(m_hOutputRead) // unfortunately it doesn't work -- in fact it locks the program altogether! (must be // because we're attempting closing a handle we're waiting (ReadFile) on) } // in all (some) cases the object self-destructs after Break() if( CPF_NOAUTODELETE & m_dwFlags) Cleanup(); // instead of deletion, prepare class for reuse else delete this; }
BOOL CConsolePipe::SendChildInput(void *pData, DWORD dwSize) { assert(m_hInputWrite != INVALID_HANDLE_VALUE); assert(IsChildRunning()); assert(pData); assert(dwSize > 0); DWORD dwWritten; return WriteFile(m_hInputWrite, pData, dwSize, &dwWritten, NULL); }
// soft-break for processes like "more" void CConsolePipe::SendCtrlBrk() { if(IsChildRunning()) { //GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_dwProcessGroupId); // extra prodding for 9x // unfortunately, this has no effect for 9x, period. What crap OS this is! //TCHAR ctrlC[] = {3, 0}; //SendChildInput(ctrlC); no effect, perhaps translated by pipework? // this is why we had to allocate a console even for NT if(GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessGroupId)) OnReceivedOutput(_T("<CtrlBreak>")); // visual reassurance } }
// write data to be read by child process from its stdin // useful for commands that expect user input e.g. Y/N answers BOOL CConsolePipe::SendChildInput(LPCTSTR pszText) { assert(m_hInputWrite != INVALID_HANDLE_VALUE); assert(IsChildRunning()); assert(pszText); //assert(0 && _T("This method wasn't really tested, be careful!")); #ifdef _DEBUG // usually sent commands come with a newline attached ATLTRACE(_T("PIPE %x: Sending command: %s "), this, pszText); #endif /* UNICODE REVELATIONS %%% * In win2000 the /U command line option for cmd.exe is a (partial) fluke since it only * affects the output from commands. The stdin is still expected in ansi (!!) which I have * discovered after a long period of head-scratching. So this member should really be * accepting a LPCSTR argument, but the conversion is handled locally for convenience. * $TSEK the same happens for XP, didn't check for NT4 */ DWORD dwLen = lstrlen(pszText); if(!dwLen) return FALSE; LPSTR oem; #ifdef UNICODE // unlike when first created, we should do an explicit oem conversion here DWORD tmp = 2 * dwLen; oem = (LPSTR)_alloca(tmp + 1); // worst case for multibyte assert(oem && dwLen < 10000); tmp = WideCharToMultiByte( (m_dwFlags & CPF_NOCONVERTOEM) ? CP_ACP : CP_OEMCP, 0, pszText, dwLen, oem, tmp, NULL, NULL); assert(tmp == dwLen); // @@@ may break for multibyte? //oem[dwLen] = 0; no need #else // i don't really get it but although the string has already the character encoded // as e.g. "128", still the console cannot find it. I thought OEM was just a mapping thing! if(m_dwFlags & CPF_NOCONVERTOEM) oem = (LPSTR)pszText; else { // it must be down to ansi fileAPIs which have converted the name in the first place // @@@ still some special characters like the EURO get messed up in the process oem = (LPSTR)_alloca(dwLen + 1); assert(oem && dwLen < 10000); CharToOem(pszText, oem); } #endif return SendChildInput(oem, dwLen); }
// for persistent command processors, send a command that will terminate cmd.exe // typically this will be called before the main app exits void CConsolePipe::StopCmd() { assert(CPF_REUSECMDPROC & m_dwFlags); assert( // assume normal "new" _CrtIsMemoryBlock(this, sizeof(CConsolePipe), 0,0,0) && IsBadReadPtr(this, sizeof(CConsolePipe))==0); if(IsChildRunning()) { SendCtrlBrk(); // in case it was stuck if( (CPF_REUSECMDPROC & m_dwFlags) ) { //SendChildInput(^Z) would help stop some blocked processors Execute(_T("exit")); Sleep(200); // cleanup will occur "naturally" } } }
void Redirector::TerminateChildProcess() { if (! m_bWorking) return; // Tell the threads to exit and wait for process thread to die. m_bRunThread = FALSE; ::SetEvent(m_hExitEvent); Sleep(500); // Check the process thread. if (m_hProcessThread != NULL) { ::WaitForSingleObject(m_hProcessThread, 1000); m_hProcessThread = NULL; } // Close all child handles first. m_hStdIn.Close(); m_hStdOut.Close(); m_hStdErr.Close(); Sleep(100); // Close all parent handles. m_hStdInWrite.Close(); m_hStdOutRead.Close(); m_hStdErrRead.Close(); Sleep(100); // Stop the stdout read thread. if (m_hStdOutThread != NULL) { if (! ::IsWinNT()) ::TerminateThread(m_hStdOutThread, 1); ::WaitForSingleObject(m_hStdOutThread, 1000); m_hStdOutThread.Close(); } // Stop the stderr read thread. if (m_hStdErrThread != NULL) { if (!::IsWinNT()) ::TerminateThread(m_hStdErrThread, 1); ::WaitForSingleObject(m_hStdErrThread, 1000); m_hStdErrThread.Close(); } Sleep(100); // Stop the child process if not already stopped. // It's not the best solution, but it is a solution. // On Win98 it may crash the system if the child process is the COMMAND.COM. // The best way is to terminate the COMMAND.COM process with an "exit" command. if (IsChildRunning()) { ::TerminateProcess(m_hChildProcess, 1); ::WaitForSingleObject(m_hChildProcess, 1000); m_hChildProcess.Close(); } // cleanup the exit event m_hExitEvent.Close(); m_bWorking = FALSE; }
BOOL CConsolePipe::LaunchRedirected(LPCTSTR pszCommand, HANDLE hChildStdOut, HANDLE hChildStdIn, HANDLE hChildStdErr) { assert(hChildStdOut != INVALID_HANDLE_VALUE && hChildStdIn != INVALID_HANDLE_VALUE && hChildStdErr != INVALID_HANDLE_VALUE); assert(pszCommand); // command interpreter will be added on top assert(!m_hChildProcess); PROCESS_INFORMATION pi; STARTUPINFO si; /* REDIRECTION STRATEGY * In all cases we redirect std* handles of the child to the various pipe ends. Platform * differences concern the console; in 9x/Me we force the child to inherit our console; for * NTx we create a fresh hidden console. */ // Set up the start up info struct. memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES; si.hStdOutput = hChildStdOut; si.hStdInput = hChildStdIn; si.hStdError = hChildStdErr; // cheeky thought that hasn't lead anywhere wrt echoing //SetConsoleMode(hChildStdIn, 0); //SetConsoleMode(hChildStdOut, 0); // another platform dependent detail is the command processor LPCTSTR sys; // i don't read %COMSPEC% since it is infrequently setup properly sys = m_bIsNT ? _T("cmd.exe /A") : _T("command.com"); DWORD dwCreateFlags = CREATE_NEW_PROCESS_GROUP; // Ctrl-breakable /* ~UNICODE: PIPE DATA * cmd.exe has a couple of flags that determine whether the output is ansi/unicode (/A * & /U respectively). However the 9x command processor has no such equivalent, but then * again unicode is not supported there! * * REVISION. The /U flag has proven unreliable. Not only it is just for the output * direction, there are situations where the kidz create additional command processors * (e.g. some batch files) and since _they_ don't use /U, our output gets mangled too. * So I go for ansi (OEM) for all cases. */ TCHAR strExec[MAX_PATH]; // /C option ensures command interpreter is short-lived // /K forces a persistent one; here we need to switch off prompt and echoing, too if(CPF_REUSECMDPROC & m_dwFlags) { // i execute more commands to begin with using the & separator // but as you'd expect, this won't work in windows 9x if(m_bIsNT) // echo off /Q version is not recognized, in XP at least // this @echo off i specify doesn't have any effect either @@@ _sntprintf(strExec, MAX_PATH, _T("%s /K @echo off & prompt $S$H & %s"), sys, pszCommand); else // one thought would be to use the pipe | but that would fail commands with input // NOTE: i'm using echo off to get rid of the prompt (!) but command echo sticks round _sntprintf(strExec, MAX_PATH, _T("%s /K echo off"), sys); // actual command laters } else _sntprintf(strExec, MAX_PATH, _T("%s /C \"%s\""), sys, pszCommand); #ifdef _DEBUG ATLTRACE(_T("PIPE %x: Launching command: %s\n"), this, (LPCTSTR)strExec); #endif /* LAUNCHING NON-CONSOLE STUFF * If you try to launch e.g a plain "notepad" then several wicked things happen. The most * important is that the console sticks around for as long as the "notepad" runs, and the * listener thread will hang till the end of this child, although it won't actually read * anything. The obvious solution is don't use it for launching non-console apps. But even * so it won't really matter as long as you don't mind having a thread or 2 waiting idle. @@@ if only there was a way to figure out whether the new process had a console... */ BOOL ok = CreateProcess(NULL, strExec, NULL, NULL, TRUE, // handles inherited dwCreateFlags, NULL, NULL, &si, &pi); if(ok) { // Close any unnecessary handles. CloseHandle(pi.hThread); m_hChildProcess = pi.hProcess; assert(IsChildRunning()); m_dwProcessGroupId = pi.dwProcessId; if(m_bIsNT==FALSE && (CPF_REUSECMDPROC & m_dwFlags) ) { // issue the actual command _sntprintf(strExec, MAX_PATH, _T("%s\r\n"), pszCommand); SendChildInput(strExec); //SendChildInput(_T("@echo off\r\n")); ineffectual } } return ok; }
static void TerminateChildProcess(pcmd_backend_data local) { local->m_bRunThread = FALSE; SetEvent(local->m_hExitEvent); SetEvent(local->m_hWriteEvent); CloseHandle(local->m_hWriteEvent); Sleep(500); // Check the process thread. if (local->m_hProcessThread != NULL) { VERIFY(WaitForSingleObject(local->m_hProcessThread, 1000) != WAIT_TIMEOUT); local->m_hProcessThread = NULL; } // Close all child handles first. if (local->m_hStdIn != NULL) VERIFY(CloseHandle(local->m_hStdIn)); local->m_hStdIn = NULL; if (local->m_hStdOut != NULL) VERIFY(CloseHandle(local->m_hStdOut)); local->m_hStdOut = NULL; if (local->m_hStdErr != NULL) VERIFY(CloseHandle(local->m_hStdErr)); local->m_hStdErr = NULL; Sleep(100); // Close all parent handles. if (local->m_hStdInWrite != NULL) VERIFY(CloseHandle(local->m_hStdInWrite)); local->m_hStdInWrite = NULL; if (local->m_hStdOutRead != NULL) VERIFY(CloseHandle(local->m_hStdOutRead)); local->m_hStdOutRead = NULL; if (local->m_hStdErrRead != NULL) VERIFY(CloseHandle(local->m_hStdErrRead)); local->m_hStdErrRead = NULL; Sleep(100); // Stop the stdout read thread. if (local->m_hStdOutThread != NULL) { TerminateThread(local->m_hStdOutThread, 1); VERIFY(WaitForSingleObject(local->m_hStdOutThread, 1000) != WAIT_TIMEOUT); local->m_hStdOutThread = NULL; } // Stop the stderr read thread. if (local->m_hStdErrThread != NULL) { TerminateThread(local->m_hStdErrThread, 1); VERIFY(WaitForSingleObject(local->m_hStdErrThread, 1000) != WAIT_TIMEOUT); local->m_hStdErrThread = NULL; } Sleep(100); // Stop the child process if not already stopped. // It's not the best solution, but it is a solution. // On Win98 it may crash the system if the child process is the COMMAND.COM. // The best way is to terminate the COMMAND.COM process with an "exit" command. if (IsChildRunning(local)) { VERIFY(TerminateProcess(local->m_hChildProcess, 1)); VERIFY(WaitForSingleObject(local->m_hChildProcess, 1000) != WAIT_TIMEOUT); } local->m_hChildProcess = NULL; // cleanup the exit event if (local->m_hExitEvent != NULL) VERIFY(CloseHandle(local->m_hExitEvent)); local->m_hExitEvent = NULL; }