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:
parent
3de2c1bcf0
commit
c9a360a144
11 changed files with 453 additions and 277 deletions
|
|
@ -28,6 +28,30 @@ int main(int argc, char **argv)
|
||||||
Log log;
|
Log log;
|
||||||
EventQueue events;
|
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);
|
ServerApp app(&events);
|
||||||
return app.run(argc, argv);
|
return app.run(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "arch/win32/ArchMiscWindows.h"
|
#include "arch/win32/ArchMiscWindows.h"
|
||||||
#include "arch/win32/ArchDaemonWindows.h"
|
#include "arch/win32/ArchDaemonWindows.h"
|
||||||
|
#include "arch/win32/XArchWindows.h"
|
||||||
#include "base/Log.h"
|
#include "base/Log.h"
|
||||||
#include "common/constants.h"
|
#include "common/constants.h"
|
||||||
|
|
||||||
|
|
@ -457,3 +458,19 @@ void ArchMiscWindows::setInstanceWin32(HINSTANCE instance)
|
||||||
assert(instance != NULL);
|
assert(instance != NULL);
|
||||||
s_instanceWin32 = instance;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,12 +155,11 @@ public:
|
||||||
static bool getParentProcessName(std::string &name);
|
static bool getParentProcessName(std::string &name);
|
||||||
|
|
||||||
static HINSTANCE instanceWin32();
|
static HINSTANCE instanceWin32();
|
||||||
|
|
||||||
static void setInstanceWin32(HINSTANCE instance);
|
static void setInstanceWin32(HINSTANCE instance);
|
||||||
|
|
||||||
static BOOL WINAPI getProcessEntry(PROCESSENTRY32 &entry, DWORD processID);
|
static BOOL WINAPI getProcessEntry(PROCESSENTRY32 &entry, DWORD processID);
|
||||||
static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32 &entry);
|
static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32 &entry);
|
||||||
static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32 &entry);
|
static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32 &entry);
|
||||||
|
static std::string getActiveDesktopName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//! Open and return a registry key, closing the parent key
|
//! Open and return a registry key, closing the parent key
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ void DaemonApp::handleIpcMessage(const Event &e, void *)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SYSAPI_WIN32
|
#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()));
|
LOG((CLOG_INFO "service status: %s", watchdogStatus.c_str()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ if(WIN32)
|
||||||
MSWindowsKeyState.h
|
MSWindowsKeyState.h
|
||||||
MSWindowsPowerManager.cpp
|
MSWindowsPowerManager.cpp
|
||||||
MSWindowsPowerManager.h
|
MSWindowsPowerManager.h
|
||||||
|
MSWindowsProcess.cpp
|
||||||
|
MSWindowsProcess.h
|
||||||
MSWindowsScreen.cpp
|
MSWindowsScreen.cpp
|
||||||
MSWindowsScreen.h
|
MSWindowsScreen.h
|
||||||
MSWindowsScreenSaver.cpp
|
MSWindowsScreenSaver.cpp
|
||||||
|
|
|
||||||
225
src/lib/platform/MSWindowsProcess.cpp
Normal file
225
src/lib/platform/MSWindowsProcess.cpp
Normal 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
|
||||||
57
src/lib/platform/MSWindowsProcess.h
Normal file
57
src/lib/platform/MSWindowsProcess.h
Normal 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
|
||||||
|
|
@ -158,23 +158,3 @@ BOOL MSWindowsSession::nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry)
|
||||||
|
|
||||||
return gotEntry;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ public:
|
||||||
MSWindowsSession();
|
MSWindowsSession();
|
||||||
~MSWindowsSession();
|
~MSWindowsSession();
|
||||||
|
|
||||||
//!
|
|
||||||
/*!
|
/*!
|
||||||
Returns true if the session ID has changed since updateActiveSession was
|
Returns true if the session ID has changed since updateActiveSession was
|
||||||
called.
|
called.
|
||||||
|
|
@ -27,18 +26,14 @@ public:
|
||||||
BOOL hasChanged();
|
BOOL hasChanged();
|
||||||
|
|
||||||
bool isProcessInSession(const char *name, PHANDLE process);
|
bool isProcessInSession(const char *name, PHANDLE process);
|
||||||
|
|
||||||
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
|
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
|
||||||
|
void updateActiveSession();
|
||||||
|
|
||||||
DWORD getActiveSessionId()
|
DWORD getActiveSessionId()
|
||||||
{
|
{
|
||||||
return m_activeSessionId;
|
return m_activeSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateActiveSession();
|
|
||||||
|
|
||||||
std::string getActiveDesktopName();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry);
|
BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#include "arch/Arch.h"
|
#include "arch/Arch.h"
|
||||||
#include "arch/win32/XArchWindows.h"
|
#include "arch/win32/XArchWindows.h"
|
||||||
#include "base/Log.h"
|
#include "base/Log.h"
|
||||||
#include "base/String.h"
|
|
||||||
#include "base/TMethodJob.h"
|
#include "base/TMethodJob.h"
|
||||||
#include "base/log_outputters.h"
|
#include "base/log_outputters.h"
|
||||||
#include "common/ipc.h"
|
#include "common/ipc.h"
|
||||||
|
|
@ -31,9 +30,6 @@
|
||||||
#define CURRENT_PROCESS_ID 0
|
#define CURRENT_PROCESS_ID 0
|
||||||
#define MAXIMUM_WAIT_TIME 3
|
#define MAXIMUM_WAIT_TIME 3
|
||||||
|
|
||||||
// TODO: maybe this should be \winlogon if we have logonui.exe?
|
|
||||||
static char g_desktopName[] = "winsta0\\Default";
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::string trimDesktopName(const std::string &nameFromTraces)
|
std::string trimDesktopName(const std::string &nameFromTraces)
|
||||||
{
|
{
|
||||||
|
|
@ -50,17 +46,6 @@ std::string trimDesktopName(const std::string &nameFromTraces)
|
||||||
return name;
|
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
|
} // namespace
|
||||||
|
|
||||||
enum
|
enum
|
||||||
|
|
@ -79,15 +64,14 @@ MSWindowsWatchdog::MSWindowsWatchdog(
|
||||||
m_autoDetectCommand(autoDetectCommand),
|
m_autoDetectCommand(autoDetectCommand),
|
||||||
m_monitoring(true),
|
m_monitoring(true),
|
||||||
m_commandChanged(false),
|
m_commandChanged(false),
|
||||||
m_stdOutWrite(NULL),
|
m_outputWritePipe(nullptr),
|
||||||
m_stdOutRead(NULL),
|
m_outputReadPipe(nullptr),
|
||||||
m_ipcServer(ipcServer),
|
m_ipcServer(ipcServer),
|
||||||
m_ipcLogOutputter(ipcLogOutputter),
|
m_ipcLogOutputter(ipcLogOutputter),
|
||||||
m_elevateProcess(false),
|
m_elevateProcess(false),
|
||||||
m_processFailures(0),
|
m_processFailures(0),
|
||||||
m_processRunning(false),
|
m_processStarted(false),
|
||||||
m_fileLogOutputter(NULL),
|
m_fileLogOutputter(NULL),
|
||||||
m_autoElevated(false),
|
|
||||||
m_ready(false),
|
m_ready(false),
|
||||||
m_foreground(foreground)
|
m_foreground(foreground)
|
||||||
{
|
{
|
||||||
|
|
@ -153,22 +137,30 @@ MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES s
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLE
|
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
|
// 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,
|
// elevate for the uac dialog (consent.exe) but this would be pointless,
|
||||||
// since deskflow would re-launch as non-elevated after the desk switch,
|
// since deskflow would re-launch as non-elevated after the desk switch,
|
||||||
// and so would be unusable with the new elevated process taking focus.
|
// 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;
|
HANDLE process;
|
||||||
if (!m_session.isProcessInSession("winlogon.exe", &process)) {
|
if (!m_session.isProcessInSession("winlogon.exe", &process)) {
|
||||||
throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe");
|
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 {
|
} else {
|
||||||
LOG((CLOG_DEBUG "getting non-elevated token"));
|
LOG((CLOG_DEBUG "getting non-elevated token"));
|
||||||
return m_session.getUserToken(security);
|
return m_session.getUserToken(security);
|
||||||
|
|
@ -177,12 +169,13 @@ MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security)
|
||||||
|
|
||||||
void MSWindowsWatchdog::mainLoop(void *)
|
void MSWindowsWatchdog::mainLoop(void *)
|
||||||
{
|
{
|
||||||
|
LOG_DEBUG("starting main loop");
|
||||||
shutdownExistingProcesses();
|
shutdownExistingProcesses();
|
||||||
|
|
||||||
SendSas sendSasFunc = NULL;
|
SendSas sendSasFunc = NULL;
|
||||||
HINSTANCE sasLib = LoadLibrary("sas.dll");
|
HINSTANCE sasLib = LoadLibrary("sas.dll");
|
||||||
if (sasLib) {
|
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");
|
sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,26 +184,24 @@ void MSWindowsWatchdog::mainLoop(void *)
|
||||||
saAttr.bInheritHandle = TRUE;
|
saAttr.bInheritHandle = TRUE;
|
||||||
saAttr.lpSecurityDescriptor = NULL;
|
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());
|
throw XArch(new XArchEvalWindows());
|
||||||
}
|
}
|
||||||
|
|
||||||
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
|
|
||||||
|
|
||||||
while (m_monitoring) {
|
while (m_monitoring) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (m_processRunning && getCommand().empty()) {
|
if (m_processStarted && getCommand().empty()) {
|
||||||
LOG((CLOG_INFO "process started but command is empty, shutting down"));
|
LOG((CLOG_DEBUG "process started but command is empty, shutting down"));
|
||||||
shutdownExistingProcesses();
|
shutdownExistingProcesses();
|
||||||
m_processRunning = false;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_processFailures != 0) {
|
if (m_processFailures != 0) {
|
||||||
// increasing backoff period, maximum of 10 seconds.
|
// increasing backoff period, maximum of 10 seconds.
|
||||||
int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10;
|
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);
|
ARCH->sleep(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,12 +221,12 @@ void MSWindowsWatchdog::mainLoop(void *)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_processRunning && !isProcessActive()) {
|
if (m_processStarted && !isProcessRunning()) {
|
||||||
|
|
||||||
m_processFailures++;
|
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) {
|
if (sendSasFunc != NULL) {
|
||||||
|
|
@ -260,28 +251,34 @@ void MSWindowsWatchdog::mainLoop(void *)
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
LOG((CLOG_CRIT "failed to launch, error: %s", e.what()));
|
LOG((CLOG_CRIT "failed to launch, error: %s", e.what()));
|
||||||
m_processFailures++;
|
m_processFailures++;
|
||||||
m_processRunning = false;
|
m_processStarted = false;
|
||||||
continue;
|
continue;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
LOG((CLOG_CRIT "failed to launch, unknown error."));
|
LOG((CLOG_CRIT "failed to launch, unknown error."));
|
||||||
m_processFailures++;
|
m_processFailures++;
|
||||||
m_processRunning = false;
|
m_processStarted = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_processRunning) {
|
if (m_process != nullptr) {
|
||||||
LOG((CLOG_DEBUG "terminated running process on exit"));
|
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"));
|
LOG((CLOG_DEBUG "watchdog main thread finished"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MSWindowsWatchdog::isProcessActive()
|
bool MSWindowsWatchdog::isProcessRunning()
|
||||||
{
|
{
|
||||||
|
if (m_process == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DWORD exitCode;
|
DWORD exitCode;
|
||||||
GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
|
GetExitCodeProcess(m_process->info().hProcess, &exitCode);
|
||||||
return exitCode == STILL_ACTIVE;
|
return exitCode == STILL_ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,64 +289,63 @@ void MSWindowsWatchdog::setFileLogOutputter(FileLogOutputter *outputter)
|
||||||
|
|
||||||
void MSWindowsWatchdog::startProcess()
|
void MSWindowsWatchdog::startProcess()
|
||||||
{
|
{
|
||||||
|
LOG_INFO("daemon starting new process");
|
||||||
|
|
||||||
if (m_command.empty()) {
|
if (m_command.empty()) {
|
||||||
throw XMSWindowsWatchdogError("cannot start process, command is empty");
|
throw XMSWindowsWatchdogError("cannot start process, command is empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_commandChanged = false;
|
m_commandChanged = false;
|
||||||
|
|
||||||
if (m_processRunning) {
|
if (m_process != nullptr) {
|
||||||
LOG((CLOG_DEBUG "closing existing process to make way for new one"));
|
LOG((CLOG_DEBUG "closing existing process to make way for new one"));
|
||||||
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
|
m_process->shutdown(m_ipcServer);
|
||||||
m_processRunning = false;
|
m_process.reset();
|
||||||
|
m_processStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_process = std::make_unique<deskflow::platform::MSWindowsProcess>(m_command, m_outputWritePipe, m_outputWritePipe);
|
||||||
|
|
||||||
BOOL createRet;
|
BOOL createRet;
|
||||||
if (m_foreground) {
|
if (m_foreground) {
|
||||||
LOG((CLOG_DEBUG "starting command in foreground"));
|
LOG_DEBUG("starting command in foreground");
|
||||||
createRet = startProcessInForeground(m_command);
|
createRet = m_process->startInForeground();
|
||||||
} else {
|
} else {
|
||||||
LOG((CLOG_DEBUG "starting command as session user"));
|
LOG_DEBUG("starting new process in user session");
|
||||||
m_session.updateActiveSession();
|
|
||||||
|
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;
|
SECURITY_ATTRIBUTES sa;
|
||||||
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
|
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
|
// set UIAccess to fix Windows 8 GUI interaction
|
||||||
DWORD uiAccess = 1;
|
DWORD uiAccess = 1;
|
||||||
SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD));
|
SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD));
|
||||||
|
|
||||||
createRet = startProcessAsUser(m_command, userToken, &sa);
|
createRet = m_process->startAsUser(userToken, &sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!createRet) {
|
if (!createRet) {
|
||||||
LOG((CLOG_CRIT "could not launch command"));
|
LOG((CLOG_CRIT "could not launch command"));
|
||||||
DWORD exitCode = 0;
|
DWORD exitCode = 0;
|
||||||
GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
|
GetExitCodeProcess(m_process->info().hProcess, &exitCode);
|
||||||
LOG((CLOG_ERR "exit code: %d", exitCode));
|
LOG((CLOG_ERR "exit code: %d", exitCode));
|
||||||
throw XArch(new XArchEvalWindows);
|
throw XArch(new XArchEvalWindows);
|
||||||
} else {
|
} else {
|
||||||
// wait for program to fail.
|
// wait for program to fail.
|
||||||
ARCH->sleep(1);
|
ARCH->sleep(1);
|
||||||
if (!isProcessActive()) {
|
if (!isProcessRunning()) {
|
||||||
closeProcessHandles(m_processInfo.dwProcessId);
|
m_process.reset();
|
||||||
throw XMSWindowsWatchdogError("process immediately stopped");
|
throw XMSWindowsWatchdogError("process immediately stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_processRunning = true;
|
m_processStarted = true;
|
||||||
m_processFailures = 0;
|
m_processFailures = 0;
|
||||||
|
|
||||||
LOG((CLOG_DEBUG "started core process from daemon"));
|
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)
|
void MSWindowsWatchdog::setCommand(const std::string &command, bool elevate)
|
||||||
{
|
{
|
||||||
LOG((CLOG_INFO "service command updated"));
|
LOG_DEBUG("command parameters updated");
|
||||||
m_command = command;
|
m_command = command;
|
||||||
m_elevateProcess = elevate;
|
m_elevateProcess = elevate;
|
||||||
m_commandChanged = true;
|
m_commandChanged = true;
|
||||||
|
|
@ -463,7 +398,7 @@ void MSWindowsWatchdog::outputLoop(void *)
|
||||||
while (m_monitoring) {
|
while (m_monitoring) {
|
||||||
|
|
||||||
DWORD bytesRead;
|
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
|
// assume the process has gone away? slow down
|
||||||
// the reads until another one turns up.
|
// the reads until another one turns up.
|
||||||
|
|
@ -472,8 +407,6 @@ void MSWindowsWatchdog::outputLoop(void *)
|
||||||
} else {
|
} else {
|
||||||
buffer[bytesRead] = '\0';
|
buffer[bytesRead] = '\0';
|
||||||
|
|
||||||
testOutput(buffer);
|
|
||||||
|
|
||||||
m_ipcLogOutputter.write(kINFO, buffer);
|
m_ipcLogOutputter.write(kINFO, buffer);
|
||||||
|
|
||||||
if (m_fileLogOutputter != NULL) {
|
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()
|
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
|
// first we need to take a snapshot of the running processes
|
||||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, CURRENT_PROCESS_ID);
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, CURRENT_PROCESS_ID);
|
||||||
if (snapshot == INVALID_HANDLE_VALUE) {
|
if (snapshot == INVALID_HANDLE_VALUE) {
|
||||||
|
|
@ -565,7 +466,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
|
||||||
_stricmp(entry.szExeFile, "deskflow-core.exe") == 0) {
|
_stricmp(entry.szExeFile, "deskflow-core.exe") == 0) {
|
||||||
|
|
||||||
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
|
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);
|
CloseHandle(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -578,64 +479,53 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
|
||||||
if (err != ERROR_NO_MORE_FILES) {
|
if (err != ERROR_NO_MORE_FILES) {
|
||||||
|
|
||||||
// only worry about error if it's not the end of the snapshot
|
// 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);
|
throw XArch(new XArchEvalWindows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAllChildren();
|
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
m_processRunning = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSWindowsWatchdog::getActiveDesktop(LPSECURITY_ATTRIBUTES security)
|
std::string MSWindowsWatchdog::runActiveDesktopUtility()
|
||||||
{
|
{
|
||||||
std::string installedDir = ARCH->getInstalledDirectory();
|
const auto installDir = ARCH->getInstalledDirectory();
|
||||||
if (!installedDir.empty()) {
|
const auto coreBinPath = installDir + "\\deskflow-server.exe";
|
||||||
MSWindowsSession session;
|
std::string utilityCommand = "\"" + coreBinPath + "\" --active-desktop";
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSWindowsWatchdog::testOutput(std::string buffer)
|
LOG_DEBUG("starting active desktop utility: %s", utilityCommand.c_str());
|
||||||
{
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
ARCH->lockMutex(m_mutex);
|
SECURITY_ATTRIBUTES sa;
|
||||||
m_ready = true;
|
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
|
||||||
ARCH->broadcastCondVar(m_condVar);
|
HANDLE userToken = getUserToken(&sa, true);
|
||||||
ARCH->unlockMutex(m_mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSWindowsWatchdog::closeProcessHandles(unsigned long pid, bool removeFromMap)
|
deskflow::platform::MSWindowsProcess process(utilityCommand);
|
||||||
{
|
process.createPipes();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSWindowsWatchdog::clearAllChildren()
|
if (!process.startAsUser(userToken, &sa)) {
|
||||||
{
|
LOG_ERR("could not start active desktop process");
|
||||||
for (auto it = m_children.begin(); it != m_children.end(); ++it) {
|
throw XArch(new XArchEvalWindows());
|
||||||
closeProcessHandles(it->second.dwThreadId, false);
|
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,15 @@
|
||||||
|
|
||||||
#include "arch/IArchMultithread.h"
|
#include "arch/IArchMultithread.h"
|
||||||
#include "deskflow/XDeskflow.h"
|
#include "deskflow/XDeskflow.h"
|
||||||
|
#include "platform/MSWindowsProcess.h"
|
||||||
#include "platform/MSWindowsSession.h"
|
#include "platform/MSWindowsSession.h"
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class Thread;
|
class Thread;
|
||||||
class IpcLogOutputter;
|
class IpcLogOutputter;
|
||||||
class IpcServer;
|
class IpcServer;
|
||||||
|
|
@ -32,36 +33,28 @@ public:
|
||||||
std::string getCommand() const;
|
std::string getCommand() const;
|
||||||
void setCommand(const std::string &command, bool elevate);
|
void setCommand(const std::string &command, bool elevate);
|
||||||
void stop();
|
void stop();
|
||||||
bool isProcessActive();
|
bool isProcessRunning();
|
||||||
void setFileLogOutputter(FileLogOutputter *outputter);
|
void setFileLogOutputter(FileLogOutputter *outputter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mainLoop(void *);
|
void mainLoop(void *);
|
||||||
void outputLoop(void *);
|
void outputLoop(void *);
|
||||||
void shutdownProcess(HANDLE handle, DWORD pid, int timeout);
|
|
||||||
void shutdownExistingProcesses();
|
void shutdownExistingProcesses();
|
||||||
HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
|
HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
|
||||||
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
|
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedToken);
|
||||||
void startProcess();
|
void startProcess();
|
||||||
BOOL startProcessAsUser(std::string &command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
|
|
||||||
BOOL startProcessInForeground(std::string &command);
|
|
||||||
void sendSas();
|
void sendSas();
|
||||||
void getActiveDesktop(LPSECURITY_ATTRIBUTES security);
|
|
||||||
void testOutput(std::string buffer);
|
|
||||||
void setStartupInfo(STARTUPINFO &si);
|
void setStartupInfo(STARTUPINFO &si);
|
||||||
void checkChildren();
|
|
||||||
/**
|
/**
|
||||||
* @brief This closes the handles held to a child thread
|
* @brief Re-run the process to get the active desktop name.
|
||||||
* @param pid the ID of the process to kill, will do nothing if PID is not a
|
*
|
||||||
* valid child
|
* It is necessary to run a utility process because the daemon runs in session 0, which does not
|
||||||
* @param removeFromMap should the function remove the item from the children
|
* have access to the active desktop, and so cannot query it's name.
|
||||||
* map
|
*
|
||||||
|
* @return std::string The name of the active desktop.
|
||||||
*/
|
*/
|
||||||
void closeProcessHandles(unsigned long pid, bool removeFromMap = true);
|
std::string runActiveDesktopUtility();
|
||||||
/**
|
|
||||||
* @brief This kills off all children's handles created by this process
|
|
||||||
*/
|
|
||||||
void clearAllChildren();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Thread *m_thread;
|
Thread *m_thread;
|
||||||
|
|
@ -69,28 +62,22 @@ private:
|
||||||
std::string m_command;
|
std::string m_command;
|
||||||
bool m_monitoring;
|
bool m_monitoring;
|
||||||
bool m_commandChanged;
|
bool m_commandChanged;
|
||||||
HANDLE m_stdOutWrite;
|
HANDLE m_outputWritePipe;
|
||||||
HANDLE m_stdOutRead;
|
HANDLE m_outputReadPipe;
|
||||||
Thread *m_outputThread;
|
Thread *m_outputThread;
|
||||||
IpcServer &m_ipcServer;
|
IpcServer &m_ipcServer;
|
||||||
IpcLogOutputter &m_ipcLogOutputter;
|
IpcLogOutputter &m_ipcLogOutputter;
|
||||||
bool m_elevateProcess;
|
bool m_elevateProcess;
|
||||||
MSWindowsSession m_session;
|
MSWindowsSession m_session;
|
||||||
PROCESS_INFORMATION m_processInfo;
|
|
||||||
int m_processFailures;
|
int m_processFailures;
|
||||||
bool m_processRunning;
|
bool m_processStarted;
|
||||||
FileLogOutputter *m_fileLogOutputter;
|
FileLogOutputter *m_fileLogOutputter;
|
||||||
bool m_autoElevated;
|
|
||||||
ArchMutex m_mutex;
|
ArchMutex m_mutex;
|
||||||
ArchCond m_condVar;
|
ArchCond m_condVar;
|
||||||
bool m_ready;
|
bool m_ready;
|
||||||
bool m_foreground;
|
bool m_foreground;
|
||||||
std::string m_activeDesktop;
|
std::string m_activeDesktop;
|
||||||
|
std::unique_ptr<deskflow::platform::MSWindowsProcess> m_process;
|
||||||
/// @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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Relauncher error
|
//! Relauncher error
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue