fix: Restore active desktop name check in Windows daemon

- Use `PIPE_NOWAIT` to skip empty stderr output on process output reading from Windows daemon watchdog
- Use `CreateProcess` result to determine when to call `CloseHandle` in Windows daemon watchdog
- Trim output from active desktop process in Windows daemon watchdog
- Improve error logging in Windows daemon watchdog
This commit is contained in:
Nick Bolton
2025-02-13 17:37:58 +00:00
parent 3de2c1bcf0
commit c9a360a144
11 changed files with 453 additions and 277 deletions

View File

@ -28,6 +28,30 @@ int main(int argc, char **argv)
Log log;
EventQueue events;
// HACK: the `--active-desktop` arg actually belongs in the `deskflow-core` binary,
// but we are placing it here in the server binary temporarily until we are ready to
// ship the `deskflow-core` binary. we are deliberately not integrating `--active-desktop`
// into the existing `ServerApp` arg parsing code as that would be a waste of time.
#if SYSAPI_WIN32
for (int i = 1; i < argc; ++i) {
std::string arg(argv[i]);
// This is called by the daemon (running in session 0) when it needs to know the name of the
// interactive desktop.
// It is necessary to run a utility process because the daemon runs in session 0, which does not
// have access to the active desktop, and so cannot query it's name.
if (arg == "--active-desktop") {
const auto name = ArchMiscWindows::getActiveDesktopName();
if (name.empty()) {
LOG((CLOG_CRIT "failed to get active desktop name"));
return kExitFailed;
}
LOG((CLOG_PRINT "%s", name.c_str()));
return kExitSuccess;
}
}
#endif
ServerApp app(&events);
return app.run(argc, argv);
}

View File

@ -7,6 +7,7 @@
#include "arch/win32/ArchMiscWindows.h"
#include "arch/win32/ArchDaemonWindows.h"
#include "arch/win32/XArchWindows.h"
#include "base/Log.h"
#include "common/constants.h"
@ -457,3 +458,19 @@ void ArchMiscWindows::setInstanceWin32(HINSTANCE instance)
assert(instance != NULL);
s_instanceWin32 = instance;
}
std::string ArchMiscWindows::getActiveDesktopName()
{
HDESK desk = OpenInputDesktop(0, TRUE, GENERIC_READ);
if (desk == nullptr) {
LOG((CLOG_ERR "could not open input desktop"));
throw XArch(new XArchEvalWindows());
}
DWORD size;
GetUserObjectInformation(desk, UOI_NAME, nullptr, 0, &size);
auto *name = (TCHAR *)alloca(size + sizeof(TCHAR));
GetUserObjectInformation(desk, UOI_NAME, name, size, &size);
CloseDesktop(desk);
return name;
}

View File

@ -155,12 +155,11 @@ public:
static bool getParentProcessName(std::string &name);
static HINSTANCE instanceWin32();
static void setInstanceWin32(HINSTANCE instance);
static BOOL WINAPI getProcessEntry(PROCESSENTRY32 &entry, DWORD processID);
static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32 &entry);
static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32 &entry);
static std::string getActiveDesktopName();
private:
//! Open and return a registry key, closing the parent key

View File

@ -376,7 +376,7 @@ void DaemonApp::handleIpcMessage(const Event &e, void *)
}
#if SYSAPI_WIN32
std::string watchdogStatus = m_watchdog->isProcessActive() ? "active" : "idle";
std::string watchdogStatus = m_watchdog->isProcessRunning() ? "active" : "idle";
LOG((CLOG_INFO "service status: %s", watchdogStatus.c_str()));
#endif

View File

@ -43,6 +43,8 @@ if(WIN32)
MSWindowsKeyState.h
MSWindowsPowerManager.cpp
MSWindowsPowerManager.h
MSWindowsProcess.cpp
MSWindowsProcess.h
MSWindowsScreen.cpp
MSWindowsScreen.h
MSWindowsScreenSaver.cpp

View File

