void
MSWindowsWatchdog::mainLoop(void*)
{
	shutdownExistingProcesses();

	SendSas sendSasFunc = NULL;
	HINSTANCE sasLib = LoadLibrary("sas.dll");
	if (sasLib) {
		LOG((CLOG_DEBUG "found sas.dll"));
		sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
	}

	SECURITY_ATTRIBUTES saAttr; 
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	saAttr.bInheritHandle = TRUE; 
	saAttr.lpSecurityDescriptor = NULL; 

	if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) {
		throw XArch(new XArchEvalWindows());
	}

	ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));

	while (m_monitoring) {
		try {

			if (m_processRunning && getCommand().empty()) {
				LOG((CLOG_INFO "process started but command is empty, shutting down"));
				shutdownExistingProcesses();
				m_processRunning = false;
				continue;
			}

			if (m_processFailures != 0) {
				// increasing backoff period, maximum of 10 seconds.
				int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10;
				LOG((CLOG_INFO "backing off, wait=%ds, failures=%d", timeout, m_processFailures));
				ARCH->sleep(timeout);
			}
		
			if (!getCommand().empty() && ((m_processFailures != 0) || m_session.hasChanged() || m_commandChanged)) {
				startProcess();
			}

			if (m_processRunning && !isProcessActive()) {

				m_processFailures++;
				m_processRunning = false;
			
				LOG((CLOG_WARN "detected application not running, pid=%d",
					m_processInfo.dwProcessId));
			}

			if (sendSasFunc != NULL) {

				HANDLE sendSasEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\SendSAS");
				if (sendSasEvent != NULL) {

					// use SendSAS event to wait for next session (timeout 1 second).
					if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0) {
						LOG((CLOG_DEBUG "calling SendSAS"));
						sendSasFunc(FALSE);
					}

					CloseHandle(sendSasEvent);
					continue;
				}
			}

			// if the sas event failed, wait by sleeping.
			ARCH->sleep(1);
		
		}
		catch (std::exception& e) {
			LOG((CLOG_ERR "failed to launch, error: %s", e.what()));
			m_processFailures++;
			m_processRunning = false;
			continue;
		}
		catch (...) {
			LOG((CLOG_ERR "failed to launch, unknown error."));
			m_processFailures++;
			m_processRunning = false;
			continue;
		}
	}

	if (m_processRunning) {
		LOG((CLOG_DEBUG "terminated running process on exit"));
		shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
	}
	
	LOG((CLOG_DEBUG "watchdog main thread finished"));
}
Exemple #2
0
void
CMSWindowsRelauncher::mainLoop(void*)
{
	shutdownExistingProcesses();

	SendSas sendSasFunc = NULL;
	HINSTANCE sasLib = LoadLibrary("sas.dll");
	if (sasLib) {
		LOG((CLOG_DEBUG "found sas.dll"));
		sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
	}

	DWORD sessionId = -1;
	bool launched = false;

	SECURITY_ATTRIBUTES saAttr; 
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	saAttr.bInheritHandle = TRUE; 
	saAttr.lpSecurityDescriptor = NULL; 

	if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) {
		throw XArch(new XArchEvalWindows());
	}

	PROCESS_INFORMATION pi;
	ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

	int failures = 0;

	while (m_running) {

		HANDLE sendSasEvent = 0;
		if (sasLib && sendSasFunc) {
			// can't we just create one event? seems weird creating a new
			// event every second...
			sendSasEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\SendSAS");
		}

		DWORD newSessionId = getSessionId();

		bool running = false;
		if (launched) {

			DWORD exitCode;
			GetExitCodeProcess(pi.hProcess, &exitCode);
			running = (exitCode == STILL_ACTIVE);

			if (!running) {
				failures++;
				LOG((CLOG_INFO "detected application not running, pid=%d, failures=%d", pi.dwProcessId, failures));
				
				// increasing backoff period, maximum of 10 seconds.
				int timeout = (failures * 2) < 10 ? (failures * 2) : 10;
				LOG((CLOG_DEBUG "waiting, backoff period is %d seconds", timeout));
				ARCH->sleep(timeout);
				
				// double check, in case process started after we waited.
				GetExitCodeProcess(pi.hProcess, &exitCode);
				running = (exitCode == STILL_ACTIVE);
			}
			else {
				// reset failures when running.
				failures = 0;
			}
		}

		// only enter here when id changes, and the session isn't -1, which
		// may mean that there is no active session.
		bool sessionChanged = ((newSessionId != sessionId) && (newSessionId != -1));

		// relaunch if it was running but has stopped unexpectedly.
		bool stoppedRunning = (launched && !running);

		if (stoppedRunning || sessionChanged || m_commandChanged) {
			
			m_commandChanged = false;

			if (launched) {
				LOG((CLOG_DEBUG "closing existing process to make way for new one"));
				shutdownProcess(pi.hProcess, pi.dwProcessId, 20);
				launched = false;
			}

			// ok, this is now the active session (forget the old one if any)
			sessionId = newSessionId;

			SECURITY_ATTRIBUTES sa;
			ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));

			// get the token for the user in active session, which is the
			// one receiving input from mouse and keyboard.
			HANDLE userToken = getCurrentUserToken(sessionId, &sa);

			if (userToken != 0) {
				LOG((CLOG_DEBUG "got user token to launch new process"));

				std::string cmd = command();
				if (cmd == "") {
					// this appears on first launch when the user hasn't configured
					// anything yet, so don't show it as a warning, only show it as
					// debug to devs to let them know why nothing happened.
					LOG((CLOG_DEBUG "nothing to launch, no command specified."));
					continue;
				}

				// in case reusing process info struct
				ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

				STARTUPINFO si;
				ZeroMemory(&si, sizeof(STARTUPINFO));
				si.cb = sizeof(STARTUPINFO);
				si.lpDesktop = "winsta0\\default";
				si.hStdError = m_stdOutWrite;
				si.hStdOutput = m_stdOutWrite;
				si.dwFlags |= STARTF_USESTDHANDLES;

				LPVOID environment;
				BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
				if (!blockRet) {
					LOG((CLOG_ERR "could not create environment block (error: %i)", 
						GetLastError()));
					continue;
				}
				else {

					DWORD creationFlags = 
						NORMAL_PRIORITY_CLASS |
						CREATE_NO_WINDOW |
						CREATE_UNICODE_ENVIRONMENT;

					// re-launch in current active user session
					LOG((CLOG_INFO "starting new process"));
					BOOL createRet = CreateProcessAsUser(
						userToken, NULL, LPSTR(cmd.c_str()),
						&sa, NULL, TRUE, creationFlags,
						environment, NULL, &si, &pi);

					DestroyEnvironmentBlock(environment);
					CloseHandle(userToken);

					if (!createRet) {
						LOG((CLOG_ERR "could not launch (error: %i)", GetLastError()));
						continue;
					}
					else {
						LOG((CLOG_DEBUG "launched in session %i (cmd: %s)", 
							sessionId, cmd.c_str()));
						launched = true;
					}
				}
			}
		}

		if (sendSasEvent) {
			// use SendSAS event to wait for next session.
			if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0 && sendSasFunc) {
				LOG((CLOG_DEBUG "calling SendSAS"));
				sendSasFunc(FALSE);
			}
			CloseHandle(sendSasEvent);
		}
		else {
			// check for session change every second.
			ARCH->sleep(1);
		}
	}

	if (launched) {
		LOG((CLOG_DEBUG "terminated running process on exit"));
		shutdownProcess(pi.hProcess, pi.dwProcessId, 20);
	}
	
	LOG((CLOG_DEBUG "relauncher main thread finished"));
}