diff --git a/client/CClient.cpp b/client/CClient.cpp index a5178e43..09e78f6c 100644 --- a/client/CClient.cpp +++ b/client/CClient.cpp @@ -214,15 +214,16 @@ void CClient::runSession(void*) // check versions log((CLOG_DEBUG1 "got hello version %d.%d", major, minor)); - if (major < kMajorVersion || - (major == kMajorVersion && minor < kMinorVersion)) { + if (major < kProtocolMajorVersion || + (major == kProtocolMajorVersion && minor < kProtocolMinorVersion)) { throw XIncompatibleClient(major, minor); } // say hello back - log((CLOG_DEBUG1 "say hello version %d.%d", kMajorVersion, kMinorVersion)); + log((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion)); CProtocolUtil::writef(output.get(), "Synergy%2i%2i%s", - kMajorVersion, kMinorVersion, &m_name); + kProtocolMajorVersion, + kProtocolMinorVersion, &m_name); // record streams in a more useful place CLock lock(&m_mutex); diff --git a/client/client.cpp b/client/client.cpp index 4876c527..4c0e3ef8 100644 --- a/client/client.cpp +++ b/client/client.cpp @@ -6,8 +6,21 @@ #include "CNetworkAddress.h" #include "CThread.h" #include "XThread.h" +#include "ProtocolTypes.h" +#include "Version.h" #include +// +// program arguments +// + +static const char* pname = NULL; +static bool s_restartable = true; +static bool s_daemon = true; +static const char* s_logFilter = NULL; +static const char* s_serverName = NULL; + + // // logging thread safety // @@ -70,6 +83,160 @@ void realMain(const CString& name, } +// +// command line parsing +// + +static void bye() +{ + log((CLOG_PRINT "Try `%s --help' for more information.", pname)); + exit(1); +} + +static void version() +{ + log((CLOG_PRINT +"%s %d.%d.%d, protocol version %d.%d\n" +"%s", + pname, + kMajorVersion, + kMinorVersion, + kReleaseVersion, + kProtocolMajorVersion, + kProtocolMinorVersion, + kCopyright)); +} + +static void help() +{ + log((CLOG_PRINT +"Usage: %s" +" [--debug ]" +" [--daemon|--no-daemon]" +" [--restart|--no-restart]" +" \n" +"Start the synergy mouse/keyboard sharing server.\n" +"\n" +" -d, --debug filter out log messages with priorty below level.\n" +" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" +" DEBUG, DEBUG1, DEBUG2.\n" +" -f, --no-daemon run the client in the foreground.\n" +" --daemon run the client as a daemon.\n" +" -1, --no-restart do not try to restart the client if it fails for\n" +" some reason.\n" +" --restart restart the client automatically if it fails.\n" +" -h, --help display this help and exit.\n" +" --version display version information and exit.\n" +"\n" +"By default, the client is a restartable daemon.\n" +"\n" +"Where log messages go depends on the platform and whether or not the\n" +"client is running as a daemon.", + pname)); + +} + +static bool isArg(int argi, + int argc, char** argv, + const char* name1, + const char* name2, + int minRequiredParameters = 0) +{ + if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || + (name2 != NULL && strcmp(argv[argi], name2) == 0)) { + // match. check args left. + if (argi + minRequiredParameters >= argc) { + log((CLOG_PRINT "%s: missing arguments for `%s'", + pname, argv[argi])); + bye(); + } + return true; + } + + // no match + return false; +} + +static void parse(int argc, char** argv) +{ + assert(pname != NULL); + assert(argv != NULL); + assert(argc >= 1); + + // parse options + int i; + for (i = 1; i < argc; ++i) { + if (isArg(i, argc, argv, "-d", "--debug", 1)) { + // change logging level + s_logFilter = argv[++i]; + } + + else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + // not a daemon + s_daemon = false; + } + + else if (isArg(i, argc, argv, NULL, "--daemon")) { + // daemonize + s_daemon = true; + } + + else if (isArg(i, argc, argv, "-1", "--no-restart")) { + // don't try to restart + s_restartable = false; + } + + else if (isArg(i, argc, argv, NULL, "--restart")) { + // try to restart + s_restartable = true; + } + + else if (isArg(i, argc, argv, "-h", "--help")) { + help(); + exit(1); + } + + else if (isArg(i, argc, argv, NULL, "--version")) { + version(); + exit(1); + } + + else if (isArg(i, argc, argv, "--", NULL)) { + // remaining arguments are not options + ++i; + break; + } + + else if (argv[i][0] == '-') { + log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); + bye(); + } + + else { + // this and remaining arguments are not options + break; + } + } + + // exactly one non-option argument: server-address + if (i == argc) { + log((CLOG_PRINT "%s: a server address or name is required", pname)); + bye(); + } + if (i + 1 != argc) { + log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); + bye(); + } + s_serverName = argv[i]; + + // set log filter + if (!CLog::setFilter(s_logFilter)) { + log((CLOG_PRINT "%s: unrecognized log level `%s'", pname, s_logFilter)); + bye(); + } +} + + // // platform dependent entry points // @@ -77,22 +244,41 @@ void realMain(const CString& name, #if defined(CONFIG_PLATFORM_WIN32) #include "CMSWindowsScreen.h" +#include int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) { CMSWindowsScreen::init(instance); - if (__argc != 2) { - CString msg = "hostname required. exiting."; - MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); - return 1; + // get program name + pname = strrchr(argv[0], '/'); + if (pname == NULL) { + pname = argv[0]; + } + else { + ++pname; + } + const char* pname2 = strrchr(argv[0], '\\'); + if (pname2 != NULL && pname2 > pname) { + pname = pname2 + 1; } +// FIXME -- direct CLog to MessageBox + + parse(__argc, __argv); + +// FIXME -- undirect CLog from MessageBox +// FIXME -- if daemon then use win32 event log (however that's done), +// otherwise do what? want to use console window for debugging but +// not otherwise. + +// FIXME try { - realMain("secondary", __argv[1], 50001); + realMain("secondary", s_serverName, 50001); return 0; } catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); CString msg = "failed: "; msg += e.what(); MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); @@ -104,29 +290,162 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) } } -#else +#elif defined(CONFIG_PLATFORM_UNIX) #include +#include +#include +#include +#include +#include +#include +#include +#include + +static void daemonize() +{ + // fork so shell thinks we're done and so we're not a process + // group leader + switch (fork()) { + case -1: + // failed + log((CLOG_PRINT "failed to daemonize")); + exit(1); + + case 0: + // child + break; + + default: + // parent exits + exit(0); + } + + // become leader of a new session + setsid(); + + // chdir to root so we don't keep mounted filesystems points busy + chdir("/"); + + // mask off permissions for any but owner + umask(077); + + // close open files. we only expect stdin, stdout, stderr to be open. + close(0); + close(1); + close(2); + + // attach file descriptors 0, 1, 2 to /dev/null so inadvertent use + // of standard I/O safely goes in the bit bucket. + open("/dev/null", O_RDWR); + dup(0); + dup(0); +} + +static void syslogOutputter(int priority, const char* msg) +{ + // convert priority + switch (priority) { + case CLog::kFATAL: + case CLog::kERROR: + priority = LOG_ERR; + break; + + case CLog::kWARNING: + priority = LOG_WARNING; + break; + + case CLog::kNOTE: + priority = LOG_NOTICE; + break; + + case CLog::kINFO: + priority = LOG_INFO; + break; + + default: + priority = LOG_DEBUG; + break; + } + + // log it + syslog(priority, "%s", msg); +} int main(int argc, char** argv) { - if (argc != 2) { - fprintf(stderr, "usage: %s \n", argv[0]); - return 1; + // get program name + pname = strrchr(argv[0], '/'); + if (pname == NULL) { + pname = argv[0]; + } + else { + ++pname; } - try { - realMain("secondary", argv[1], 50001); - return 0; + // parse command line + parse(argc, argv); + + // daemonize if requested + if (s_daemon) { + daemonize(); + + // send log to syslog + openlog("synergy", 0, LOG_DAEMON); + CLog::setOutputter(&syslogOutputter); } - catch (XBase& e) { - fprintf(stderr, "failed: %s\n", e.what()); - return 1; - } - catch (XThread&) { - // terminated - return 1; + + // run the server. if running as a daemon then run it in a child + // process and restart it as necessary. we have to do this in case + // the X server restarts because our process cannot recover from + // that. + for (;;) { + // don't fork if not restartable + switch (s_restartable ? fork() : 0) { + default: { + // parent process. wait for child to exit. + int status; + if (wait(&status) == -1) { + // wait failed. this is unexpected so bail. + log((CLOG_CRIT "wait() failed")); + return 16; + } + + // what happened? if the child exited normally with a + // status less than 16 then the child was deliberately + // terminated so we also terminate. otherwise, we + // loop. + if (WIFEXITED(status) && WEXITSTATUS(status) < 16) { + return 0; + } + break; + } + + case -1: + // fork() failed. log the error and proceed as a child + log((CLOG_WARN "fork() failed; cannot automatically restart on error")); + // fall through + + case 0: + // child process + try { + realMain("secondary", s_serverName, 50001); + return 0; + } + catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); + return 16; + } + catch (XThread&) { + // terminated + return 1; + } + } } } +#else + +#error no main() for platform + #endif diff --git a/server/CServer.cpp b/server/CServer.cpp index ad91dc1a..8e0fa1d8 100644 --- a/server/CServer.cpp +++ b/server/CServer.cpp @@ -1059,7 +1059,8 @@ void CServer::handshakeClient(void* vsocket) // say hello log((CLOG_DEBUG1 "saying hello")); CProtocolUtil::writef(output.get(), "Synergy%2i%2i", - kMajorVersion, kMinorVersion); + kProtocolMajorVersion, + kProtocolMinorVersion); output->flush(); // wait for the reply @@ -1117,7 +1118,7 @@ void CServer::handshakeClient(void* vsocket) // FIXME -- could print network address if socket had suitable method log((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); CProtocolUtil::writef(output.get(), kMsgEIncompatible, - kMajorVersion, kMinorVersion); + kProtocolMajorVersion, kProtocolMinorVersion); } catch (XBadClient&) { // client not behaving diff --git a/server/CServerProtocol.cpp b/server/CServerProtocol.cpp index c74bf0a3..73b7ecc5 100644 --- a/server/CServerProtocol.cpp +++ b/server/CServerProtocol.cpp @@ -56,13 +56,13 @@ IServerProtocol* CServerProtocol::create(SInt32 major, SInt32 minor, } // disallow connection from test versions to release versions - if (major == 0 && kMajorVersion != 0) { + if (major == 0 && kProtocolMajorVersion != 0) { throw XIncompatibleClient(major, minor); } // hangup (with error) if version isn't supported - if (major > kMajorVersion || - (major == kMajorVersion && minor > kMinorVersion)) { + if (major > kProtocolMajorVersion || + (major == kProtocolMajorVersion && minor > kProtocolMinorVersion)) { throw XIncompatibleClient(major, minor); } diff --git a/server/server.cpp b/server/server.cpp index 7ea320b2..671b1b7c 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -6,14 +6,10 @@ #include "CThread.h" #include "XThread.h" #include "ProtocolTypes.h" +#include "Version.h" #include "stdfstream.h" #include -static const char* s_copyright = "Copyright (C) 2002 Chris Schoeneman"; -static const SInt32 s_majorVersion = 0; -static const SInt32 s_minorVersion = 5; -static const char s_releaseVersion = ' '; - // configuration file name #if defined(CONFIG_PLATFORM_WIN32) #define CONFIG_NAME "synergy.sgc" @@ -115,15 +111,15 @@ static void bye() static void version() { log((CLOG_PRINT -"%s %d.%d%c protocol version %d.%d\n" +"%s %d.%d.%d, protocol version %d.%d\n" "%s", pname, - s_majorVersion, - s_minorVersion, - s_releaseVersion, kMajorVersion, kMinorVersion, - s_copyright)); + kReleaseVersion, + kProtocolMajorVersion, + kProtocolMinorVersion, + kCopyright)); } static void help() @@ -335,6 +331,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) // load the configuration file if we haven't already if (s_configFile == NULL) { +// FIXME } // FIXME @@ -531,7 +528,6 @@ int main(int argc, char** argv) } catch (XBase& e) { log((CLOG_CRIT "failed: %s", e.what())); - fprintf(stderr, "failed: %s\n", e.what()); return 16; } catch (XThread&) { diff --git a/synergy/ProtocolTypes.h b/synergy/ProtocolTypes.h index e06093fa..32cd4e0b 100644 --- a/synergy/ProtocolTypes.h +++ b/synergy/ProtocolTypes.h @@ -4,8 +4,8 @@ #include "BasicTypes.h" // version number -static const SInt32 kMajorVersion = 0; -static const SInt32 kMinorVersion = 1; +static const SInt16 kProtocolMajorVersion = 0; +static const SInt16 kProtocolMinorVersion = 1; // // message codes (trailing NUL is not part of code). in comments, $n diff --git a/synergy/Version.h b/synergy/Version.h new file mode 100644 index 00000000..24367162 --- /dev/null +++ b/synergy/Version.h @@ -0,0 +1,13 @@ +#ifndef VERSION_H +#define VERSION_H + +#include "BasicTypes.h" + +static const char* kCopyright = "Copyright (C) 2002 Chris Schoeneman"; + +// build version +static const SInt16 kMajorVersion = 0; +static const SInt16 kMinorVersion = 5; +static const SInt16 kReleaseVersion = 0; + +#endif