barrier/server/server.cpp

776 lines
17 KiB
C++

#include "CServer.h"
#include "CConfig.h"
#include "CPlatform.h"
#include "ProtocolTypes.h"
#include "Version.h"
#include "CNetwork.h"
#include "XSocket.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "XThread.h"
#include "CLog.h"
#include "stdfstream.h"
#include <cstring>
// platform dependent name of a daemon
#if defined(CONFIG_PLATFORM_WIN32)
#define DAEMON_NAME "Synergy Server"
#elif defined(CONFIG_PLATFORM_UNIX)
#define DAEMON_NAME "synergyd"
#endif
// configuration file name
#if defined(CONFIG_PLATFORM_WIN32)
#define CONFIG_NAME "synergy.sgc"
#elif defined(CONFIG_PLATFORM_UNIX)
#define CONFIG_NAME "synergy.conf"
#endif
//
// program arguments
//
static const char* pname = NULL;
static bool s_restartable = true;
static bool s_daemon = true;
#if defined(CONFIG_PLATFORM_WIN32)
static bool s_install = false;
static bool s_uninstall = false;
#endif
static const char* s_configFile = NULL;
static const char* s_logFilter = NULL;
static CString s_name;
static CNetworkAddress s_synergyAddress;
static CNetworkAddress s_httpAddress;
static CConfig s_config;
//
// logging thread safety
//
static CMutex* s_logMutex = NULL;
static
void
logLock(bool lock)
{
assert(s_logMutex != NULL);
if (lock) {
s_logMutex->lock();
}
else {
s_logMutex->unlock();
}
}
//
// platform independent main
//
static CServer* s_server = NULL;
static
int
realMain(CMutex* mutex)
{
// s_serverLock should have mutex locked on entry
try {
// initialize threading library
CThread::init();
// make logging thread safe
CMutex logMutex;
s_logMutex = &logMutex;
CLog::setLock(&logLock);
bool locked = true;
try {
// if configuration has no screens then add this system
// as the default
if (s_config.begin() == s_config.end()) {
s_config.addScreen(s_name);
}
// set the contact address, if provided, in the config.
// otherwise, if the config doesn't have an address, use
// the default.
if (s_synergyAddress.isValid()) {
s_config.setSynergyAddress(s_synergyAddress);
}
else if (!s_config.getSynergyAddress().isValid()) {
s_config.setSynergyAddress(CNetworkAddress(kDefaultPort));
}
// set HTTP address is provided
if (s_httpAddress.isValid()) {
s_config.setHTTPAddress(s_httpAddress);
}
// create server
s_server = new CServer(s_name);
// run server (unlocked)
if (mutex != NULL) {
mutex->unlock();
}
locked = false;
s_server->setConfig(s_config);
s_server->run();
locked = true;
if (mutex != NULL) {
mutex->lock();
}
// clean up
delete s_server;
s_server = NULL;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
}
catch (...) {
// clean up
if (!locked && mutex != NULL) {
mutex->lock();
}
delete s_server;
s_server = NULL;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
throw;
}
}
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
return 16;
}
catch (XThread&) {
// terminated
return 1;
}
return 0;
}
static
int
restartMain()
{
return realMain(NULL);
}
// invoke realMain and wait for it. if s_restartable then keep
// restarting realMain until it returns a terminate code.
static
int
restartableMain()
{
if (s_restartable) {
CPlatform platform;
return platform.restart(restartMain, 16);
}
else {
return realMain(NULL);
}
}
//
// command line parsing
//
#define BYE "\nTry `%s --help' for more information."
static void (*bye)(int) = &exit;
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()
{
#if defined(CONFIG_PLATFORM_WIN32)
# define PLATFORM_ARGS \
" {--daemon|--no-daemon}" \
" [--install]\n" \
"or\n" \
" --uninstall\n"
# define PLATFORM_DESC \
" --install install server as a daemon.\n" \
" --uninstall uninstall server daemon.\n"
# define PLATFORM_EXTRA \
"At least one command line argument is required. If you don't otherwise\n" \
"need an argument use `--daemon'.\n" \
"\n"
#else
# define PLATFORM_ARGS \
" [--daemon|--no-daemon]"
# define PLATFORM_DESC
# define PLATFORM_EXTRA
#endif
CPlatform platform;
log((CLOG_PRINT
"Usage: %s"
" [--address <address>]"
" [--config <pathname>]"
" [--debug <level>]"
" [--name <screen-name>]"
" [--restart|--no-restart]\n"
PLATFORM_ARGS
"\n"
"Start the synergy mouse/keyboard sharing server.\n"
"\n"
" -a, --address <address> listen for clients on the given address.\n"
" -c, --config <pathname> use the named configuration file instead.\n"
" -d, --debug <level> 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 server in the foreground.\n"
"* --daemon run the server as a daemon.\n"
" -n, --name <screen-name> use screen-name instead the hostname to identify\n"
" this screen in the configuration.\n"
" -1, --no-restart do not try to restart the server if it fails for\n"
" some reason.\n"
"* --restart restart the server automatically if it fails.\n"
PLATFORM_DESC
" -h, --help display this help and exit.\n"
" --version display version information and exit.\n"
"\n"
"* marks defaults.\n"
"\n"
PLATFORM_EXTRA
"The argument for --address is of the form: [<hostname>][:<port>]. The\n"
"hostname must be the address or hostname of an interface on the system.\n"
"The default is to listen on all interfaces. The port overrides the\n"
"default port, %d.\n"
"\n"
"If no configuration file pathname is provided then the first of the\n"
"following to load sets the configuration:\n"
" %s\n"
" %s\n"
"If no configuration file can be loaded then the configuration uses its\n"
"defaults with just the server screen.\n"
"\n"
"Where log messages go depends on the platform and whether or not the\n"
"server is running as a daemon.",
pname,
kDefaultPort,
platform.addPathComponent(
platform.getUserDirectory(),
CONFIG_NAME).c_str(),
platform.addPathComponent(
platform.getSystemDirectory(),
CONFIG_NAME).c_str()));
}
static
bool
isArg(int argi, int argc, const 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'" BYE,
pname, argv[argi], pname));
bye(2);
}
return true;
}
// no match
return false;
}
static
void
parse(int argc, const char** argv)
{
assert(pname != NULL);
assert(argv != NULL);
assert(argc >= 1);
// set defaults
char hostname[256];
if (CNetwork::gethostname(hostname, sizeof(hostname)) != CNetwork::Error) {
s_name = hostname;
}
// 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, "-a", "--address", 1)) {
// save listen address
try {
s_synergyAddress = CNetworkAddress(argv[i + 1], kDefaultPort);
}
catch (XSocketAddress&) {
log((CLOG_PRINT "%s: invalid address for `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
++i;
}
else if (isArg(i, argc, argv, NULL, "--http", 1)) {
// save listen address
try {
s_httpAddress = CNetworkAddress(argv[i + 1], kDefaultPort + 1);
}
catch (XSocketAddress&) {
log((CLOG_PRINT "%s: invalid address for `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
++i;
}
else if (isArg(i, argc, argv, "-n", "--name", 1)) {
// save screen name
s_name = argv[++i];
}
else if (isArg(i, argc, argv, "-c", "--config", 1)) {
// save configuration file path
s_configFile = 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();
bye(0);
}
else if (isArg(i, argc, argv, NULL, "--version")) {
version();
bye(0);
}
#if defined(CONFIG_PLATFORM_WIN32)
else if (isArg(i, argc, argv, NULL, "--install")) {
s_install = true;
if (s_uninstall) {
log((CLOG_PRINT "%s: `--install' and `--uninstall'"
" are mutually exclusive" BYE,
pname, argv[i], pname));
bye(2);
}
}
#endif
#if defined(CONFIG_PLATFORM_WIN32)
else if (isArg(i, argc, argv, NULL, "--uninstall")) {
s_uninstall = true;
if (s_install) {
log((CLOG_PRINT "%s: `--install' and `--uninstall'"
" are mutually exclusive" BYE,
pname, argv[i], pname));
bye(2);
}
}
#endif
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'" BYE,
pname, argv[i], pname));
bye(2);
}
else {
// this and remaining arguments are not options
break;
}
}
// no non-option arguments are allowed
if (i != argc) {
log((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
// increase default filter level for daemon. the user must
// explicitly request another level for a daemon.
if (s_daemon && s_logFilter == NULL) {
#if defined(CONFIG_PLATFORM_WIN32)
if (CPlatform::isWindows95Family()) {
// windows 95 has no place for logging so avoid showing
// the log console window.
s_logFilter = "FATAL";
}
else
#endif
{
s_logFilter = "NOTE";
}
}
// set log filter
if (!CLog::setFilter(s_logFilter)) {
log((CLOG_PRINT "%s: unrecognized log level `%s'" BYE,
pname, s_logFilter, pname));
bye(2);
}
}
static
bool
loadConfig(const char* pathname, bool require)
{
assert(pathname != NULL);
try {
// load configuration
log((CLOG_DEBUG "opening configuration \"%s\"", pathname));
std::ifstream configStream(pathname);
if (!configStream) {
throw XConfigRead("cannot open configuration");
}
configStream >> s_config;
log((CLOG_DEBUG "configuration read successfully"));
return true;
}
catch (XConfigRead& e) {
if (require) {
log((CLOG_PRINT "%s: cannot read configuration '%s': %s",
pname, pathname, e.what()));
bye(3);
}
else {
log((CLOG_DEBUG "cannot read configuration \"%s\": %s",
pathname, e.what()));
}
}
return false;
}
static
void
loadConfig()
{
// load the config file, if specified
if (s_configFile != NULL) {
// require the user specified file to load correctly
loadConfig(s_configFile, true);
}
// load the default configuration if no explicit file given
else {
// get the user's home directory. use the effective user id
// so a user can't get a setuid root program to load his file.
CPlatform platform;
bool loaded = false;
CString path = platform.getUserDirectory();
if (!path.empty()) {
// complete path
path = platform.addPathComponent(path, CONFIG_NAME);
// now try loading the user's configuration
loaded = loadConfig(path.c_str(), false);
}
if (!loaded) {
// try the system-wide config file
path = platform.getSystemDirectory();
if (!path.empty()) {
path = platform.addPathComponent(path, CONFIG_NAME);
loadConfig(path.c_str(), false);
}
}
}
}
//
// platform dependent entry points
//
#if defined(CONFIG_PLATFORM_WIN32)
#include "CMSWindowsScreen.h"
static
bool
logMessageBox(int priority, const char* msg)
{
if (priority <= CLog::kFATAL) {
MessageBox(NULL, msg, pname, MB_OK | MB_ICONWARNING);
return true;
}
else {
return false;
}
}
static
void
byeThrow(int x)
{
throw CWin32Platform::CDaemonFailed(x);
}
static
void
daemonStop(void)
{
s_server->quit();
}
static
int
daemonStartup(IPlatform* iplatform, int argc, const char** argv)
{
// get platform pointer
CWin32Platform* platform = static_cast<CWin32Platform*>(iplatform);
// catch errors that would normally exit
bye = &byeThrow;
// parse command line
s_install = false;
s_uninstall = false;
parse(argc, argv);
if (s_install || s_uninstall) {
// not allowed to install/uninstall from service
throw CWin32Platform::CDaemonFailed(1);
}
// load configuration
loadConfig();
// run as a service
return platform->runDaemon(realMain, daemonStop);
}
static
int
daemonStartup95(IPlatform*, int, const char**)
{
return restartableMain();
}
int WINAPI
WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
{
CPlatform platform;
// save instance
CMSWindowsScreen::init(instance);
// get program name
pname = platform.getBasename(__argv[0]);
// initialize network library
CNetwork::init();
// send PRINT and FATAL output to a message box
CLog::setOutputter(&logMessageBox);
// windows NT family starts services using no command line options.
// since i'm not sure how to tell the difference between that and
// a user providing no options we'll assume that if there are no
// arguments and we're on NT then we're being invoked as a service.
// users on NT can use `--daemon' or `--no-daemon' to force us out
// of the service code path.
if (__argc <= 1 && !CWin32Platform::isWindows95Family()) {
int result = platform.daemonize(DAEMON_NAME, &daemonStartup);
if (result == -1) {
log((CLOG_CRIT "failed to start as a service" BYE, pname));
return 16;
}
return result;
}
// parse command line
parse(__argc, const_cast<const char**>(__argv));
// install/uninstall
if (s_install) {
// get the full path to this program
TCHAR path[MAX_PATH];
if (GetModuleFileName(NULL, path,
sizeof(path) / sizeof(path[0])) == 0) {
log((CLOG_CRIT "cannot determine absolute path to program"));
return 16;
}
// construct the command line to start the service with
CString commandLine;
commandLine += "--daemon";
if (s_restartable) {
commandLine += " --restart";
}
else {
commandLine += " --no-restart";
}
if (s_logFilter != NULL) {
commandLine += " --debug ";
commandLine += s_logFilter;
}
if (s_configFile != NULL) {
commandLine += " --config \"";
commandLine += s_configFile;
commandLine += "\"";
}
// install
if (!platform.installDaemon(DAEMON_NAME,
"Shares this system's mouse and keyboard with others.",
path, commandLine.c_str())) {
log((CLOG_CRIT "failed to install service"));
return 16;
}
log((CLOG_PRINT "installed successfully"));
return 0;
}
else if (s_uninstall) {
switch (platform.uninstallDaemon(DAEMON_NAME)) {
case IPlatform::kSuccess:
log((CLOG_PRINT "uninstalled successfully"));
return 0;
case IPlatform::kFailed:
log((CLOG_CRIT "failed to uninstall service"));
return 16;
case IPlatform::kAlready:
log((CLOG_CRIT "service isn't installed"));
return 16;
}
}
// load configuration
loadConfig();
// daemonize if requested
int result;
if (s_daemon) {
// redirect log messages
platform.installDaemonLogger(DAEMON_NAME);
// start as a daemon
if (CWin32Platform::isWindows95Family()) {
result = platform.daemonize(DAEMON_NAME, &daemonStartup95);
if (result == -1) {
log((CLOG_CRIT "failed to start as a service" BYE, pname));
return 16;
}
}
else {
// cannot start a service from the command line so just
// run normally (except with log messages redirected).
result = restartableMain();
}
}
else {
// run
result = restartableMain();
}
return result;
}
#elif defined(CONFIG_PLATFORM_UNIX)
static
int
daemonStartup(IPlatform*, int, const char**)
{
return restartableMain();
}
int
main(int argc, char** argv)
{
CPlatform platform;
// get program name
pname = platform.getBasename(argv[0]);
// initialize network library
CNetwork::init();
// parse command line
parse(argc, const_cast<const char**>(argv));
// load configuration
loadConfig();
// daemonize if requested
int result;
if (s_daemon) {
result = platform.daemonize(DAEMON_NAME, &daemonStartup);
if (result == -1) {
log((CLOG_CRIT "failed to daemonize"));
return 16;
}
}
else {
result = restartableMain();
}
return result;
}
#else
#error no main() for platform
#endif