// \file service.cpp // // Copyright (C) 2014 MicroNeil Research Corporation. // // This program is part of the MicroNeil Research Open Library Project. For // more information go to http://www.microneil.com/OpenLibrary/index.html // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2 of the License, or (at your // option) any later version. // // This program 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, write to the Free Software Foundation, Inc., 59 Temple // Place, Suite 330, Boston, MA 02111-1307 USA //============================================================================== #ifdef WIN32 #include #include #else #include #include #include #include #include #endif #ifdef DEBUG_LOG_FILE #include #endif #include #include #include #include "service.hpp" #ifdef WIN32 // Application main entry point for Windows. int main(int argc, char *argv[]) { #ifdef DEBUG_LOG_FILE std::ofstream logStream; logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "main. argc: " << argc << std::endl; for (int i = 0; i < argc; i++) { logStream << "arg " << i << ": '" << argv[i] << "'" << std::endl; } logStream.close(); #endif CodeDweller::Service &service = CodeDweller::Service::getInstance(); return service.main(argc, (char **) argv); } // Service entry point for Windows. VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv) { #ifdef DEBUG_LOG_FILE std::ofstream logStream; logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "ServiceMain. argc: " << argc << std::endl; for (unsigned int i = 0; i < argc; i++) { logStream << "arg " << i << ": '" << argv[i] << "'" << std::endl; } logStream.close(); #endif CodeDweller::Service &service = CodeDweller::Service::getInstance(); return service.serviceMain(argc, argv); } /// Control message handler for Windows. VOID WINAPI ServiceCtrlHandler(DWORD message) { #ifdef DEBUG_LOG_FILE std::ofstream logStream; logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "ServiceCtrlHandler. message: " << message << ". Calling processCtrlMessage" << std::endl; logStream.close(); #endif CodeDweller::Service &service = CodeDweller::Service::getInstance(); service.processCtrlMessage(message); #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "ServiceCtrlHandler. Done." << std::endl; logStream.close(); #endif } #else // Main program for *nix daemon. int main(int argc, char *argv[]) { CodeDweller::Service &service = CodeDweller::Service::getInstance(); return service.main(argc, argv); } #endif namespace CodeDweller { std::mutex Service::objectMutex; std::vector Service::cmdLineArgs; bool Service::pauseReceived = false; bool Service::resumeReceived = false; bool Service::stopReceived = false; std::vector Service::pauseCallbacks; std::vector Service::resumeCallbacks; std::vector Service::stopCallbacks; int Service::stopCallbackTimeout_ms = 30 * 1000; Service::Service() : stopCallbacksActive(true) { } Service &Service::getInstance() { std::lock_guard scopeMutex(objectMutex); static Service service; return service; } int Service::main(int argc, char *argv[]) { // Under Windows, only arg 0 is passed. Under *nix, all arguments // are passed. loadArguments(argc, (char **) argv); #ifdef DEBUG_LOG_FILE std::ofstream logStream; logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "Service::main. argc: " << argc << std::endl; for (int i = 0; i < argc; i++) { logStream << "arg " << i << ": '" << argv[i] << "'" << std::endl; } logStream.close(); #endif #ifdef WIN32 SERVICE_TABLE_ENTRY ServiceTable[] = { {(LPTSTR) "", ServiceMain}, {NULL, NULL} }; if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) { return GetLastError(); } return 0; #else pid_t pid; // Fork the process. pid = fork(); if (pid < 0) { perror("Error from first fork"); return(EXIT_FAILURE); } // Terminate the parent. if (pid > 0) { return(EXIT_SUCCESS); } // This is the child. Become the session leader. if (setsid() < 0) { perror("Error from setsid"); return(EXIT_FAILURE); } // Fork again. pid = fork(); if (pid < 0) { perror("Error from second fork"); return(EXIT_FAILURE); } // Terminate the parent. if (pid > 0) { return(EXIT_SUCCESS); } // Set default file permissions. This call always succeeds. umask(0); // Disassociate the process from the default directory of the // grandparent. if (chdir("/") != 0) { perror("Error from chdir"); return(EXIT_FAILURE); } // Initialize the set of signals to wait for. if (sigemptyset(&signalSet) != 0) { perror("Error from sigemptyset"); return(EXIT_FAILURE); } if ((sigaddset(&signalSet, SIGTSTP) != 0) || (sigaddset(&signalSet, SIGCONT) != 0) || (sigaddset(&signalSet, SIGTERM) != 0)) { perror("Error from sigaddset"); return(EXIT_FAILURE); } // Block the signals. if (sigprocmask(SIG_BLOCK, &signalSet, NULL) != 0) { perror("Error from sigprocmask"); return(EXIT_FAILURE); } // Close all open file descriptors. for (int fd = 2; fd >= 0; fd--) { if (close(fd) != 0) { // There is no controlling terminal to send any message to. return(EXIT_FAILURE); } } // Connect standard I/O to the null device. int fd; // stdin. fd = open("/dev/null", O_RDONLY); if (fd < 0) { return(EXIT_FAILURE); } // stdout. fd = open("/dev/null", O_WRONLY); if (fd < 0) { return(EXIT_FAILURE); } // stderr. fd = open("/dev/null", O_WRONLY); if (fd < 0) { return(EXIT_FAILURE); } // Start the thread to process messages. std::thread messageThread(&Service::processMessages, this); // Run the service. int status; status = run(); // Send a Stop message so that messageThread exits. pthread_kill(messageThread.native_handle(), SIGTERM); messageThread.join(); return(status); #endif } #ifdef WIN32 void Service::serviceMain(DWORD argc, LPTSTR *argv) { // Arg 0 contains the service name; skip it. The remaining // arguments contain the command-line arguments, excluding the // executable name; append them to the object's argument list. loadArguments(argc - 1, (char **) argv + 1); // Register the service control handler with the SCM. serviceStatusHandle = RegisterServiceCtrlHandler("", ServiceCtrlHandler); #ifdef DEBUG_LOG_FILE std::ofstream logStream; logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "ServiceMain. argc: " << argc << std::endl; for (DWORD i = 0; i < argc; i++) { logStream << "arg " << i << ": '" << argv[i] << "'" << std::endl; } logStream << " serviceStatusHandle == NULL: " << (NULL == serviceStatusHandle) << std::endl; logStream.close(); #endif if (serviceStatusHandle == NULL) { return; } ZeroMemory(&serviceStatus, sizeof(serviceStatus)); serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; serviceStatus.dwControlsAccepted = 0; serviceStatus.dwCurrentState = SERVICE_START_PENDING; serviceStatus.dwWin32ExitCode = NO_ERROR; serviceStatus.dwServiceSpecificExitCode = 0; serviceStatus.dwCheckPoint = 0; serviceStatus.dwWaitHint = 5000; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); // Tell the service controller that the service has started. serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; serviceStatus.dwCurrentState = SERVICE_RUNNING; serviceStatus.dwWin32ExitCode = 0; serviceStatus.dwCheckPoint = 0; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "Starting application thread." << std::endl; logStream.close(); #endif // Start the application thread. int status; status = run(); // Change status to stopped. serviceStatus.dwControlsAccepted = 0; serviceStatus.dwCurrentState = SERVICE_STOPPED; serviceStatus.dwWin32ExitCode = status; serviceStatus.dwCheckPoint = 0; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "Exiting." << std::endl; logStream.close(); #endif return; } #endif void Service::loadArguments(int argc, char *argv[]) { for (int i = 0; i < argc; i++) { cmdLineArgs.push_back(argv[i]); } } const std::vector &Service::arguments() { return cmdLineArgs; } void Service::setStopCallbackTimeout_ms(int timeout_ms) { stopCallbackTimeout_ms = (timeout_ms < 1 ? 1 : timeout_ms); } void Service::onPauseCall(Callback &pauseFunctor) { std::lock_guard scopeMutex(objectMutex); pauseCallbacks.push_back(&pauseFunctor); } void Service::onResumeCall(Callback &resumeFunctor) { std::lock_guard scopeMutex(objectMutex); resumeCallbacks.push_back(&resumeFunctor); } void Service::onStopCall(Callback &stopFunctor) { std::lock_guard scopeMutex(objectMutex); stopCallbacks.push_back(&stopFunctor); } bool Service::receivedPause() { return (pauseReceived); } bool Service::receivedResume() { return (resumeReceived); } bool Service::receivedStop() { return (stopReceived); } void Service::clearReceivedPause() { pauseReceived = false; } void Service::clearReceivedResume() { resumeReceived = false; } void Service::clearReceivedStop() { stopReceived = false; } #ifdef WIN32 void Service::processCtrlMessage(DWORD message) { #ifdef DEBUG_LOG_FILE std::ofstream logStream; auto startTime = std::chrono::steady_clock::now(); logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "processCtrlMessage. message: " << message << std::endl; logStream.close(); #endif switch (message) { case SERVICE_CONTROL_PAUSE: #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "processCtrlMessage. Received Pause" << std::endl; logStream.close(); #endif serviceStatus.dwControlsAccepted = 0; serviceStatus.dwCurrentState = SERVICE_PAUSE_PENDING; serviceStatus.dwWin32ExitCode = NO_ERROR; serviceStatus.dwCheckPoint = 1; serviceStatus.dwWaitHint = 1000 * 60; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); pauseReceived = true; #ifdef DEBUG_LOG_FILE startTime = std::chrono::steady_clock::now(); #endif for (auto callback : pauseCallbacks) { (*callback)(); serviceStatus.dwCheckPoint++; #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "Service::processCtrlMessage. " << "Cumulative time after callback (ms): " << std::chrono::duration_cast (std::chrono::steady_clock::now() - startTime).count() << ", dwWaitHint: " << serviceStatus.dwWaitHint << std::endl; logStream.close(); #endif (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "Service::processCtrlMessage. " << "Cumulative time after SetServiceStatus (ms): " << std::chrono::duration_cast (std::chrono::steady_clock::now() - startTime).count() << std::endl; logStream.close(); #endif } serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; serviceStatus.dwCurrentState = SERVICE_PAUSED; serviceStatus.dwWin32ExitCode = NO_ERROR; serviceStatus.dwCheckPoint = 0; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "Service::processCtrlMessage. " << "Cumulative time after final SetServiceStatus (ms): " << std::chrono::duration_cast (std::chrono::steady_clock::now() - startTime).count() << std::endl; logStream.close(); #endif break; case SERVICE_CONTROL_CONTINUE: #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "processCtrlMessage. Received Resume" << std::endl; logStream.close(); #endif serviceStatus.dwControlsAccepted = 0; serviceStatus.dwCurrentState = SERVICE_CONTINUE_PENDING; serviceStatus.dwWin32ExitCode = NO_ERROR; serviceStatus.dwCheckPoint = 1; serviceStatus.dwWaitHint = 1000 * 60; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); resumeReceived = true; for (auto callback : resumeCallbacks) { (*callback)(); serviceStatus.dwCheckPoint++; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); } serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; serviceStatus.dwCurrentState = SERVICE_RUNNING; serviceStatus.dwWin32ExitCode = NO_ERROR; serviceStatus.dwCheckPoint = 0; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); break; case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: { #ifdef DEBUG_LOG_FILE logStream.open(DEBUG_LOG_FILE, std::fstream::app); logStream << "processCtrlMessage. Received " << (message == SERVICE_CONTROL_STOP ? "STOP" : "SHUTDOWN") << std::endl; logStream.close(); #endif serviceStatus.dwControlsAccepted = 0; serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; serviceStatus.dwWin32ExitCode = NO_ERROR; serviceStatus.dwCheckPoint = 1; serviceStatus.dwWaitHint = stopCallbackTimeout_ms; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); stopReceived = true; stopCallbacksActive = true; // Get the timeout time. timeoutTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(stopCallbackTimeout_ms); // Start watchdog thread. std::thread watchdogThread(&Service::watchdog, this); for (auto callback : stopCallbacks) { (*callback)(); timeoutTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(stopCallbackTimeout_ms); serviceStatus.dwCheckPoint++; (void) SetServiceStatus(serviceStatusHandle, &serviceStatus); } stopCallbacksActive = false; watchdogThread.join(); } break; default: break; } } #else void Service::processMessages() { int sigNum; int status; while (true) { // Wait for a signal. status = sigwait(&signalSet, &sigNum); if (0 != status) { perror("Error from sigwait"); // TODO: Implement recovery. ; } // Process the message. switch (sigNum) { case SIGTSTP: { pauseReceived = true; for (auto callback : pauseCallbacks) { (*callback)(); } } break; case SIGCONT: { resumeReceived = true; for (auto callback : resumeCallbacks) { (*callback)(); } } break; case SIGTERM: { stopReceived = true; stopCallbacksActive = true; // Get the timeout time. timeoutTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(stopCallbackTimeout_ms); // Start watchdog thread. std::thread watchdogThread(&Service::watchdog, this); for (auto callback : stopCallbacks) { (*callback)(); timeoutTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(stopCallbackTimeout_ms); } stopCallbacksActive = false; watchdogThread.join(); } // Exit. return; break; default: ; } // switch. } // while. } #endif void Service::watchdog() { // Sleep and check whether the process should exit. std::chrono::milliseconds sleepTime(100); while (std::chrono::steady_clock::now() < timeoutTime) { std::this_thread::sleep_for(sleepTime); if (!stopCallbacksActive) { return; } } // Timeout expired. std::exit(EXIT_FAILURE); } }