Implemented daemon mode, configured from config.
I have to do a weird thing - forking during initialization of static variables. I turns out main() is too late for that, because we bind sockets in static variables and when we run second instance, it fails in bind and not in our lockfile checking code. Some other ramifications come out because of our heavy use of global variables. Don't do that in your next project - use global variables only when strictly necessary.
This commit is contained in:
parent
2bafba55a3
commit
7db74d0cce
|
@ -23,6 +23,36 @@
|
||||||
# So be cafeful.
|
# So be cafeful.
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Daemonization options.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Define Server.Daemonize to run OpenBTS in background.
|
||||||
|
#Server.Daemonize
|
||||||
|
$optional Server.Daemonize
|
||||||
|
|
||||||
|
# Directory to create PID file in.
|
||||||
|
# Filesystem Hierarchy Standard specifies it daemons should write PID
|
||||||
|
# to /var/run, but you may specify any other directory if you run
|
||||||
|
# OpenBTS manually.
|
||||||
|
#Server.WritePID /var/run/OpenBTS.pid
|
||||||
|
Server.WritePID OpenBTS.pid
|
||||||
|
$static Server.WritePID
|
||||||
|
|
||||||
|
# Set umask.
|
||||||
|
# 027(oct)=23(dec) which equals to file creation mode 0750(oct)
|
||||||
|
Server.umask 23
|
||||||
|
$static Server.umask
|
||||||
|
|
||||||
|
# Define Server.ChdirToRoot to change directory to / on daemon startup.
|
||||||
|
# This is the recommended behaviour if daemon runs system-wide, because
|
||||||
|
# prevents the current directory from being locked; hence not being able
|
||||||
|
# to remove it. But when you run manually this behaviour may be inconvenient
|
||||||
|
# for you, because all paths should be absolute.
|
||||||
|
#Server.ChdirToRoot
|
||||||
|
$optional Server.ChdirToRoot
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Logging and reporting parameters
|
# Logging and reporting parameters
|
||||||
#
|
#
|
||||||
|
|
|
@ -47,15 +47,33 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace GSM;
|
using namespace GSM;
|
||||||
using namespace CommandLine;
|
using namespace CommandLine;
|
||||||
|
|
||||||
|
static int daemonize();
|
||||||
|
|
||||||
|
class DaemonInitializer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DaemonInitializer(bool doDaemonize)
|
||||||
|
{
|
||||||
|
// Start in daemon mode?
|
||||||
|
if (doDaemonize)
|
||||||
|
if (daemonize() != EXIT_SUCCESS)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
// Load configuration from a file.
|
// Load configuration from a file.
|
||||||
ConfigurationTable gConfig("OpenBTS.config");
|
ConfigurationTable gConfig("OpenBTS.config");
|
||||||
// Initialize Logger form the config.
|
// Initialize Logger form the config.
|
||||||
LogInitializer gLogInitializer;
|
LogInitializer gLogInitializer;
|
||||||
|
// Fork daemon if needed.
|
||||||
|
DaemonInitializer gDaemonInitializer(gConfig.defines("Server.Daemonize"));
|
||||||
|
|
||||||
|
|
||||||
// All of the other globals that rely on the global configuration file need to
|
// All of the other globals that rely on the global configuration file need to
|
||||||
|
@ -109,7 +127,7 @@ void restartTransceiver()
|
||||||
|
|
||||||
void startBTS()
|
void startBTS()
|
||||||
{
|
{
|
||||||
COUT("\nStarting the system...");
|
cout << endl << "Starting the system..." << endl;
|
||||||
|
|
||||||
if (gConfig.defines("Control.TMSITable.SavePath")) {
|
if (gConfig.defines("Control.TMSITable.SavePath")) {
|
||||||
gTMSITable.load(gConfig.getStr("Control.TMSITable.SavePath"));
|
gTMSITable.load(gConfig.getStr("Control.TMSITable.SavePath"));
|
||||||
|
@ -275,9 +293,10 @@ void exitCLI()
|
||||||
fclose(stdin);
|
fclose(stdin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void signalHandler(int sig)
|
static void signalHandler(int sig)
|
||||||
{
|
{
|
||||||
COUT("Handling signal " << sig);
|
COUT("Handling signal " << sig);
|
||||||
|
LOG(INFO) << "Handling signal " << sig;
|
||||||
switch(sig){
|
switch(sig){
|
||||||
case SIGHUP:
|
case SIGHUP:
|
||||||
// re-read the config
|
// re-read the config
|
||||||
|
@ -293,6 +312,151 @@ void signalHandler(int sig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void childHandler(int signum)
|
||||||
|
{
|
||||||
|
LOG(INFO) << "Handling signal " << signum;
|
||||||
|
switch(signum) {
|
||||||
|
case SIGALRM:
|
||||||
|
// alarm() fired.
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
break;
|
||||||
|
case SIGUSR1:
|
||||||
|
//Child sent us a signal. Good sign!
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
break;
|
||||||
|
case SIGCHLD:
|
||||||
|
// Child has died
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int daemonize()
|
||||||
|
{
|
||||||
|
// Already a daemon
|
||||||
|
if ( getppid() == 1 ) return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
// Sanity checks
|
||||||
|
if (strcasecmp(gConfig.getStr("CLI.Type"),"Local") == 0) {
|
||||||
|
LOG(ERROR) << "OpenBTS runs in daemon mode, but CLI is set to Local!";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (!gConfig.defines("Server.WritePID")) {
|
||||||
|
LOG(ERROR) << "OpenBTS runs in daemon mode, but Server.WritePID is not set in config!";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// According to the Filesystem Hierarchy Standard 5.13.2:
|
||||||
|
// "The naming convention for PID files is <program-name>.pid."
|
||||||
|
// The same standard specifies that PID files should be placed
|
||||||
|
// in /var/run, but we make this configurable.
|
||||||
|
std::string lockfile = gConfig.getStr("Server.WritePID");
|
||||||
|
|
||||||
|
// Create the PID file as the current user
|
||||||
|
int lfp = open(lockfile.data(), O_RDWR|O_CREAT, 0640);
|
||||||
|
if (lfp < 0) {
|
||||||
|
LOG(ERROR) << "Unable to create PID file " << lockfile << ", code="
|
||||||
|
<< errno << " (" << strerror(errno) << ")";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Created PID file " << lockfile;
|
||||||
|
|
||||||
|
// Drop user if there is one, and we were run as root
|
||||||
|
/* if ( getuid() == 0 || geteuid() == 0 ) {
|
||||||
|
struct passwd *pw = getpwnam(RUN_AS_USER);
|
||||||
|
if ( pw ) {
|
||||||
|
syslog( LOG_NOTICE, "setting user to " RUN_AS_USER );
|
||||||
|
setuid( pw->pw_uid );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Trap signals that we expect to receive
|
||||||
|
signal(SIGCHLD, childHandler);
|
||||||
|
signal(SIGUSR1, childHandler);
|
||||||
|
signal(SIGALRM, childHandler);
|
||||||
|
|
||||||
|
// Fork off the parent process
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
LOG(ERROR) << "Unable to fork daemon, code=" << errno
|
||||||
|
<< " (" << strerror(errno) << ")";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
// If we got a good PID, then we can exit the parent process.
|
||||||
|
if (pid > 0) {
|
||||||
|
// Wait for confirmation from the child via SIGUSR1 or SIGCHLD.
|
||||||
|
LOG(INFO) << "Forked child process with PID " << pid;
|
||||||
|
// Some recommend to add timeout here too (it will signal SIGALRM),
|
||||||
|
// but I don't think it's a good idea if we start on a slow system.
|
||||||
|
// Or may be we should make timeout value configurable and set it
|
||||||
|
// a big enough value.
|
||||||
|
// alarm(2);
|
||||||
|
// pause() should not return.
|
||||||
|
pause();
|
||||||
|
LOG(ERROR) << "Executing code after pause()!";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now lock our PID file and write our PID to it
|
||||||
|
if (lockf(lfp, F_TLOCK,0) < 0) {
|
||||||
|
LOG(ERROR) << "Unable to lock PID file " << lockfile << ", code="
|
||||||
|
<< errno << " (" << strerror(errno) << ")";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
char tempBuf[64];
|
||||||
|
snprintf(tempBuf, sizeof(tempBuf), "%d\n", getpid());
|
||||||
|
ssize_t tempDataLen = strlen(tempBuf);
|
||||||
|
if (write(lfp, tempBuf, tempDataLen) != tempDataLen) {
|
||||||
|
LOG(ERROR) << "Unable to wite PID to file " << lockfile << ", code="
|
||||||
|
<< errno << " (" << strerror(errno) << ")";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we are executing as the child process
|
||||||
|
pid_t parent = getppid();
|
||||||
|
|
||||||
|
// Return SIGCHLD to default handler
|
||||||
|
signal(SIGCHLD, SIG_DFL);
|
||||||
|
|
||||||
|
// Change the file mode mask
|
||||||
|
// This will restrict file creation mode to 750 (complement of 027).
|
||||||
|
umask(gConfig.getNum("Server.umask"));
|
||||||
|
|
||||||
|
// Create a new SID for the child process
|
||||||
|
pid_t sid = setsid();
|
||||||
|
if (sid < 0) {
|
||||||
|
LOG(ERROR) << "Unable to create a new session, code=" << errno
|
||||||
|
<< " (" << strerror(errno) << ")";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the current working directory. This prevents the current
|
||||||
|
// directory from being locked; hence not being able to remove it.
|
||||||
|
if (gConfig.defines("Server.ChdirToRoot")) {
|
||||||
|
if (chdir("/") < 0) {
|
||||||
|
LOG(ERROR) << "Unable to change directory to %s, code" << errno
|
||||||
|
<< " (" << strerror(errno) << ")";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} else {
|
||||||
|
LOG(INFO) << "Changed current directory to \"/\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect standard files to /dev/null
|
||||||
|
if (freopen( "/dev/null", "r", stdin) == NULL)
|
||||||
|
LOG(WARN) << "Error redirecting stdin to /dev/null";
|
||||||
|
if (freopen( "/dev/null", "w", stdout) == NULL)
|
||||||
|
LOG(WARN) << "Error redirecting stdout to /dev/null";
|
||||||
|
if (freopen( "/dev/null", "w", stderr) == NULL)
|
||||||
|
LOG(WARN) << "Error redirecting stderr to /dev/null";
|
||||||
|
|
||||||
|
// Tell the parent process that we are okay
|
||||||
|
kill(parent, SIGUSR1);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
srandom(time(NULL));
|
srandom(time(NULL));
|
||||||
|
@ -300,15 +464,23 @@ int main(int argc, char *argv[])
|
||||||
// Catch signal to re-read config
|
// Catch signal to re-read config
|
||||||
if (signal(SIGHUP, signalHandler) == SIG_ERR) {
|
if (signal(SIGHUP, signalHandler) == SIG_ERR) {
|
||||||
cerr << "Error while setting handler for SIGHUP.";
|
cerr << "Error while setting handler for SIGHUP.";
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
// Catch signal to shutdown gracefully
|
// Catch signal to shutdown gracefully
|
||||||
if (signal(SIGTERM, signalHandler) == SIG_ERR) {
|
if (signal(SIGTERM, signalHandler) == SIG_ERR) {
|
||||||
cerr << "Error while setting handler for SIGTERM.";
|
cerr << "Error while setting handler for SIGTERM.";
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
// Catch Ctrl-C signal
|
// Catch Ctrl-C signal
|
||||||
if (signal(SIGINT, signalHandler) == SIG_ERR) {
|
if (signal(SIGINT, signalHandler) == SIG_ERR) {
|
||||||
cerr << "Error while setting handler for SIGINT.";
|
cerr << "Error while setting handler for SIGINT.";
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
// Various TTY signals
|
||||||
|
// We don't really care about return values of these.
|
||||||
|
signal(SIGTSTP,SIG_IGN);
|
||||||
|
signal(SIGTTOU,SIG_IGN);
|
||||||
|
signal(SIGTTIN,SIG_IGN);
|
||||||
|
|
||||||
cout << endl << endl << gOpenBTSWelcome << endl;
|
cout << endl << endl << gOpenBTSWelcome << endl;
|
||||||
|
|
||||||
|
@ -331,6 +503,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
stopBTS();
|
stopBTS();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// vim: ts=4 sw=4
|
// vim: ts=4 sw=4
|
||||||
|
|
Reference in New Issue