예제 #1
0
// 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;
}
예제 #2
0
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);
}
예제 #3
0
// 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
	}
}
예제 #4
0
// 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);
}
예제 #5
0
// 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"
		}
	}
}
예제 #6
0
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;
}
예제 #7
0
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;
}
예제 #8
0
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;

}