diff options
Diffstat (limited to 'cpp/src/Ice/Service.cpp')
-rw-r--r-- | cpp/src/Ice/Service.cpp | 2278 |
1 files changed, 2278 insertions, 0 deletions
diff --git a/cpp/src/Ice/Service.cpp b/cpp/src/Ice/Service.cpp new file mode 100644 index 00000000000..dd282212753 --- /dev/null +++ b/cpp/src/Ice/Service.cpp @@ -0,0 +1,2278 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#include <IceUtil/DisableWarnings.h> +#include <IceUtil/CtrlCHandler.h> +#include <IceUtil/StringUtil.h> +#include <IceUtil/Thread.h> +#include <IceUtil/Monitor.h> +#include <IceUtil/Mutex.h> +#include <IceUtil/ArgVector.h> +#include <IceUtil/FileUtil.h> +#include <Ice/Service.h> +#include <Ice/LoggerI.h> +#include <Ice/Initialize.h> +#include <Ice/StringConverter.h> +#include <Ice/Communicator.h> +#include <Ice/LocalException.h> +#include <Ice/Properties.h> + +#ifdef _WIN32 +# include <winsock2.h> +# include <Ice/EventLoggerMsg.h> +#else +# include <Ice/Logger.h> +# include <Ice/Network.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <csignal> +#endif + +using namespace std; +using namespace Ice; + +Ice::Service* Ice::Service::_instance = 0; +static IceUtil::CtrlCHandler* _ctrlCHandler = 0; + +// +// Callback for IceUtil::CtrlCHandler. +// +static void +ctrlCHandlerCallback(int sig) +{ + Ice::Service* service = Ice::Service::instance(); + assert(service != 0); + service->handleInterrupt(sig); +} + +#ifdef _WIN32 + +// +// Main function for Win32 service. +// +void WINAPI +Ice_Service_ServiceMain(DWORD argc, LPTSTR* argv) +{ + Ice::Service* service = Ice::Service::instance(); + assert(service != 0); + service->serviceMain(argc, argv); +} + +// +// Win32 service control handler. +// +void WINAPI +Ice_Service_CtrlHandler(DWORD ctrl) +{ + Ice::Service* service = Ice::Service::instance(); + assert(service != 0); + service->control(ctrl); +} + +namespace +{ + +class ServiceStatusManager : public IceUtil::Monitor<IceUtil::Mutex> +{ +public: + + ServiceStatusManager(SERVICE_STATUS_HANDLE); + + // + // Start a thread to provide regular status updates to the SCM. + // + void startUpdate(DWORD); + + // + // Stop the update thread. + // + void stopUpdate(); + + // + // Change the service status and report it (once). + // + void changeStatus(DWORD, DWORD); + + // + // Report the current status. + // + void reportStatus(); + +private: + + void run(); + + class StatusThread : public IceUtil::Thread + { + public: + + StatusThread(ServiceStatusManager* manager) : + IceUtil::Thread("Ice service status manager thread"), + _manager(manager) + { + } + + virtual void run() + { + _manager->run(); + } + + private: + + ServiceStatusManager* _manager; + }; + friend class StatusThread; + + SERVICE_STATUS_HANDLE _handle; + SERVICE_STATUS _status; + IceUtil::ThreadPtr _thread; + bool _stopped; +}; + +static ServiceStatusManager* serviceStatusManager; + +// +// Interface implemented by SMEventLoggerI and called from +// SMEventLoggerIWrapper. +// +class SMEventLogger : public IceUtil::Shared +{ +public: + virtual void print(const string&, const string&) = 0; + virtual void trace(const string&, const string&, const string&) = 0; + virtual void warning(const string&, const string&) = 0; + virtual void error(const string&, const string&) = 0; +}; +typedef IceUtil::Handle<SMEventLogger> SMEventLoggerPtr; + +class SMEventLoggerIWrapper : public Ice::Logger +{ +public: + + SMEventLoggerIWrapper(const SMEventLoggerPtr& logger, const string& prefix) : + _logger(logger), + _prefix(prefix) + { + assert(_logger); + } + + virtual void + print(const string& message) + { + _logger->print(_prefix, message); + } + + void + trace(const string& category, const string& message) + { + _logger->trace(_prefix, category, message); + } + + virtual void + warning(const string& message) + { + _logger->warning(_prefix, message); + } + + virtual void + error(const string& message) + { + _logger->error(_prefix, message); + } + + virtual Ice::LoggerPtr + cloneWithPrefix(const string& prefix) + { + return new SMEventLoggerIWrapper(_logger, prefix); + } + +private: + + SMEventLoggerPtr _logger; + string _prefix; +}; + +class SMEventLoggerI : public Ice::Logger, public SMEventLogger +{ +public: + + SMEventLoggerI(const string& source) + { + _source = RegisterEventSource(0, mangleSource(source).c_str()); + if(_source == 0) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = GetLastError(); + throw ex; + } + } + + ~SMEventLoggerI() + { + assert(_source != 0); + DeregisterEventSource(_source); + } + + static void + addKeys(const string& source) + { + HKEY hKey; + DWORD d; + LONG err = RegCreateKeyEx(HKEY_LOCAL_MACHINE, createKey(source).c_str(), 0, "REG_SZ", + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, &d); + if(err != ERROR_SUCCESS) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = err; + throw ex; + } + + // + // Get the filename of this DLL. + // + char path[_MAX_PATH]; + assert(_module != 0); + if(!GetModuleFileName(_module, path, _MAX_PATH)) + { + RegCloseKey(hKey); + SyscallException ex(__FILE__, __LINE__); + ex.error = GetLastError(); + throw ex; + } + + // + // The event resources are bundled into this DLL, therefore + // the "EventMessageFile" key should contain the path to this + // DLL. + // + err = RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, + reinterpret_cast<unsigned char*>(path), static_cast<DWORD>(strlen(path) + 1)); + if(err == ERROR_SUCCESS) + { + // + // The "TypesSupported" key indicates the supported event + // types. + // + DWORD typesSupported = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; + err = RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, + reinterpret_cast<unsigned char*>(&typesSupported), sizeof(typesSupported)); + } + if(err != ERROR_SUCCESS) + { + RegCloseKey(hKey); + SyscallException ex(__FILE__, __LINE__); + ex.error = err; + throw ex; + } + + RegCloseKey(hKey); + } + + static void + removeKeys(const string& source) + { + LONG err = RegDeleteKey(HKEY_LOCAL_MACHINE, createKey(source).c_str()); + if(err != ERROR_SUCCESS) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = err; + throw ex; + } + } + + virtual void + print(const string& prefix, const string& message) + { + string s; + if(!prefix.empty()) + { + s = prefix; + s.append(": "); + } + s.append(message); + print(s); + } + + virtual void + print(const string& message) + { + const char* str[1]; + str[0] = message.c_str(); + // + // We ignore any failures from ReportEvent since there isn't + // anything we can do about it. + // + ReportEvent(_source, EVENTLOG_INFORMATION_TYPE, 0, EVENT_LOGGER_MSG, 0, 1, 0, str, 0); + } + + virtual void + trace(const string& prefix, const string& category, const string& message) + { + string s; + if(!category.empty()) + { + s = category; + s.append(": "); + } + s.append(message); + trace(prefix, s); + } + + virtual void + trace(const string& category, const string& message) + { + string s; + if(!category.empty()) + { + s = category; + s.append(": "); + } + s.append(message); + + const char* str[1]; + str[0] = s.c_str(); + // + // We ignore any failures from ReportEvent since there isn't + // anything we can do about it. + // + ReportEvent(_source, EVENTLOG_INFORMATION_TYPE, 0, EVENT_LOGGER_MSG, 0, 1, 0, str, 0); + } + + virtual void + warning(const string& prefix, const string& message) + { + string s; + if(!prefix.empty()) + { + s = prefix; + s.append(": "); + } + s.append(message); + warning(s); + } + + virtual void + warning(const string& message) + { + const char* str[1]; + str[0] = message.c_str(); + // + // We ignore any failures from ReportEvent since there isn't + // anything we can do about it. + // + ReportEvent(_source, EVENTLOG_WARNING_TYPE, 0, EVENT_LOGGER_MSG, 0, 1, 0, str, 0); + } + + virtual void + error(const string& prefix, const string& message) + { + string s; + if(!prefix.empty()) + { + s = prefix; + s.append(": "); + } + s.append(message); + error(s); + } + + virtual void + error(const string& message) + { + const char* str[1]; + str[0] = message.c_str(); + // + // We ignore any failures from ReportEvent since there isn't + // anything we can do about it. + // + ReportEvent(_source, EVENTLOG_ERROR_TYPE, 0, EVENT_LOGGER_MSG, 0, 1, 0, str, 0); + } + + virtual Ice::LoggerPtr + cloneWithPrefix(const string& prefix) + { + return new SMEventLoggerIWrapper(this, prefix); + } + + static void + setModuleHandle(HMODULE module) + { + _module = module; + } + +private: + + static string + mangleSource(string name) + { + // + // The source name cannot contain backslashes. + // + string::size_type pos = 0; + while((pos = name.find('\\', pos)) != string::npos) + { + name[pos] = '/'; + } + return name; + } + + static string + createKey(string name) + { + // + // The registry key is: + // + // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application. + // + return "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" + mangleSource(name); + } + + + HANDLE _source; + static HMODULE _module; +}; + +HMODULE SMEventLoggerI::_module = 0; + +} + +#endif + +Ice::Service::Service() +{ + assert(_instance == 0); + _nohup = true; + _service = false; + _instance = this; + +#ifndef _WIN32 + _changeDirectory = true; + _closeFiles = true; +#endif +} + +Ice::Service::~Service() +{ + _instance = 0; + delete _ctrlCHandler; +} + +bool +Ice::Service::shutdown() +{ + if(_communicator) + { + try + { + _communicator->shutdown(); + } + catch(const CommunicatorDestroyedException&) + { + // + // Expected if the service communicator is being destroyed. + // + } + catch(const Ice::Exception& ex) + { + ostringstream ostr; + ostr << "exception during shutdown:\n" << ex; + warning(ostr.str()); + } + catch(...) + { + warning("unknown exception during shutdown"); + } + } + return true; +} + +void +Ice::Service::interrupt() +{ + shutdown(); +} + +int +Ice::Service::main(int& argc, char* argv[], const InitializationData& initializationData) +{ + _name = ""; + if(argc > 0) + { + _name = argv[0]; + } + + // + // We parse the properties here to extract Ice.ProgramName and + // Ice.EventLog.Source on Windows. + // + InitializationData initData = initializationData; + try + { + initData.properties = createProperties(argc, argv, initData.properties, initData.stringConverter); + } + catch(const Ice::Exception& ex) + { + ostringstream ostr; + ostr << ex; + error(ostr.str()); + return EXIT_FAILURE; + } + +#ifdef _WIN32 + + // + // First check for the --service option. + // + string name; + string eventLogSource; + int idx = 1; + while(idx < argc) + { + if(strcmp(argv[idx], "--service") == 0) + { + if(idx + 1 >= argc) + { + error("service name argument expected for `" + string(argv[idx]) + "'"); + return EXIT_FAILURE; + } + + name = argv[idx + 1]; + + // + // If the process logger is the default logger then we use + // our own logger. + // + _logger = getProcessLogger(); + if(LoggerIPtr::dynamicCast(_logger)) + { + string eventLogSource = initData.properties->getPropertyWithDefault("Ice.EventLog.Source", name); + _logger = new SMEventLoggerIWrapper(new SMEventLoggerI(eventLogSource), ""); + setProcessLogger(_logger); + } + + for(int i = idx; i + 2 < argc; ++i) + { + argv[i] = argv[i + 2]; + } + argc -= 2; + } + else + { + ++idx; + } + } + + // + // Next check for service control options. + // + string op; + idx = 1; + while(idx < argc) + { + if(strcmp(argv[idx], "--install") == 0 || + strcmp(argv[idx], "--uninstall") == 0 || + strcmp(argv[idx], "--start") == 0 || + strcmp(argv[idx], "--stop") == 0) + { + if(!op.empty()) + { + error("cannot specify `" + op + "' and `" + string(argv[idx]) + "'"); + return EXIT_FAILURE; + } + + if(!name.empty()) + { + error("cannot specify `--service' and `" + string(argv[idx]) + "'"); + return EXIT_FAILURE; + } + + if(idx + 1 >= argc) + { + error("service name argument expected for `" + string(argv[idx]) + "'"); + return EXIT_FAILURE; + } + + op = argv[idx]; + name = argv[idx + 1]; + + warning("the " + op + " option is deprecated"); + + for(int i = idx ; i + 2 < argc ; ++i) + { + argv[i] = argv[i + 2]; + } + argc -= 2; + } + else + { + ++idx; + } + } + + if(!op.empty()) + { + if(op == "--install") + { + // + // Check for --display, --executable. + // + string display, executable; + idx = 1; + while(idx < argc) + { + if(strcmp(argv[idx], "--display") == 0) + { + if(idx + 1 >= argc) + { + error("argument expected for `" + string(argv[idx]) + "'"); + return EXIT_FAILURE; + } + + display = argv[idx + 1]; + + for(int i = idx ; i + 2 < argc ; ++i) + { + argv[i] = argv[i + 2]; + } + argc -= 2; + } + else if(strcmp(argv[idx], "--executable") == 0) + { + if(idx + 1 >= argc) + { + error("argument expected for `" + string(argv[idx]) + "'"); + return EXIT_FAILURE; + } + + executable = argv[idx + 1]; + + for(int i = idx ; i + 2 < argc ; ++i) + { + argv[i] = argv[i + 2]; + } + argc -= 2; + } + else + { + ++idx; + } + } + + vector<string> args; + // + // Prepend the arguments "--service NAME" so that the service + // starts properly. + // + args.push_back("--service"); + args.push_back(name); + for(idx = 1; idx < argc; ++idx) + { + args.push_back(argv[idx]); + } + try + { + // + // Add the registry keys for the event logger if + // initData.logger is empty (which is the case if the + // user wants to use the service default logger). + // + return installService(!initData.logger, name, display, executable, args); + } + catch(const Ice::Exception& ex) + { + ostringstream ostr; + ostr << ex; + error(ostr.str()); + return EXIT_FAILURE; + } + } + else if(op == "--uninstall") + { + try + { + return uninstallService(!initData.logger, name); + } + catch(const Ice::Exception& ex) + { + ostringstream ostr; + ostr << ex; + error(ostr.str()); + return EXIT_FAILURE; + } + } + else if(op == "--start") + { + vector<string> args; + for(idx = 1; idx < argc; ++idx) + { + args.push_back(argv[idx]); + } + return startService(name, args); + } + else + { + assert(op == "--stop"); + return stopService(name); + } + } + + if(!name.empty()) + { + configureService(name); + } +#else + // + // Check for --daemon, --noclose, --nochdir and --pidfile. + // + + bool daemonize = false; + bool closeFiles = true; + bool changeDirectory = true; + string pidFile; + int idx = 1; + while(idx < argc) + { + if(strcmp(argv[idx], "--daemon") == 0) + { + for(int i = idx; i + 1 < argc; ++i) + { + argv[i] = argv[i + 1]; + } + argc -= 1; + + daemonize = true; + } + else if(strcmp(argv[idx], "--noclose") == 0) + { + for(int i = idx; i + 1 < argc; ++i) + { + argv[i] = argv[i + 1]; + } + argc -= 1; + + closeFiles = false; + } + else if(strcmp(argv[idx], "--nochdir") == 0) + { + for(int i = idx; i + 1 < argc; ++i) + { + argv[i] = argv[i + 1]; + } + argc -= 1; + + changeDirectory = false; + } + else if(strcmp(argv[idx], "--pidfile") == 0) + { + if(idx + 1 < argc) + { + pidFile = argv[idx + 1]; + } + else + { + if(argv[0]) + { + cerr << argv[0] << ": "; + } + cerr << "--pidfile must be followed by an argument" << endl; + return EXIT_FAILURE; + } + + for(int i = idx; i + 2 < argc; ++i) + { + argv[i] = argv[i + 2]; + } + argc -= 2; + } + else + { + ++idx; + } + } + + if(!closeFiles && !daemonize) + { + if(argv[0]) + { + cerr << argv[0] << ": "; + } + cerr << "--noclose must be used with --daemon" << endl; + return EXIT_FAILURE; + } + + if(pidFile.size() > 0 && !daemonize) + { + if(argv[0]) + { + cerr << argv[0] << ": "; + } + cerr << "--pidfile <file> must be used with --daemon" << endl; + return EXIT_FAILURE; + } + + if(daemonize) + { + configureDaemon(changeDirectory, closeFiles, pidFile); + } +#endif + + // + // If no logger has been set yet, we set it to the process logger. If the + // process logger is the default logger, we change it to a logger which is + // using the program name for the prefix. + // + if(!_logger) + { + _logger = getProcessLogger(); + if(LoggerIPtr::dynamicCast(_logger)) + { + _logger = new LoggerI(initData.properties->getProperty("Ice.ProgramName"), ""); + setProcessLogger(_logger); + } + } + + return run(argc, argv, initData); +} + +int +Ice::Service::main(int argc, char* const argv[], const InitializationData& initializationData) +{ + IceUtilInternal::ArgVector av(argc, argv); + return main(av.argc, av.argv, initializationData); +} + +#ifdef _WIN32 + +int +Ice::Service::main(int& argc, wchar_t* argv[], const InitializationData& initializationData) +{ +#ifdef __BCPLUSPLUS__ // COMPILER FIX + // + // BCC doesn't see the main overload if we don't create the temp args object here. + // + Ice::StringSeq args = Ice::argsToStringSeq(argc, argv, initializationData.stringConverter); + return main(args, initializationData); +#else + return main(Ice::argsToStringSeq(argc, argv, initializationData.stringConverter), initializationData); +#endif + +} + +#endif + +int +Ice::Service::main(StringSeq& args, const InitializationData& initData) +{ + IceUtilInternal::ArgVector av(args); + return main(av.argc, av.argv, initData); +} + +Ice::CommunicatorPtr +Ice::Service::communicator() const +{ + return _communicator; +} + +Ice::Service* +Ice::Service::instance() +{ + return _instance; +} + +bool +Ice::Service::service() const +{ + return _service; +} + +string +Ice::Service::name() const +{ + return _name; +} + +bool +Ice::Service::checkSystem() const +{ +#ifdef _WIN32 + // + // Check Windows version. + // + OSVERSIONINFO ver; + ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&ver); + return (ver.dwPlatformId == VER_PLATFORM_WIN32_NT); +#else + return true; +#endif +} + +int +Ice::Service::run(int& argc, char* argv[], const InitializationData& initData) +{ + if(_service) + { +#ifdef _WIN32 + return runService(argc, argv, initData); +#else + return runDaemon(argc, argv, initData); +#endif + } + + // + // Run as a foreground process. + // + int status = EXIT_FAILURE; + try + { + // + // Create the CtrlCHandler after any potential forking so that signals + // are initialized properly. We do this before initializing the + // communicator because we need to ensure that this is done before any + // additional threads are created. + // + _ctrlCHandler = new IceUtil::CtrlCHandler; + + // + // Initialize the communicator. + // + _communicator = initializeCommunicator(argc, argv, initData); + + // + // Use the configured logger. + // + _logger = _communicator->getLogger(); + + // + // Determines whether we ignore SIGHUP/CTRL_LOGOFF_EVENT. + // + _nohup = _communicator->getProperties()->getPropertyAsIntWithDefault("Ice.Nohup", 1) > 0; + + // + // Start the service. + // + if(start(argc, argv, status)) + { + // + // Wait for service shutdown. + // + waitForShutdown(); + + // + // Stop the service. + // + if(stop()) + { + status = EXIT_SUCCESS; + } + } + } + catch(const std::exception& ex) + { + ostringstream ostr; + ostr << "service caught unhandled exception:\n" << ex.what(); + error(ostr.str()); + } + catch(const std::string& msg) + { + ostringstream ostr; + ostr << "service caught unhandled exception:\n" << msg; + error(ostr.str()); + } + catch(const char* msg) + { + ostringstream ostr; + ostr << "service caught unhandled exception:\n" << msg; + error(ostr.str()); + } + catch(...) + { + error("service caught unhandled C++ exception"); + } + + if(_communicator) + { + try + { + _communicator->destroy(); + } + catch(...) + { + } + } + + return status; +} + +#ifdef _WIN32 + +void +Ice::Service::configureService(const string& name) +{ + _service = true; + _name = name; +} + +int +Ice::Service::installService(bool useEventLogger, const string& name, const string& display, const string& executable, + const vector<string>& args) +{ + string disp, exec; + + disp = display; + if(disp.empty()) + { + disp = name; + } + + exec = executable; + if(exec.empty()) + { + // + // Use this executable if none is specified. + // + char buf[_MAX_PATH]; + if(GetModuleFileName(0, buf, _MAX_PATH) == 0) + { + error("unable to obtain file name of executable"); + return EXIT_FAILURE; + } + exec = buf; + } + + // + // Compose service command line. The executable and any arguments must + // be enclosed in quotes if they contain whitespace. + // + string command; + if(executable.find(' ') != string::npos) + { + command.push_back('"'); + command.append(exec); + command.push_back('"'); + } + else + { + command = exec; + } + for(vector<string>::const_iterator p = args.begin(); p != args.end(); ++p) + { + command.push_back(' '); + + if(p->find_first_of(" \t\n\r") != string::npos) + { + command.push_back('"'); + command.append(*p); + command.push_back('"'); + } + else + { + command.append(*p); + } + } + + SC_HANDLE hSCM = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if(hSCM == 0) + { + syserror("failure in OpenSCManager"); + return EXIT_FAILURE; + } + SC_HANDLE hService = CreateService( + hSCM, + name.c_str(), + disp.c_str(), + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + command.c_str(), + 0, + 0, + 0, + 0, + 0); + + if(hService == 0) + { + syserror("unable to install service `" + name + "'"); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + CloseServiceHandle(hSCM); + CloseServiceHandle(hService); + + // + // Add the registry keys for the event logger if _logger is + // empty (which is the case if the user wants to use + // the service default logger). + // + if(useEventLogger) + { + SMEventLoggerI::addKeys(name); + } + + return EXIT_SUCCESS; +} + +int +Ice::Service::uninstallService(bool useEventLogger, const string& name) +{ + SC_HANDLE hSCM = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if(hSCM == 0) + { + syserror("failure in OpenSCManager"); + return EXIT_FAILURE; + } + + SC_HANDLE hService = OpenService(hSCM, name.c_str(), SERVICE_ALL_ACCESS); + if(hService == 0) + { + syserror("unable to open service `" + name + "'"); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + BOOL b = DeleteService(hService); + + if(!b) + { + syserror("unable to uninstall service `" + name + "'"); + CloseServiceHandle(hSCM); + CloseServiceHandle(hService); + return EXIT_FAILURE; + } + + CloseServiceHandle(hSCM); + CloseServiceHandle(hService); + + // + // Remove the registry keys for the event logger if necessary. + // + if(useEventLogger) + { + SMEventLoggerI::removeKeys(name); + } + + return EXIT_SUCCESS; +} + +int +Ice::Service::startService(const string& name, const vector<string>& args) +{ + SC_HANDLE hSCM = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if(hSCM == 0) + { + syserror("failure in OpenSCManager"); + return EXIT_FAILURE; + } + + SC_HANDLE hService = OpenService(hSCM, name.c_str(), SERVICE_ALL_ACCESS); + if(hService == 0) + { + syserror("unable to open service `" + name + "'"); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + // + // Create argument vector. Note that StartService() automatically + // adds the service name in argv[0], so the argv that is passed to + // StartService() must *not* include the the service name in + // argv[0]. + // + const int argc = static_cast<int>(args.size()); + LPCSTR* argv = new LPCSTR[argc]; + int i = 0; + for(vector<string>::const_iterator p = args.begin(); p != args.end(); ++p) + { +#if defined(_MSC_VER) && (_MSC_VER >= 1400) + argv[i++] = _strdup(p->c_str()); +#else + argv[i++] = strdup(p->c_str()); +#endif + } + + // + // Start service. + // + BOOL b = StartService(hService, argc, argv); + + // + // Clean up argument vector. + // + for(i = 0; i < argc; ++i) + { + free(const_cast<char*>(argv[i])); + } + delete[] argv; + + if(!b) + { + syserror("unable to start service `" + name + "'"); + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + trace("Service start pending."); + + // + // Wait until the service is started or an error is detected. + // + SERVICE_STATUS status; + if(!waitForServiceState(hService, SERVICE_START_PENDING, status)) + { + syserror("unable to query status of service `" + name + "'"); + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + + if(status.dwCurrentState == SERVICE_RUNNING) + { + trace("Service is running."); + } + else + { + showServiceStatus("Service failed to start.", status); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int +Ice::Service::stopService(const string& name) +{ + SC_HANDLE hSCM = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if(hSCM == 0) + { + syserror("failure in OpenSCManager"); + return EXIT_FAILURE; + } + + SC_HANDLE hService = OpenService(hSCM, name.c_str(), SERVICE_ALL_ACCESS); + if(hService == 0) + { + syserror("unable to open service `" + name + "'"); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + SERVICE_STATUS status; + BOOL b = ControlService(hService, SERVICE_CONTROL_STOP, &status); + + if(!b) + { + syserror("unable to stop service `" + name + "'"); + CloseServiceHandle(hSCM); + CloseServiceHandle(hService); + return EXIT_FAILURE; + } + + trace("Service stop pending."); + + // + // Wait until the service is stopped or an error is detected. + // + if(!waitForServiceState(hService, SERVICE_STOP_PENDING, status)) + { + syserror("unable to query status of service `" + name + "'"); + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return EXIT_FAILURE; + } + + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + + if(status.dwCurrentState == SERVICE_STOPPED) + { + trace("Service is stopped."); + } + else + { + showServiceStatus("Service failed to stop.", status); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +void +Ice::Service::setModuleHandle(HMODULE module) +{ + SMEventLoggerI::setModuleHandle(module); +} + +#else + +void +Ice::Service::configureDaemon(bool changeDirectory, bool closeFiles, const string& pidFile) +{ + _service = true; + _changeDirectory = changeDirectory; + _closeFiles = closeFiles; + _pidFile = pidFile; +} + +#endif + +void +Ice::Service::handleInterrupt(int sig) +{ +#ifdef _WIN32 + if(_nohup && sig == CTRL_LOGOFF_EVENT) + { + return; + } +#else + if(_nohup && sig == SIGHUP) + { + return; + } +#endif + + interrupt(); +} + +void +Ice::Service::waitForShutdown() +{ + if(_communicator) + { + enableInterrupt(); + _communicator->waitForShutdown(); + disableInterrupt(); + } +} + +bool +Ice::Service::stop() +{ + return true; +} + +Ice::CommunicatorPtr +Ice::Service::initializeCommunicator(int& argc, char* argv[], const InitializationData& initData) +{ + return Ice::initialize(argc, argv, initData); +} + +void +Ice::Service::syserror(const string& msg) +{ + string errmsg = IceUtilInternal::lastErrorToString(); + if(_logger) + { + ostringstream ostr; + if(!msg.empty()) + { + ostr << msg << endl; + } + if(!errmsg.empty()) + { + ostr << errmsg; + } + _logger->error(ostr.str()); + } + else + { + if(!_name.empty()) + { + cerr << _name << ": "; + } + if(!msg.empty()) + { + cerr << msg << endl; + } + if(!errmsg.empty()) + { + cerr << errmsg; + } + } +} + +void +Ice::Service::error(const string& msg) +{ + if(_logger) + { + _logger->error(msg); + } + else + { + if(!_name.empty()) + { + cerr << _name << ": "; + } + cerr << "error: " << msg << endl; + } +} + +void +Ice::Service::warning(const string& msg) +{ + if(_logger) + { + _logger->warning(msg); + } + else + { + if(!_name.empty()) + { + cerr << _name << ": "; + } + cerr << "warning: " << msg << endl; + } +} + +void +Ice::Service::trace(const string& msg) +{ + if(_logger) + { + _logger->trace("", msg); + } + else + { + cerr << msg << endl; + } +} + +void +Ice::Service::print(const string& msg) +{ + if(_logger) + { + _logger->print(msg); + } + else + { + cerr << msg << endl; + } +} + +void +Ice::Service::enableInterrupt() +{ + _ctrlCHandler->setCallback(ctrlCHandlerCallback); +} + +void +Ice::Service::disableInterrupt() +{ + _ctrlCHandler->setCallback(0); +} + +#ifdef _WIN32 + +int +Ice::Service::runService(int argc, char* argv[], const InitializationData& initData) +{ + assert(_service); + + if(!checkSystem()) + { + error("Win32 service not supported on Windows 9x/ME"); + return EXIT_FAILURE; + } + + if(_name.empty()) + { + error("invalid name for Win32 service"); + return EXIT_FAILURE; + } + + // + // Arguments passed to the executable are not passed to the service's main function, + // so save them now and serviceMain will merge them later. + // + for(int idx = 1; idx < argc; ++idx) + { + _serviceArgs.push_back(argv[idx]); + } + + _initData = initData; + + SERVICE_TABLE_ENTRY ste[] = + { + { const_cast<char*>(_name.c_str()), Ice_Service_ServiceMain }, + { 0, 0 }, + }; + + // + // Start the service. + // + if(!StartServiceCtrlDispatcher(ste)) + { + syserror("unable to start service control dispatcher"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +void +Ice::Service::terminateService(DWORD exitCode) +{ + serviceStatusManager->stopUpdate(); + delete serviceStatusManager; + serviceStatusManager = 0; + + SERVICE_STATUS status; + + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status.dwCurrentState = SERVICE_STOPPED; + status.dwControlsAccepted = 0; + if(exitCode != 0) + { + status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + } + else + { + status.dwWin32ExitCode = 0; + } + status.dwServiceSpecificExitCode = exitCode; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + + SetServiceStatus(_statusHandle, &status); +} + +bool +Ice::Service::waitForServiceState(SC_HANDLE hService, DWORD pendingState, SERVICE_STATUS& status) +{ + if(!QueryServiceStatus(hService, &status)) + { + return false; + } + + // + // Save the tick count and initial checkpoint. + // + DWORD startTickCount = GetTickCount(); + DWORD oldCheckPoint = status.dwCheckPoint; + + // + // Loop while the service is in the pending state. + // + while(status.dwCurrentState == pendingState) + { + // + // Do not wait longer than the wait hint. A good interval is + // one tenth the wait hint, but no less than 1 second and no + // more than 10 seconds. + // + + DWORD waitTime = status.dwWaitHint / 10; + + if(waitTime < 1000) + { + waitTime = 1000; + } + else if(waitTime > 10000) + { + waitTime = 10000; + } + + Sleep(waitTime); + + // + // Check the status again. + // + if(!QueryServiceStatus(hService, &status)) + { + return false; + } + + if(status.dwCheckPoint > oldCheckPoint) + { + // + // The service is making progress. + // + startTickCount = GetTickCount(); + oldCheckPoint = status.dwCheckPoint; + } + else + { + if(GetTickCount() - startTickCount > status.dwWaitHint) + { + // + // No progress made within the wait hint. + // + break; + } + } + } + + return true; +} + +void +Ice::Service::showServiceStatus(const string& msg, SERVICE_STATUS& status) +{ + string state; + switch(status.dwCurrentState) + { + case SERVICE_STOPPED: + state = "STOPPED"; + break; + case SERVICE_START_PENDING: + state = "START PENDING"; + break; + case SERVICE_STOP_PENDING: + state = "STOP PENDING"; + break; + case SERVICE_RUNNING: + state = "RUNNING"; + break; + case SERVICE_CONTINUE_PENDING: + state = "CONTINUE PENDING"; + break; + case SERVICE_PAUSE_PENDING: + state = "PAUSE PENDING"; + break; + case SERVICE_PAUSED: + state = "PAUSED"; + break; + default: + state = "UNKNOWN"; + break; + } + + ostringstream ostr; + ostr << msg << endl + << " Current state: " << state << endl + << " Exit code: " << status.dwWin32ExitCode << endl + << " Service specific exit code: " << status.dwServiceSpecificExitCode << endl + << " Check point: " << status.dwCheckPoint << endl + << " Wait hint: " << status.dwWaitHint; + trace(ostr.str()); +} + +void +Ice::Service::serviceMain(int argc, char* argv[]) +{ + _ctrlCHandler = new IceUtil::CtrlCHandler; + + // + // Register the control handler function. + // + _statusHandle = RegisterServiceCtrlHandler(argv[0], Ice_Service_CtrlHandler); + if(_statusHandle == (SERVICE_STATUS_HANDLE)0) + { + syserror("unable to register service control handler"); + return; + } + + // + // Create the service status manager and start a thread to periodically + // update the service's status with the service control manager (SCM). + // The SCM must receive periodic updates otherwise it assumes that + // initialization failed and terminates the service. + // + serviceStatusManager = new ServiceStatusManager(_statusHandle); + serviceStatusManager->startUpdate(SERVICE_START_PENDING); + + // + // Merge the executable's arguments with the service's arguments. + // + char** args = new char*[_serviceArgs.size() + argc]; + args[0] = argv[0]; + int i = 1; + for(vector<string>::iterator p = _serviceArgs.begin(); p != _serviceArgs.end(); ++p) + { + args[i++] = const_cast<char*>(p->c_str()); + } + for(int j = 1; j < argc; ++j) + { + args[i++] = argv[j]; + } + argc += static_cast<int>(_serviceArgs.size()); + + // + // If we can't initialize a communicator, then stop immediately. + // + try + { + _communicator = initializeCommunicator(argc, args, _initData); + } + catch(const Ice::Exception& ex) + { + delete[] args; + ostringstream ostr; + ostr << "exception occurred while initializing a communicator:\n" << ex; + error(ostr.str()); + terminateService(EXIT_FAILURE); + return; + } + catch(...) + { + delete[] args; + error("unknown exception occurred while initializing a communicator"); + terminateService(EXIT_FAILURE); + return; + } + + // + // Use the configured logger. + // + _logger = _communicator->getLogger(); + + // + // Determines whether we ignore SIGHUP/CTRL_LOGOFF_EVENT. + // + _nohup = _communicator->getProperties()->getPropertyAsIntWithDefault("Ice.Nohup", 1) > 0; + + DWORD status = EXIT_FAILURE; + try + { + int tmpStatus = EXIT_FAILURE; + if(start(argc, args, tmpStatus)) + { + trace("Service started successfully."); + + // + // Change the current status from START_PENDING to RUNNING. + // + serviceStatusManager->stopUpdate(); + serviceStatusManager->changeStatus(SERVICE_RUNNING, SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + + // + // Wait for the service to be shut down. + // + waitForShutdown(); + + // + // Give the service a chance to clean up. + // + if(stop()) + { + status = EXIT_SUCCESS; + } + } + else + { + status = tmpStatus; + } + } + catch(const IceUtil::Exception& ex) + { + ostringstream ostr; + ostr << "service caught unhandled Ice exception:\n" << ex; + error(ostr.str()); + } + catch(...) + { + error("service caught unhandled C++ exception"); + } + + delete[] args; + + try + { + assert(_communicator); + _communicator->destroy(); + } + catch(...) + { + } + + terminateService(status); +} + +void +Ice::Service::control(int ctrl) +{ + assert(serviceStatusManager); + + switch(ctrl) + { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + { + serviceStatusManager->startUpdate(SERVICE_STOP_PENDING); + shutdown(); + break; + } + default: + { + if(ctrl != SERVICE_CONTROL_INTERROGATE) + { + ostringstream ostr; + ostr << "unrecognized service control code " << ctrl; + error(ostr.str()); + } + + serviceStatusManager->reportStatus(); + break; + } + } +} + +ServiceStatusManager::ServiceStatusManager(SERVICE_STATUS_HANDLE handle) : + _handle(handle), _stopped(false) +{ + _status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + _status.dwControlsAccepted = 0; + _status.dwWin32ExitCode = 0; + _status.dwServiceSpecificExitCode = 0; + _status.dwCheckPoint = 0; + _status.dwWaitHint = 0; +} + +void +ServiceStatusManager::startUpdate(DWORD state) +{ + Lock sync(*this); + + assert(state == SERVICE_START_PENDING || state == SERVICE_STOP_PENDING); + assert(!_thread); + + _status.dwCurrentState = state; + _status.dwControlsAccepted = 0; // Don't accept any other control messages while pending. + + _stopped = false; + + _thread = new StatusThread(this); + _thread->start(); +} + +void +ServiceStatusManager::stopUpdate() +{ + IceUtil::ThreadPtr thread; + + { + Lock sync(*this); + + if(_thread) + { + _stopped = true; + notify(); + thread = _thread; + _thread = 0; + } + } + + if(thread) + { + thread->getThreadControl().join(); + } +} + +void +ServiceStatusManager::changeStatus(DWORD state, DWORD controlsAccepted) +{ + Lock sync(*this); + + _status.dwCurrentState = state; + _status.dwControlsAccepted = controlsAccepted; + + SetServiceStatus(_handle, &_status); +} + +void +ServiceStatusManager::reportStatus() +{ + Lock sync(*this); + + SetServiceStatus(_handle, &_status); +} + +void +ServiceStatusManager::run() +{ + Lock sync(*this); + + IceUtil::Time delay = IceUtil::Time::milliSeconds(1000); + _status.dwWaitHint = 2000; + _status.dwCheckPoint = 0; + + while(!_stopped) + { + _status.dwCheckPoint++; + SetServiceStatus(_handle, &_status); + timedWait(delay); + } +} + +#else + +int +Ice::Service::runDaemon(int argc, char* argv[], const InitializationData& initData) +{ + assert(_service); + + // + // Create a pipe that is used to notify the parent when the child is ready. + // + SOCKET fds[2]; + IceInternal::createPipe(fds); + + // + // Fork the child. + // + pid_t pid = fork(); + if(pid < 0) + { + if(argv[0]) + { + cerr << argv[0] << ": "; + } + cerr << strerror(errno) << endl; + return EXIT_FAILURE; + } + + if(pid != 0) + { + // + // Parent process. + // + + // + // Close an unused end of the pipe. + // + close(fds[1]); + + // + // Wait for the child to write a byte to the pipe to indicate that it + // is ready to receive requests, or that an error occurred. + // + char c = 0; + while(true) + { + if(read(fds[0], &c, 1) == -1) + { + if(IceInternal::interrupted()) + { + continue; + } + + if(argv[0]) + { + cerr << argv[0] << ": "; + } + cerr << strerror(errno) << endl; + _exit(EXIT_FAILURE); + } + break; + } + + if(c != 0) + { + // + // Read an error message. + // + char msg[1024]; + size_t pos = 0; + while(pos < sizeof(msg)) + { + ssize_t n = read(fds[0], &msg[pos], sizeof(msg) - pos); + if(n == -1) + { + if(IceInternal::interrupted()) + { + continue; + } + + if(argv[0]) + { + cerr << ": "; + } + cerr << "I/O error while reading error message from child:\n" << strerror(errno) << endl; + _exit(EXIT_FAILURE); + } + pos += n; + break; + } + if(argv[0]) + { + cerr << argv[0] << ": "; + } + cerr << "failure occurred in daemon"; + if(strlen(msg) > 0) + { + cerr << ':' << endl << msg; + } + cerr << endl; + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + // + // Child process. + // + + string errMsg; + int status = EXIT_FAILURE; + try + { + // + // Become a session and process group leader. + // + if(setsid() == -1) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSystemErrno(); + throw ex; + } + + // + // Ignore SIGHUP so that the grandchild process is not sent SIGHUP when this + // process exits. + // + signal(SIGHUP, SIG_IGN); + + // + // Fork again to eliminate the possibility of acquiring a controlling terminal. + // + pid = fork(); + if(pid < 0) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSystemErrno(); + throw ex; + } + if(pid != 0) + { + exit(0); + } + + if(_changeDirectory) + { + // + // Change the working directory. + // + if(chdir("/") != 0) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSystemErrno(); + throw ex; + } + } + + vector<int> fdsToClose; + if(_closeFiles) + { + // + // Take a snapshot of the open file descriptors. We don't actually close these + // descriptors until after the communicator is initialized, so that plug-ins + // have an opportunity to use stdin/stdout/stderr if necessary. This also + // conveniently allows the Ice.PrintProcessId property to work as expected. + // + int fdMax = static_cast<int>(sysconf(_SC_OPEN_MAX)); + if(fdMax <= 0) + { + SyscallException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSystemErrno(); + throw ex; + } + + for(int i = 0; i < fdMax; ++i) + { + if(fcntl(i, F_GETFL) != -1) + { + // + // Don't close the write end of the pipe. + // + if(i != fds[1]) + { + fdsToClose.push_back(i); + } + } + } + } + + // + // Create the CtrlCHandler after forking the child so that signals are initialized + // properly. We do this before initializing the communicator because we need to + // ensure that signals are initialized before additional threads are created. The + // communicator thread pools currently use lazy initialization, but a thread can + // be created to monitor connections. + // + _ctrlCHandler = new IceUtil::CtrlCHandler; + + // + // Initialize the communicator. + // + _communicator = initializeCommunicator(argc, argv, initData); + + if(_closeFiles) + { + // + // Close unnecessary file descriptors. + // + PropertiesPtr properties = _communicator->getProperties(); + string stdOut = properties->getProperty("Ice.StdOut"); + string stdErr = properties->getProperty("Ice.StdErr"); + + vector<int>::const_iterator p; + for(p = fdsToClose.begin(); p != fdsToClose.end(); ++p) + { + // + // NOTE: Do not close stdout if Ice.StdOut is defined. Likewise for Ice.StdErr. + // + if((*p == 1 && !stdOut.empty()) || (*p == 2 && !stdErr.empty())) + { + continue; + } + close(*p); + } + + // + // Associate stdin, stdout and stderr with /dev/null. + // + int fd; + fd = open("/dev/null", O_RDWR); + assert(fd == 0); + if(stdOut.empty()) + { + fd = dup2(0, 1); + assert(fd == 1); + } + if(stdErr.empty()) + { + fd = dup2(1, 2); + assert(fd == 2); + } + } + + // + // Write PID + // + if(_pidFile.size() > 0) + { + IceUtilInternal::ofstream of(Ice::nativeToUTF8(_communicator, _pidFile)); + of << getpid() << endl; + + if(!of) + { + warning("Could not write PID file " + _pidFile); + } + } + + // + // Use the configured logger. + // + _logger = _communicator->getLogger(); + + // + // Start the service. + // + if(start(argc, argv, status)) + { + // + // Notify the parent that the child is ready. + // + char c = 0; + while(true) + { + if(write(fds[1], &c, 1) == -1) + { + if(IceInternal::interrupted()) + { + continue; + } + } + break; + } + close(fds[1]); + fds[1] = -1; + + // + // Wait for service shutdown. + // + waitForShutdown(); + + // + // Stop the service. + // + if(stop()) + { + status = EXIT_SUCCESS; + } + } + } + catch(const IceUtil::Exception& ex) + { + ostringstream ostr; + ostr << "service caught unhandled Ice exception:\n" << ex; + errMsg = ostr.str(); + error(errMsg); + } + catch(...) + { + errMsg = "service caught unhandled C++ exception"; + error(errMsg); + } + + // + // If the service failed and the pipe to the parent is still open, + // then send an error notification to the parent. + // + if(status != EXIT_SUCCESS && fds[1] != -1) + { + char c = 1; + while(true) + { + if(write(fds[1], &c, 1) == -1) + { + if(IceInternal::interrupted()) + { + continue; + } + } + break; + } + const char* msg = errMsg.c_str(); + size_t len = strlen(msg) + 1; // Include null byte + size_t pos = 0; + while(len > 0) + { + ssize_t n = write(fds[1], &msg[pos], len); + if(n == -1) + { + if(IceInternal::interrupted()) + { + continue; + } + else + { + break; + } + } + len -= n; + pos += n; + } + close(fds[1]); + } + + if(_communicator) + { + try + { + _communicator->destroy(); + } + catch(...) + { + } + } + + return status; +} + +#endif |