/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012 Bolton Software Ltd. * Copyright (C) 2012 Nick Bolton * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // TODO: split this class into windows and unix to get rid // of all the #ifdefs! #include "synergy/DaemonApp.h" #include "synergy/App.h" #include "synergy/ArgParser.h" #include "synergy/ServerArgs.h" #include "synergy/ClientArgs.h" #include "ipc/IpcClientProxy.h" #include "ipc/IpcMessage.h" #include "ipc/IpcLogOutputter.h" #include "net/SocketMultiplexer.h" #include "arch/XArch.h" #include "base/Log.h" #include "base/TMethodJob.h" #include "base/TMethodEventJob.h" #include "base/EventQueue.h" #include "base/log_outputters.h" #include "base/Log.h" #if SYSAPI_WIN32 #include "arch/win32/ArchMiscWindows.h" #include "arch/win32/XArchWindows.h" #include "synergy/Screen.h" #include "platform/MSWindowsScreen.h" #include "platform/MSWindowsDebugOutputter.h" #include "platform/MSWindowsWatchdog.h" #include "platform/MSWindowsEventQueueBuffer.h" #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include using namespace std; CDaemonApp* CDaemonApp::s_instance = NULL; int mainLoopStatic() { CDaemonApp::s_instance->mainLoop(true); return kExitSuccess; } int unixMainLoopStatic(int, const char**) { return mainLoopStatic(); } #if SYSAPI_WIN32 int winMainLoopStatic(int, const char**) { return CArchMiscWindows::runDaemon(mainLoopStatic); } #endif CDaemonApp::CDaemonApp() : m_ipcServer(nullptr), m_ipcLogOutputter(nullptr), #if SYSAPI_WIN32 m_watchdog(nullptr), #endif m_events(nullptr), m_fileLogOutputter(nullptr) { s_instance = this; } CDaemonApp::~CDaemonApp() { } int CDaemonApp::run(int argc, char** argv) { #if SYSAPI_WIN32 // win32 instance needed for threading, etc. CArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL)); #endif CArch arch; arch.init(); CLog log; CEventQueue events; m_events = &events; bool uninstall = false; try { #if SYSAPI_WIN32 // sends debug messages to visual studio console window. log.insert(new CMSWindowsDebugOutputter()); #endif // default log level to system setting. string logLevel = arch.setting("LogLevel"); if (logLevel != "") log.setFilter(logLevel.c_str()); bool foreground = false; for (int i = 1; i < argc; ++i) { string arg(argv[i]); if (arg == "/f" || arg == "-f") { foreground = true; } #if SYSAPI_WIN32 else if (arg == "/install") { uninstall = true; arch.installDaemon(); return kExitSuccess; } else if (arg == "/uninstall") { arch.uninstallDaemon(); return kExitSuccess; } #endif else { stringstream ss; ss << "Unrecognized argument: " << arg; foregroundError(ss.str().c_str()); return kExitArgs; } } if (foreground) { // run process in foreground instead of daemonizing. // useful for debugging. mainLoop(false); } else { #if SYSAPI_WIN32 arch.daemonize("Synergy", winMainLoopStatic); #elif SYSAPI_UNIX arch.daemonize("Synergy", unixMainLoopStatic); #endif } return kExitSuccess; } catch (XArch& e) { CString message = e.what(); if (uninstall && (message.find("The service has not been started") != CString::npos)) { // TODO: if we're keeping this use error code instead (what is it?!). // HACK: this message happens intermittently, not sure where from but // it's quite misleading for the user. they thing something has gone // horribly wrong, but it's just the service manager reporting a false // positive (the service has actually shut down in most cases). } else { foregroundError(message.c_str()); } return kExitFailed; } catch (std::exception& e) { foregroundError(e.what()); return kExitFailed; } catch (...) { foregroundError("Unrecognized error."); return kExitFailed; } } void CDaemonApp::mainLoop(bool logToFile) { try { DAEMON_RUNNING(true); if (logToFile) { m_fileLogOutputter = new CFileLogOutputter(logFilename().c_str()); CLOG->insert(m_fileLogOutputter); } // create socket multiplexer. this must happen after daemonization // on unix because threads evaporate across a fork(). CSocketMultiplexer multiplexer; // uses event queue, must be created here. m_ipcServer = new CIpcServer(m_events, &multiplexer); // send logging to gui via ipc, log system adopts outputter. m_ipcLogOutputter = new CIpcLogOutputter(*m_ipcServer); CLOG->insert(m_ipcLogOutputter); #if SYSAPI_WIN32 m_watchdog = new CMSWindowsWatchdog(false, *m_ipcServer, *m_ipcLogOutputter); m_watchdog->setFileLogOutputter(m_fileLogOutputter); #endif m_events->adoptHandler( m_events->forCIpcServer().messageReceived(), m_ipcServer, new TMethodEventJob(this, &CDaemonApp::handleIpcMessage)); m_ipcServer->listen(); #if SYSAPI_WIN32 // install the platform event queue to handle service stop events. m_events->adoptBuffer(new CMSWindowsEventQueueBuffer(m_events)); CString command = ARCH->setting("Command"); bool elevate = ARCH->setting("Elevate") == "1"; if (command != "") { LOG((CLOG_INFO "using last known command: %s", command.c_str())); m_watchdog->setCommand(command, elevate); } m_watchdog->startAsync(); #endif m_events->loop(); #if SYSAPI_WIN32 m_watchdog->stop(); delete m_watchdog; #endif m_events->removeHandler( m_events->forCIpcServer().messageReceived(), m_ipcServer); CLOG->remove(m_ipcLogOutputter); delete m_ipcLogOutputter; delete m_ipcServer; DAEMON_RUNNING(false); } catch (std::exception& e) { LOG((CLOG_CRIT "An error occurred: %s", e.what())); } catch (...) { LOG((CLOG_CRIT "An unknown error occurred.\n")); } } void CDaemonApp::foregroundError(const char* message) { #if SYSAPI_WIN32 MessageBox(NULL, message, "Synergy Service", MB_OK | MB_ICONERROR); #elif SYSAPI_UNIX cerr << message << endl; #endif } std::string CDaemonApp::logFilename() { string logFilename; logFilename = ARCH->setting("LogFilename"); if (logFilename.empty()) { logFilename = ARCH->getLogDirectory(); logFilename.append("/"); logFilename.append(LOG_FILENAME); } return logFilename; } void CDaemonApp::handleIpcMessage(const CEvent& e, void*) { CIpcMessage* m = static_cast(e.getDataObject()); switch (m->type()) { case kIpcCommand: { CIpcCommandMessage* cm = static_cast(m); CString command = cm->command(); // if empty quotes, clear. if (command == "\"\"") { command.clear(); } if (!command.empty()) { LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str())); std::vector argsArray; CArgParser::splitCommandString(command, argsArray); CArgParser argParser(NULL); const char** argv = argParser.getArgv(argsArray); CServerArgs serverArgs; CClientArgs clientArgs; int argc = static_cast(argsArray.size()); bool server = argsArray[0].find("synergys") != CString::npos ? true : false; CArgsBase* argBase = NULL; if (server) { argParser.parseServerArgs(serverArgs, argc, argv); argBase = &serverArgs; } else { argParser.parseClientArgs(clientArgs, argc, argv); argBase = &clientArgs; } delete[] argv; CString logLevel(argBase->m_logFilter); if (!logLevel.empty()) { try { // change log level based on that in the command string // and change to that log level now. ARCH->setting("LogLevel", logLevel); CLOG->setFilter(logLevel.c_str()); } catch (XArch& e) { LOG((CLOG_ERR "failed to save LogLevel setting, %s", e.what())); } } #if SYSAPI_WIN32 CString logFilename; if (argBase->m_logFile != NULL) { logFilename = CString(argBase->m_logFile); ARCH->setting("LogFilename", logFilename); m_watchdog->setFileLogOutputter(m_fileLogOutputter); command = CArgParser::assembleCommand(argsArray, "--log", 1); LOG((CLOG_DEBUG "removed log file argument and filename %s from command ", logFilename.c_str())); LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str())); } else { m_watchdog->setFileLogOutputter(NULL); } m_fileLogOutputter->setLogFilename(logFilename.c_str()); #endif } else { LOG((CLOG_DEBUG "empty command, elevate=%d", cm->elevate())); } try { // store command in system settings. this is used when the daemon // next starts. ARCH->setting("Command", command); // TODO: it would be nice to store bools/ints... ARCH->setting("Elevate", CString(cm->elevate() ? "1" : "0")); } catch (XArch& e) { LOG((CLOG_ERR "failed to save settings, %s", e.what())); } #if SYSAPI_WIN32 // tell the relauncher about the new command. this causes the // relauncher to stop the existing command and start the new // command. m_watchdog->setCommand(command, cm->elevate()); #endif break; } case kIpcHello: CIpcHelloMessage* hm = static_cast(m); CString type; switch (hm->clientType()) { case kIpcClientGui: type = "gui"; break; case kIpcClientNode: type = "node"; break; default: type = "unknown"; break; } LOG((CLOG_DEBUG "ipc hello, type=%s", type.c_str())); #if SYSAPI_WIN32 CString watchdogStatus = m_watchdog->isProcessActive() ? "ok" : "error"; LOG((CLOG_INFO "watchdog status: %s", watchdogStatus.c_str())); #endif m_ipcLogOutputter->notifyBuffer(); break; } }