@ -0,0 +1,225 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "MSWindowsProcess.h"
#include "arch/win32/XArchWindows.h"
#include "base/Log.h"
#include "ipc/IpcMessage.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <UserEnv.h>
#include <string>
namespace deskflow::platform {
MSWindowsProcess::MSWindowsProcess(const std::string &command, HANDLE stdOutput, HANDLE stdError)
: m_command(command),
m_stdOutput(stdOutput),
m_stdError(stdError)
{
}
MSWindowsProcess::~MSWindowsProcess()
{
if (m_createProcessResult) {
CloseHandle(m_info.hProcess);
CloseHandle(m_info.hThread);
}
}
BOOL MSWindowsProcess::startInForeground()
{
// clear, as we're reusing process info struct
ZeroMemory(&m_info, sizeof(PROCESS_INFORMATION));
// show the console window when in foreground mode,
// so we can close it gracefully, but minimize it
// so it doesn't get in the way.
STARTUPINFO si;
setStartupInfo(si);
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZE;
m_createProcessResult =
CreateProcess(nullptr, LPSTR(m_command.c_str()), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &m_info);
return m_createProcessResult;
}
BOOL MSWindowsProcess::startAsUser(HANDLE userToken, LPSECURITY_ATTRIBUTES sa)
{
STARTUPINFO si;
setStartupInfo(si);
LPVOID environment;
if (!CreateEnvironmentBlock(&environment, userToken, FALSE)) {
LOG((CLOG_ERR "could not create environment block"));
throw XArch(new XArchEvalWindows);
}
ZeroMemory(&m_info, sizeof(PROCESS_INFORMATION));
const DWORD creationFlags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
m_createProcessResult = CreateProcessAsUser(
userToken, nullptr, LPSTR(m_command.c_str()), sa, nullptr, TRUE, creationFlags, environment, nullptr, &si, &m_info
);
DestroyEnvironmentBlock(environment);
CloseHandle(userToken);
return m_createProcessResult;
}
void MSWindowsProcess::setStartupInfo(STARTUPINFO &si)
{
static char g_desktopName[] = "winsta0\\Default"; // NOSONAR -- Idiomatic Win32
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = g_desktopName;
si.hStdOutput = m_stdOutput;
si.hStdError = m_stdError;
si.dwFlags |= STARTF_USESTDHANDLES;
}
DWORD MSWindowsProcess::waitForExit()
{
const auto kMaxWaitMilliseconds = 10000;
LOG_INFO("waiting for process to exit, pid: %lu", m_info.dwProcessId);
if (WaitForSingleObject(m_info.hProcess, kMaxWaitMilliseconds) != WAIT_OBJECT_0) {
LOG_ERR("process did not exit within the expected time");
TerminateProcess(m_info.hProcess, 1);
throw XArch(new XArchEvalWindows());
}
DWORD exitCode = 0;
if (!GetExitCodeProcess(m_info.hProcess, &exitCode)) {
LOG_ERR("failed to get exit code, error: %lu", GetLastError());
throw XArch(new XArchEvalWindows());
}
if (exitCode != 0) {
LOG_WARN("process failed with exit code: %lu", exitCode);
} else {
LOG_DEBUG("process exited with code: %lu", exitCode);
}
return exitCode;
}
void MSWindowsProcess::shutdown(IpcServer &ipcServer, int timeout)
{
shutdown(m_info.hProcess, m_info.dwProcessId, ipcServer, timeout);
}
void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer, int timeout)
{
LOG_DEBUG("shutting down process %d", pid);
DWORD exitCode;
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
LOG_DEBUG("process %d is already shutdown", pid);
return;
}
IpcShutdownMessage shutdown;
ipcServer.send(shutdown, IpcClientType::Node);
// wait for process to exit gracefully.
double start = ARCH->time();
while (true) { // NOSONAR -- Multiple breaks necessary
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
// yay, we got a graceful shutdown. there should be no hook in use errors!
LOG((CLOG_DEBUG "process %d was shutdown gracefully", pid));
break;
}
if (double elapsed = (ARCH->time() - start); elapsed > timeout) {
// if timeout reached, kill forcefully.
// calling TerminateProcess on deskflow is very bad!
// it causes the hook DLL to stay loaded in some apps,
// making it impossible to start deskflow again.
LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
TerminateProcess(handle, kExitSuccess);
break;
}
ARCH->sleep(1);
}
}
void MSWindowsProcess::createPipes()
{
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = nullptr;
if (!CreatePipe(&m_outputPipe, &m_stdOutput, &saAttr, 0)) {
LOG_ERR("could not create output pipe");
throw XArch(new XArchEvalWindows());
}
if (!CreatePipe(&m_errorPipe, &m_stdError, &saAttr, 0)) {
LOG_ERR("could not create error pipe");
throw XArch(new XArchEvalWindows());
}
// Set the pipes to non-blocking mode
DWORD mode = PIPE_NOWAIT;
if (!SetNamedPipeHandleState(m_outputPipe, &mode, nullptr, nullptr)) {
throw XArch(new XArchEvalWindows());
}
if (!SetNamedPipeHandleState(m_errorPipe, &mode, nullptr, nullptr)) {
throw XArch(new XArchEvalWindows());
}
}
std::string MSWindowsProcess::readStdOutput()
{
return readOutput(m_outputPipe);
}
std::string MSWindowsProcess::readStdError()
{
return readOutput(m_errorPipe);
}
std::string MSWindowsProcess::readOutput(HANDLE handle)
{
const auto kOutputBufferSize = 4096;
char buffer[kOutputBufferSize]; // NOSONAR -- Idiomatic Win32
DWORD bytesRead;
DWORD totalBytesAvail;
DWORD bytesLeftThisMessage;
// Check if there is data available in the pipe, which prevents `ReadFile` from freezing execution.
if (!PeekNamedPipe(handle, nullptr, 0, nullptr, &totalBytesAvail, &bytesLeftThisMessage)) {
LOG_ERR("could not peek into pipe");
throw XArch(new XArchEvalWindows());
}
if (totalBytesAvail == 0) {
return "";
}
if (!ReadFile(handle, buffer, kOutputBufferSize, &bytesRead, nullptr)) {
LOG_ERR("could not read from pipe");
throw XArch(new XArchEvalWindows());
}
return std::string(buffer, bytesRead);
}
} // namespace deskflow::platform

