git-svn-id: https://svn.microneil.com/svn/CodeDweller/branches/adeniz_1@35 d34b734f-a00e-4b39-a726-e4eeb87269abadeniz_1
// \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 | |||||
//============================================================================== | |||||
#include <unistd.h> | |||||
#include <sys/types.h> | |||||
#include <sys/stat.h> | |||||
#include <fcntl.h> | |||||
#include <signal.h> | |||||
#include <iostream> | |||||
#include <cstdio> | |||||
#include <cstdlib> | |||||
#include <thread> | |||||
#include <chrono> | |||||
#include "service.hpp" | |||||
// Main program for *nix daemon. | |||||
int main(int argc, char *argv[]) { | |||||
CodeDweller::Service &service = CodeDweller::Service::getInstance(); | |||||
return service.main(argc, argv); | |||||
} | |||||
namespace CodeDweller { | |||||
Service::Service() : | |||||
lastMessage(Message::None) { | |||||
} | |||||
int Service::main(int argc, char *argv[]) { | |||||
// Load the arguments. | |||||
loadArguments(argc, argv); | |||||
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, SIGHUP) != 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(); | |||||
messageThread.join(); | |||||
return(status); | |||||
} | |||||
void Service::loadArguments(int argc, char *argv[]) { | |||||
for (int i = 0; i < argc; i++) { | |||||
cmdLineArgs.push_back(argv[i]); | |||||
} | |||||
} | |||||
const std::vector<std::string> &Service::arguments() { | |||||
return cmdLineArgs; | |||||
} | |||||
void Service::onStopCall(Callback *stopFunctor) { | |||||
stopCallbacks.push_back(stopFunctor); | |||||
} | |||||
bool Service::receivedStop() { | |||||
return (Message::Stop == lastMessage); | |||||
} | |||||
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: | |||||
lastMessage = Message::Pause; | |||||
break; | |||||
case SIGCONT: | |||||
lastMessage = Message::Resume; | |||||
break; | |||||
case SIGHUP: | |||||
lastMessage = Message::Restart; | |||||
break; | |||||
case SIGTERM: | |||||
lastMessage = Message::Stop; | |||||
for (auto callback : stopCallbacks) { | |||||
(*callback)(); | |||||
} | |||||
// Exit. | |||||
return; | |||||
break; | |||||
default: | |||||
; | |||||
} // switch. | |||||
} // while. | |||||
} | |||||
} |
// \file service.hpp | |||||
// | |||||
// 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 | |||||
//============================================================================== | |||||
/* | |||||
\brief The service module provides a framework for implementing *nix | |||||
daemons and Windows services. | |||||
*/ | |||||
#ifndef SERVICE_HPP | |||||
#define SERVICE_HPP | |||||
#include <string> | |||||
#include <vector> | |||||
namespace CodeDweller { | |||||
/** Singleton class that implements a daemon (*nix) or service | |||||
(Windows). | |||||
This class implements a *nix daemon or a Windows service. | |||||
To implement a daemon or service, implement the required methods | |||||
of this class, and link with service.cpp. When compiled under | |||||
*nix, the file service.cpp contains the definition of main for | |||||
the *nix daemon. When compiled under Windows, the file | |||||
service.cpp contains the entry points for the Windows service. | |||||
Nomenclature: | |||||
<ol> | |||||
<li>Service is a *nix daemon or Windows service.</li> | |||||
<li>Message is a posix signal or Windows message. This class | |||||
supports the following messages: | |||||
<ol> | |||||
<li>Pause. This is the posix TSTP signal or Windows Pause | |||||
message. This instructs the service to temporarily stop | |||||
running.</li> | |||||
<li>Resume. This is the posix CONT signal or Windows | |||||
Resume message. This instructs the service to resume | |||||
running after receiving a Pause message. If the service | |||||
isn't temporarily stopped after receiving a Pause message, | |||||
the Resume message has no effect.</li> | |||||
<li>Restart. This is the posix HUP signal or Windows | |||||
Restart message. This instructs the service to shut down | |||||
and restart.</li> | |||||
<li>Stop. This is the posix TERM signal or Windows Stop | |||||
message. This instructs the service to shut down and | |||||
exit.</li> | |||||
</ol> | |||||
</li> | |||||
<li>Callback. This is a function that is executed when a | |||||
message is received.</li> | |||||
</ol> | |||||
*/ | |||||
class Service | |||||
{ | |||||
public: | |||||
/// Get the instance of the singleton. | |||||
static Service& getInstance() | |||||
{ | |||||
static Service service; | |||||
return service; | |||||
} | |||||
/// Callback functor interface. | |||||
class Callback { | |||||
public: | |||||
/// Callback method. | |||||
virtual void operator()() = 0; | |||||
}; | |||||
/// Main entry point. | |||||
// | |||||
// \param[in] argc is the number of arguments. | |||||
// | |||||
// \param[in] argv is an array of strings containing the | |||||
// command-line arguments. The end of the array is indicated by a | |||||
// null pointer. | |||||
// | |||||
// \returns exit code of the service. | |||||
// | |||||
int main(int argc, char *argv[]); | |||||
/// Register a callback for receipt of Stop. | |||||
// | |||||
// \param[in] stopFunctor is the function object to invoke when | |||||
// Stop is received. | |||||
// | |||||
void onStopCall(Callback *stopFunctor); | |||||
/// Check whether the last message received was Stop. | |||||
// | |||||
// \returns true if Stop was the most recent message received, | |||||
// false otherwise. | |||||
// | |||||
bool receivedStop(); | |||||
/// Get a reference to the command-line arguments. | |||||
// | |||||
// \returns a reference to the vector of command-line arguments of | |||||
// the application. Index i corresponds to command-line argument | |||||
// i. | |||||
// | |||||
const std::vector<std::string> &arguments(); | |||||
private: | |||||
/// Private constructor prevents instantiation. | |||||
Service(); | |||||
/// Prevent copying. | |||||
Service(Service const&) {} | |||||
/// Prevent assignment. | |||||
void operator=(Service const&) {} | |||||
/// Load the command-line arguments. | |||||
// | |||||
// This method loads the object with the command-line parameters. | |||||
// | |||||
// \param[in] argc is the number of arguments. | |||||
// | |||||
// \param[in] argv is an array of strings containing the | |||||
// command-line arguments. The end of the array is indicated by a | |||||
// null pointer. | |||||
// | |||||
void loadArguments(int argc, char *argv[]); | |||||
/// Initialize and run the application. | |||||
// | |||||
// \returns the exit status of the service. | |||||
// | |||||
int run(); | |||||
/// Thread start function to receive messages. | |||||
void processMessages(); | |||||
/// Command-line arguments. | |||||
std::vector<std::string> cmdLineArgs; | |||||
/// Enumeration specifying the most recent message received. | |||||
enum class Message { | |||||
Pause, | |||||
Resume, | |||||
Restart, | |||||
Stop, | |||||
None | |||||
}; | |||||
/// Most recent message received. | |||||
Message lastMessage; | |||||
/// Functions to invoke when the Stop is received. | |||||
std::vector<Callback *> stopCallbacks; | |||||
/// Set of signals to wait for. | |||||
sigset_t signalSet; | |||||
}; | |||||
} | |||||
#endif // SERVICE_HPP |