View File

@ -0,0 +1,57 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include "ipc/IpcServer.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <string>
namespace deskflow::platform {
namespace { // NOSONAR -- Deliberate anonymous
const auto kDefaultShutdownTimeout = 10;
}
class MSWindowsProcess
{
public:
explicit MSWindowsProcess(const std::string &command, HANDLE stdOutput = nullptr, HANDLE stdError = nullptr);
~MSWindowsProcess();
BOOL startInForeground();
BOOL startAsUser(HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
void shutdown(IpcServer &ipcServer, int timeout = kDefaultShutdownTimeout);
DWORD waitForExit();
void createPipes();
std::string readStdOutput();
std::string readStdError();
PROCESS_INFORMATION info() const
{
return m_info;
}
static void shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer, int timeout = kDefaultShutdownTimeout);
private:
void setStartupInfo(STARTUPINFO &si);
static std::string readOutput(HANDLE handle);
std::string m_command;
HANDLE m_stdOutput;
HANDLE m_stdError;
HANDLE m_outputPipe = nullptr;
HANDLE m_errorPipe = nullptr;
PROCESS_INFORMATION m_info;
BOOL m_createProcessResult = FALSE;
};
} // namespace deskflow::platform

View File

@ -158,23 +158,3 @@ BOOL MSWindowsSession::nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry)
return gotEntry;
}
std::string MSWindowsSession::getActiveDesktopName()
{
std::string result;
try {
HDESK hd = OpenInputDesktop(0, TRUE, GENERIC_READ);
if (hd != NULL) {
DWORD size;
GetUserObjectInformation(hd, UOI_NAME, NULL, 0, &size);
TCHAR *name = (TCHAR *)alloca(size + sizeof(TCHAR));
GetUserObjectInformation(hd, UOI_NAME, name, size, &size);
result = name;
CloseDesktop(hd);
}
} catch (std::exception &error) {
LOG((CLOG_ERR "failed to get active desktop name: %s", error.what()));
}
return result;
}

View File

@ -19,7 +19,6 @@ public:
MSWindowsSession();
~MSWindowsSession();
//!
/*!
Returns true if the session ID has changed since updateActiveSession was
called.
@ -27,18 +26,14 @@ public:
BOOL hasChanged();
bool isProcessInSession(const char *name, PHANDLE process);
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
void updateActiveSession();
DWORD getActiveSessionId()
{
return m_activeSessionId;
}
void updateActiveSession();
std::string getActiveDesktopName();
private:
BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry);

View File

@ -10,7 +10,6 @@
#include "arch/Arch.h"
#include "arch/win32/XArchWindows.h"
#include "base/Log.h"
#include "base/String.h"
#include "base/TMethodJob.h"
#include "base/log_outputters.h"
#include "common/ipc.h"
@ -31,9 +30,6 @@
#define CURRENT_PROCESS_ID 0
#define MAXIMUM_WAIT_TIME 3
// TODO: maybe this should be \winlogon if we have logonui.exe?
static char g_desktopName[] = "winsta0\\Default";
namespace {
std::string trimDesktopName(const std::string &nameFromTraces)
{
@ -50,17 +46,6 @@ std::string trimDesktopName(const std::string &nameFromTraces)
return name;
}
bool isDesktopRunnable(const std::string &desktopName)
{
const std::string winlogon = "Winlogon";
bool isNotLoginScreen = std::strncmp(desktopName.c_str(), winlogon.c_str(), winlogon.length());
const auto setting = ARCH->setting("runOnLoginScreen");
bool runOnLoginScreen = (setting.empty() || setting == "true");
return (runOnLoginScreen || isNotLoginScreen);
}
} // namespace
enum
@ -79,15 +64,14 @@ MSWindowsWatchdog::MSWindowsWatchdog(
m_autoDetectCommand(autoDetectCommand),
m_monitoring(true),
m_commandChanged(false),
m_stdOutWrite(NULL),
m_stdOutRead(NULL),
m_outputWritePipe(nullptr),
m_outputReadPipe(nullptr),
m_ipcServer(ipcServer),
m_ipcLogOutputter(ipcLogOutputter),
m_elevateProcess(false),
m_processFailures(0),
m_processRunning(false),
m_processStarted(false),
m_fileLogOutputter(NULL),
m_autoElevated(false),
m_ready(false),
m_foreground(foreground)
{
@ -153,22 +137,30 @@ MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES s
}
HANDLE
MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security)
MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedToken)
{
m_session.updateActiveSession();
// always elevate if we are at the vista/7 login screen. we could also
// elevate for the uac dialog (consent.exe) but this would be pointless,
// since deskflow would re-launch as non-elevated after the desk switch,
// and so would be unusable with the new elevated process taking focus.
if (m_elevateProcess || m_autoElevated || m_session.isProcessInSession("logonui.exe", NULL)) {
if (elevatedToken || m_session.isProcessInSession("logonui.exe", NULL)) {
LOG((CLOG_DEBUG "getting elevated token, %s", (m_elevateProcess ? "elevation required" : "at login screen")));
LOG((CLOG_DEBUG "getting elevated token, %s", (elevatedToken ? "elevation required" : "at login screen")));
HANDLE process;
if (!m_session.isProcessInSession("winlogon.exe", &process)) {
throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe");
}
return duplicateProcessToken(process, security);
try {
return duplicateProcessToken(process, security);
} catch (XArch &e) {
LOG_ERR("failed to duplicate user token from winlogon.exe");
CloseHandle(process);
throw e;
}
} else {
LOG((CLOG_DEBUG "getting non-elevated token"));
return m_session.getUserToken(security);
@ -177,12 +169,13 @@ MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security)
void MSWindowsWatchdog::mainLoop(void *)
{
LOG_DEBUG("starting main loop");
shutdownExistingProcesses();
SendSas sendSasFunc = NULL;
HINSTANCE sasLib = LoadLibrary("sas.dll");
if (sasLib) {
LOG((CLOG_DEBUG "found sas.dll"));
LOG((CLOG_DEBUG "loaded sas.dll, used to simulate ctrl-alt-del"));
sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
}
@ -191,26 +184,24 @@ void MSWindowsWatchdog::mainLoop(void *)
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) {
if (!CreatePipe(&m_outputReadPipe, &m_outputWritePipe, &saAttr, 0)) {
LOG((CLOG_ERR "could not create output pipe"));
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"));
if (m_processStarted && getCommand().empty()) {
LOG((CLOG_DEBUG "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));
LOG((CLOG_WARN "backing off, wait=%ds, failures=%d", timeout, m_processFailures));
ARCH->sleep(timeout);
}
@ -230,12 +221,12 @@ void MSWindowsWatchdog::mainLoop(void *)
}
}
if (m_processRunning && !isProcessActive()) {
if (m_processStarted && !isProcessRunning()) {
m_processFailures++;
m_processRunning = false;
m_processStarted = false;
LOG((CLOG_WARN "detected application not running, pid=%d", m_processInfo.dwProcessId));
LOG((CLOG_WARN "detected application not running, pid=%d", m_process->info().dwProcessId));
}
if (sendSasFunc != NULL) {
@ -260,28 +251,34 @@ void MSWindowsWatchdog::mainLoop(void *)
} catch (std::exception &e) {
LOG((CLOG_CRIT "failed to launch, error: %s", e.what()));
m_processFailures++;
m_processRunning = false;
m_processStarted = false;
continue;
} catch (...) {
LOG((CLOG_CRIT "failed to launch, unknown error."));
m_processFailures++;
m_processRunning = false;
m_processStarted = false;
continue;
}
}
if (m_processRunning) {
if (m_process != nullptr) {
LOG((CLOG_DEBUG "terminated running process on exit"));
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
m_process->shutdown(m_ipcServer);
m_process.reset();
m_processStarted = false;
}
LOG((CLOG_DEBUG "watchdog main thread finished"));
}
bool MSWindowsWatchdog::isProcessActive()
bool MSWindowsWatchdog::isProcessRunning()
{
if (m_process == nullptr) {
return false;
}
DWORD exitCode;
GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
GetExitCodeProcess(m_process->info().hProcess, &exitCode);
return exitCode == STILL_ACTIVE;
}
@ -292,64 +289,63 @@ void MSWindowsWatchdog::setFileLogOutputter(FileLogOutputter *outputter)
void MSWindowsWatchdog::startProcess()
{
LOG_INFO("daemon starting new process");
if (m_command.empty()) {
throw XMSWindowsWatchdogError("cannot start process, command is empty");
}
m_commandChanged = false;
if (m_processRunning) {
if (m_process != nullptr) {
LOG((CLOG_DEBUG "closing existing process to make way for new one"));
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
m_processRunning = false;
m_process->shutdown(m_ipcServer);
m_process.reset();
m_processStarted = false;
}
m_process = std::make_unique<deskflow::platform::MSWindowsProcess>(m_command, m_outputWritePipe, m_outputWritePipe);
BOOL createRet;
if (m_foreground) {
LOG((CLOG_DEBUG "starting command in foreground"));
createRet = startProcessInForeground(m_command);
LOG_DEBUG("starting command in foreground");
createRet = m_process->startInForeground();
} else {
LOG((CLOG_DEBUG "starting command as session user"));
m_session.updateActiveSession();
LOG_DEBUG("starting new process in user session");
LOG_DEBUG("getting active desktop name");
const auto activeDesktopName = runActiveDesktopUtility();
LOG_DEBUG("active desktop name: %s", activeDesktopName.c_str());
// When we're at a UAC prompt, lock screen, or the login screen, Windows switches to the Winlogon desktop.
const auto isOnSecureDesktop = activeDesktopName == "Winlogon";
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
HANDLE userToken = getUserToken(&sa, isOnSecureDesktop || m_elevateProcess);
getActiveDesktop(&sa);
if (!isDesktopRunnable(m_activeDesktop)) {
LOG((CLOG_INFO, "starting on the login screen is disabled"));
return;
}
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
HANDLE userToken = getUserToken(&sa);
m_elevateProcess = m_autoElevated ? m_autoElevated : m_elevateProcess;
m_autoElevated = false;
// patch by Jack Zhou and Henry Tung
// set UIAccess to fix Windows 8 GUI interaction
DWORD uiAccess = 1;
SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD));
createRet = startProcessAsUser(m_command, userToken, &sa);
createRet = m_process->startAsUser(userToken, &sa);
}
if (!createRet) {
LOG((CLOG_CRIT "could not launch command"));
DWORD exitCode = 0;
GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
GetExitCodeProcess(m_process->info().hProcess, &exitCode);
LOG((CLOG_ERR "exit code: %d", exitCode));
throw XArch(new XArchEvalWindows);
} else {
// wait for program to fail.
ARCH->sleep(1);
if (!isProcessActive()) {
closeProcessHandles(m_processInfo.dwProcessId);
if (!isProcessRunning()) {
m_process.reset();
throw XMSWindowsWatchdogError("process immediately stopped");
}
m_processRunning = true;
m_processStarted = true;
m_processFailures = 0;
LOG((CLOG_DEBUG "started core process from daemon"));
@ -360,70 +356,9 @@ void MSWindowsWatchdog::startProcess()
}
}
void MSWindowsWatchdog::setStartupInfo(STARTUPINFO &si)
{
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = g_desktopName;
si.hStdError = m_stdOutWrite;
si.hStdOutput = m_stdOutWrite;
si.dwFlags |= STARTF_USESTDHANDLES;
}
BOOL MSWindowsWatchdog::startProcessInForeground(std::string &command)
{
// clear, as we're reusing process info struct
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
// show the console window when in foreground mode,
// so we can close it gracefully, but minimize it
// so it doesn't get in the way.
STARTUPINFO si;
setStartupInfo(si);
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZE;
BOOL result = CreateProcess(NULL, LPSTR(command.c_str()), NULL, NULL, TRUE, 0, NULL, NULL, &si, &m_processInfo);
m_children.insert(std::make_pair(m_processInfo.dwProcessId, m_processInfo));
return result;
}
BOOL MSWindowsWatchdog::startProcessAsUser(std::string &command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa)
{
// clear, as we're reusing process info struct
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
STARTUPINFO si;
setStartupInfo(si);
LPVOID environment;
BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
if (!blockRet) {
LOG((CLOG_ERR "could not create environment block"));
throw XArch(new XArchEvalWindows);
}
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(command.c_str()), sa, NULL, TRUE, creationFlags, environment, NULL, &si, &m_processInfo
);
m_children.insert(std::make_pair(m_processInfo.dwProcessId, m_processInfo));
DestroyEnvironmentBlock(environment);
CloseHandle(userToken);
return createRet;
}
void MSWindowsWatchdog::setCommand(const std::string &command, bool elevate)
{
LOG((CLOG_INFO "service command updated"));
LOG_DEBUG("command parameters updated");
m_command = command;
m_elevateProcess = elevate;
m_commandChanged = true;
@ -463,7 +398,7 @@ void MSWindowsWatchdog::outputLoop(void *)
while (m_monitoring) {
DWORD bytesRead;
BOOL success = ReadFile(m_stdOutRead, buffer, kOutputBufferSize, &bytesRead, NULL);
BOOL success = ReadFile(m_outputReadPipe, buffer, kOutputBufferSize, &bytesRead, NULL);
// assume the process has gone away? slow down
// the reads until another one turns up.
@ -472,8 +407,6 @@ void MSWindowsWatchdog::outputLoop(void *)
} else {
buffer[bytesRead] = '\0';
testOutput(buffer);
m_ipcLogOutputter.write(kINFO, buffer);
if (m_fileLogOutputter != NULL) {
@ -493,48 +426,16 @@ void MSWindowsWatchdog::outputLoop(void *)
}
}
void MSWindowsWatchdog::shutdownProcess(HANDLE handle, DWORD pid, int timeout)
{
DWORD exitCode;
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
return;
}
IpcShutdownMessage shutdown;
m_ipcServer.send(shutdown, IpcClientType::Node);
// wait for process to exit gracefully.
double start = ARCH->time();
while (true) {
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
// yay, we got a graceful shutdown. there should be no hook in use errors!
LOG((CLOG_DEBUG "process %d was shutdown gracefully", pid));
break;
} else {
double elapsed = (ARCH->time() - start);
if (elapsed > timeout) {
// if timeout reached, kill forcefully.
// calling TerminateProcess on deskflow is very bad!
// it causes the hook DLL to stay loaded in some apps,
// making it impossible to start deskflow again.
LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
TerminateProcess(handle, kExitSuccess);
break;
}
ARCH->sleep(1);
}
}
closeProcessHandles(pid);
}
void MSWindowsWatchdog::shutdownExistingProcesses()
{
LOG_INFO("deamon shutting down existing processes");
if (m_process != nullptr) {
m_process->shutdown(m_ipcServer);
m_process.reset();
m_processStarted = false;
}
// first we need to take a snapshot of the running processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, CURRENT_PROCESS_ID);
if (snapshot == INVALID_HANDLE_VALUE) {
@ -565,7 +466,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
_stricmp(entry.szExeFile, "deskflow-core.exe") == 0) {
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
shutdownProcess(handle, entry.th32ProcessID, 10);
deskflow::platform::MSWindowsProcess::shutdown(handle, entry.th32ProcessID, m_ipcServer);
CloseHandle(handle);
}
}
@ -578,64 +479,53 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
if (err != ERROR_NO_MORE_FILES) {
// only worry about error if it's not the end of the snapshot
LOG((CLOG_ERR "could not get subsiquent process entry"));
LOG((CLOG_ERR "could not get next process entry"));
throw XArch(new XArchEvalWindows);
}
}
}
clearAllChildren();
CloseHandle(snapshot);
m_processRunning = false;
}
void MSWindowsWatchdog::getActiveDesktop(LPSECURITY_ATTRIBUTES security)
std::string MSWindowsWatchdog::runActiveDesktopUtility()
{
std::string installedDir = ARCH->getInstalledDirectory();
if (!installedDir.empty()) {
MSWindowsSession session;
std::string name = session.getActiveDesktopName();
if (name.empty()) {
LOG((CLOG_DEBUG "no active desktop in current session"));
} else {
LOG((CLOG_INFO "active desktop name: %s", name.c_str()));
}
}
}
const auto installDir = ARCH->getInstalledDirectory();
const auto coreBinPath = installDir + "\\deskflow-server.exe";
std::string utilityCommand = "\"" + coreBinPath + "\" --active-desktop";
void MSWindowsWatchdog::testOutput(std::string buffer)
{
// HACK: check standard output seems hacky.
size_t i = buffer.find(g_activeDesktop);
if (i != std::string::npos) {
size_t s = sizeof(g_activeDesktop);
std::string defaultScreen = "Default";
m_activeDesktop = trimDesktopName(buffer.substr(i + s - 1));
m_autoElevated = std::strncmp(m_activeDesktop.c_str(), defaultScreen.c_str(), defaultScreen.length());
LOG_DEBUG("starting active desktop utility: %s", utilityCommand.c_str());
ARCH->lockMutex(m_mutex);
m_ready = true;
ARCH->broadcastCondVar(m_condVar);
ARCH->unlockMutex(m_mutex);
}
}
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
HANDLE userToken = getUserToken(&sa, true);
void MSWindowsWatchdog::closeProcessHandles(unsigned long pid, bool removeFromMap)
{
auto processInfo = m_children.find(pid);
if (processInfo != m_children.end()) {
CloseHandle(processInfo->second.hProcess);
CloseHandle(processInfo->second.hThread);
if (removeFromMap) {
m_children.erase(processInfo);
}
}
}
deskflow::platform::MSWindowsProcess process(utilityCommand);
process.createPipes();
void MSWindowsWatchdog::clearAllChildren()
{
for (auto it = m_children.begin(); it != m_children.end(); ++it) {
closeProcessHandles(it->second.dwThreadId, false);
if (!process.startAsUser(userToken, &sa)) {
LOG_ERR("could not start active desktop process");
throw XArch(new XArchEvalWindows());
}
m_children.clear();
LOG_DEBUG("started active desktop process, pid=%d", process.info().dwProcessId);
if (const auto exitCode = process.waitForExit(); exitCode != kExitSuccess) {
LOG_ERR("active desktop process, exit code: %d", exitCode);
throw XMSWindowsWatchdogError("could not get active desktop");
}
LOG_DEBUG("reading active desktop std error");
if (const auto error = process.readStdError(); !error.empty()) {
LOG_WARN("active desktop process, error: %s", error.c_str());
}
LOG_DEBUG("reading active desktop std output");
auto output = process.readStdOutput();
if (output.empty()) {
LOG_ERR("could not get active desktop, no output");
throw XMSWindowsWatchdogError("could not get active desktop");
}
output.erase(output.find_last_not_of("\r\n") + 1);
return output;
}

View File

@ -9,14 +9,15 @@
#include "arch/IArchMultithread.h"
#include "deskflow/XDeskflow.h"
#include "platform/MSWindowsProcess.h"
#include "platform/MSWindowsSession.h"
#include <map>
#include <string>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <memory>
#include <string>
class Thread;
class IpcLogOutputter;
class IpcServer;
@ -32,36 +33,28 @@ public:
std::string getCommand() const;
void setCommand(const std::string &command, bool elevate);
void stop();
bool isProcessActive();
bool isProcessRunning();
void setFileLogOutputter(FileLogOutputter *outputter);
private:
void mainLoop(void *);
void outputLoop(void *);
void shutdownProcess(HANDLE handle, DWORD pid, int timeout);
void shutdownExistingProcesses();
HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedToken);
void startProcess();
BOOL startProcessAsUser(std::string &command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
BOOL startProcessInForeground(std::string &command);
void sendSas();
void getActiveDesktop(LPSECURITY_ATTRIBUTES security);
void testOutput(std::string buffer);
void setStartupInfo(STARTUPINFO &si);
void checkChildren();
/**
* @brief This closes the handles held to a child thread
* @param pid the ID of the process to kill, will do nothing if PID is not a
* valid child
* @param removeFromMap should the function remove the item from the children
* map
* @brief Re-run the process to get the active desktop name.
*
* It is necessary to run a utility process because the daemon runs in session 0, which does not
* have access to the active desktop, and so cannot query it's name.
*
* @return std::string The name of the active desktop.
*/
void closeProcessHandles(unsigned long pid, bool removeFromMap = true);
/**
* @brief This kills off all children's handles created by this process
*/
void clearAllChildren();
std::string runActiveDesktopUtility();
private:
Thread *m_thread;
@ -69,28 +62,22 @@ private:
std::string m_command;
bool m_monitoring;
bool m_commandChanged;
HANDLE m_stdOutWrite;
HANDLE m_stdOutRead;
HANDLE m_outputWritePipe;
HANDLE m_outputReadPipe;
Thread *m_outputThread;
IpcServer &m_ipcServer;
IpcLogOutputter &m_ipcLogOutputter;
bool m_elevateProcess;
MSWindowsSession m_session;
PROCESS_INFORMATION m_processInfo;
int m_processFailures;
bool m_processRunning;
bool m_processStarted;
FileLogOutputter *m_fileLogOutputter;
bool m_autoElevated;
ArchMutex m_mutex;
ArchCond m_condVar;
bool m_ready;
bool m_foreground;
std::string m_activeDesktop;
/// @brief Save the info of all process made
/// We will use this to track all processes we make and
/// kill off handels and children that we no longer need
std::map<unsigned long, PROCESS_INFORMATION> m_children;
std::unique_ptr<deskflow::platform::MSWindowsProcess> m_process;
};
//! Relauncher error