Patch to give player server ability to daemonize

Taahir Ahmed <[email protected]>
Newsgroups gmane.science.robotics.playerstage
Organization TAMU-CS-DAIRL
Message-ID <3516440.FpNkqqXHWu@basis>
I've attached of a local modification to player that might be useful to others.  
I'm going to be running multirobot experiments using the python fabric library 
to control experiment trials, and I needed the ability for player to detach 
itself from its shell.

This patch gives the player server the ability to become a standard unix 
daemon if it's passed the -s command line argument.

I've done some rudimentary testing to ensure that clients can still connect to 
the server in daemon mode, etc.

Is there anything about this code that needs to be changed to be included in 
the player mainline?

The patch itself was generated using the git format-patch command.  If another 
format is preferred, just let me know.

Taahir Ahmed
Distributed AI Robotics Laboratory
Texas A&M University

------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and 
threat landscape has changed and how IT managers can respond. Discussions 
will include endpoint security, mobile security and the latest in malware 
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/

_______________________________________________
Playerstage-developers mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/playerstage-developers
0001-Added-a-rudimentary-daemon-ability-to-the-main-playe.patch (text/x-patch, 11.7 KB)
>From 542817360c611565c8901a6ff470be7fa31f7628 Mon Sep 17 00:00:00 2001
From: Taahir Ahmed <[email protected]>
Date: Wed, 11 Jul 2012 20:16:46 -0500
Subject: [PATCH] Added a rudimentary daemon ability to the main player
 server.  If passed the -s flag, and compiled on a Unix
 platform (which I presume to be posix-compliant), player
 will detach from the console and become a daemon. 
 Stdout/stderr are redirected to /tmp/player.stdout and
 /tmp/player.stderr; I hope to be able to add syslog
 integration at a later time.

Currently, there is no way to stop the daemon besides issuing 'killall
player' or equivalent in a terminal.  I will add a -k flag to player
that will attempt to kill any current daemon instance of player.
Issuing killall does gracefully shut down the server, though.

The daemon is not suid or guid -- it runs as the user that invokes it.

Before becoming a daemon, the program will obtain (if possible)
absolute paths to the specified config and log files, as it chdir()s
to /tmp (good practice for a daemon).

I've added this functionality because I'm trying to run experiments on
many robots using the fabric python library, which uses multiple
discrete ssh session to execute different tasks.  Player needs to be
started up in one task and accessed from another, so it needs to
detach from the shell it was created in to avoid dying.
---
 cmake/internal/FindOS.cmake |    5 +
 server/server.cc            |  280 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 277 insertions(+), 8 deletions(-)

diff --git a/cmake/internal/FindOS.cmake b/cmake/internal/FindOS.cmake
index 7fcfe01..e5ce2ca 100644
--- a/cmake/internal/FindOS.cmake
+++ b/cmake/internal/FindOS.cmake
@@ -7,6 +7,11 @@ STRING (REGEX MATCH "BSD" PLAYER_OS_BSD ${CMAKE_SYSTEM_NAME})
 # Or Solaris. I'm seeing a trend, here
 STRING (REGEX MATCH "SunOS" PLAYER_OS_SOLARIS ${CMAKE_SYSTEM_NAME})
 
+# Export CMAKE's UNIX variable to source files
+IF(UNIX)
+  ADD_DEFINITIONS(-DPLAYER_UNIX)
+ENDIF(UNIX)
+
 # Windows is easy (for once)
 IF (WIN32)
     SET (PLAYER_OS_WIN TRUE BOOL INTERNAL)
diff --git a/server/server.cc b/server/server.cc
index 943c4af..d0d1c16 100644
--- a/server/server.cc
+++ b/server/server.cc
@@ -98,6 +98,18 @@ Listening on ports: 6665
   #include <unistd.h>
 #endif
 
+// Includes for umask() and open
+#ifdef PLAYER_UNIX
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif
+
+#include <stdexcept>
+using std::runtime_error;
+#include <string>
+using std::string;
+
 #include <libplayertcp/playertcp.h>
 #include <libplayertcp/playerudp.h>
 #include <libplayerinterface/functiontable.h>
@@ -117,10 +129,19 @@ void PrintCopyrightMsg();
 void PrintUsage();
 int ParseArgs(int* port, int* debuglevel,
               char** cfgfilename, int* gz_serverid, char** logfilename,
+              bool &shoud_daemonize,
               int argc, char** argv);
 void Quit(int signum);
 void Cleanup();
 
+int lockfile_id = -1;
+bool process_is_daemon = false;
+
+#ifdef PLAYER_UNIX
+runtime_error posix_exception(const string &prefix);
+bool daemonize_self();
+#endif
+
 PlayerTCP* ptcp;
 PlayerUDP* pudp;
 ConfigFile* cf;
@@ -135,10 +156,10 @@ main(int argc, char** argv)
   int debuglevel = 1;
   int port = PLAYERTCP_DEFAULT_PORT;
   int gz_serverid = -1;
-  int* ports;
-  int* new_ports;
-  int num_ports;
-  char* cfgfilename;
+  int* ports = NULL;
+  int* new_ports = NULL;
+  int num_ports = 0;
+  char* cfgfilename = NULL;
   char * logfileName = NULL;
 
 #ifdef WIN32
@@ -184,12 +205,81 @@ main(int argc, char** argv)
   assert(pudp);
 
   PrintVersion();
-  if(ParseArgs(&port, &debuglevel, &cfgfilename, &gz_serverid, &logfileName, argc, argv) < 0)
+
+  bool should_daemonize = false;
+
+  char *logfilename_unres = NULL;
+  char *cfgfilename_unres = NULL;
+
+  if(ParseArgs(&port, &debuglevel, &cfgfilename_unres, &gz_serverid,
+               &logfilename_unres, should_daemonize, argc, argv) < 0)
   {
     PrintUsage();
     exit(-1);
   }
 
+#ifdef PLAYER_UNIX
+  // Adjust logfileName and cfgfilename to be absolute paths
+  try
+  {
+      if(logfilename_unres != NULL
+         && (logfileName = realpath(logfilename_unres, logfileName)) == NULL)
+      {
+          throw posix_exception("Call to realpath on supplied"
+                                " log file name failed: ");
+      }
+      
+      if((cfgfilename = realpath(cfgfilename_unres, cfgfilename)) == NULL)
+      {
+          throw posix_exception("Call to realpath on supplied config file name"
+                                " failed: ");
+      }
+  }
+  catch(runtime_error &re)
+  {
+      PLAYER_ERROR1("Error while processing arguments: %s", re.what());
+      Cleanup();
+      return 1;
+  }
+
+  if(should_daemonize)
+  {
+      
+      // Alert the user that we're daemonizing
+      printf("Forking to daemon process...\n");
+
+      try
+      {
+          process_is_daemon = daemonize_self();
+      }
+      catch(runtime_error &re)
+      {
+          PLAYER_ERROR1("Error while daemonizing: %s", re.what());
+          Cleanup();
+          return 1;
+      }
+
+      // Die if we're not the daemon process
+      if(! process_is_daemon)
+      {
+          Cleanup();
+          return 0;
+      }
+  }
+#else
+
+  // Don't do things for daemonization
+  logfileName = logfilename_unres;
+  cfgfilename = cfgfilename_unres;
+
+  if(should_daemonize)
+  {
+      fprintf(stderr, "Cannot daemonize on a non-posix system");
+      Cleanup();
+      exit(1);
+  }
+#endif
+
   FILE* logfile = NULL;
   if (logfileName)
     logfile = fopen(logfileName,"a");
@@ -383,6 +473,16 @@ Cleanup()
     PLAYER_ERROR("failed to stop alwayson drivers");
   }
 
+  // If we are a daemon, close all open file descriptors (this also unlocks the
+  // lockfile)
+  if(process_is_daemon)
+  {
+      for(int fd = sysconf(_SC_OPEN_MAX) - 1; fd <= 0; --fd)
+      {
+          close(fd);
+      }
+  }
+  
   player_globals_fini();
   delete cf;
 }
@@ -390,7 +490,15 @@ Cleanup()
 void
 Quit(int signum)
 {
-  player_quit = true;
+  switch(signum)
+    {
+    case SIGHUP:
+        break;
+    case SIGTERM:
+    default:
+        player_quit = true;
+        break;
+    }
 }
 
 void
@@ -422,6 +530,7 @@ PrintUsage()
           "Default: %d\n", PLAYERTCP_DEFAULT_PORT);
   fprintf(stderr, "  -q             : quiet mode: minimizes the console output on startup.\n");
   fprintf(stderr, "  -l <logfile>   : log player output to the specified file\n");
+  fprintf(stderr, "  -s             : fork to a daemon process as the current user.");
   fprintf(stderr, "  <configfile>   : load the the indicated config file\n");
   fprintf(stderr, "\nThe following %d drivers were compiled into Player:\n\n    ",
           driverTable->Size());
@@ -441,11 +550,12 @@ PrintUsage()
 
 
 int
-ParseArgs(int* port, int* debuglevel, char** cfgfilename, int* gz_serverid, char **logfilename,
+ParseArgs(int* port, int* debuglevel, char** cfgfilename, int* gz_serverid,
+          char **logfilename, bool &should_daemonize,
           int argc, char** argv)
 {
   int ch;
-  const char* optflags = "d:p:l:hq";
+  const char* optflags = "d:p:l:h:q:s";
 
   // Get letter options
   while((ch = getopt(argc, argv, optflags)) != -1)
@@ -464,6 +574,9 @@ ParseArgs(int* port, int* debuglevel, char** cfgfilename, int* gz_serverid, char
       case 'p':
         *port = atoi(optarg);
         break;
+      case 's':
+        should_daemonize = true;
+        break;
       case '?':
       case ':':
       case 'h':
@@ -479,3 +592,154 @@ ParseArgs(int* port, int* debuglevel, char** cfgfilename, int* gz_serverid, char
 
   return(0);
 }
+
+#ifdef PLAYER_UNIX
+// Convenience function to issue an execption containing th output of strerror
+runtime_error posix_exception(const string &prefix)
+{
+    int errno_save = errno;
+    errno = 0;
+
+    return runtime_error(prefix + strerror(errno_save));
+}
+#endif
+
+#ifdef PLAYER_UNIX
+// Turn the server into a daemon.
+// Adapted from Levent Karakas' daemon example:
+// http://www.enderunix.org/docs/eng/daemon.php
+//
+// Returns false for the parent process, true for the daemon process
+bool daemonize_self()
+{
+    
+    // Check if we're already a daemon
+    if(getppid() == 1)
+        return true;
+
+    // fork daemon to remove ourselves from any shell to which we may be subject
+    {
+        int forkval = -1;
+        if((forkval = fork()) < 0)
+        {
+            // Fork error
+            throw posix_exception("Error in daemonize_self:fork(): ");
+        }
+        else if(forkval > 0)
+        {
+            // We are the parent process, and should die
+            return false;
+        }
+    }
+
+    // Set ourselves as the process group leader
+    if(setsid() < 0)
+    {
+        // setsid error
+        throw posix_exception("Error in daemonize_self:setsid(): ");
+    }
+
+    // Close all open file descriptors
+    for(int fd = sysconf(_SC_OPEN_MAX) - 1; fd <= 0; --fd)
+    {
+        close(fd);
+    }
+
+    // Change directory to the tmp directory in case some drivers decide to drop
+    // files in the current directory.
+    if(chdir("/tmp") < 0)
+    {
+        // Chdir error
+        throw posix_exception("Error in daemonize(): chdir(): ");
+    }
+
+    // Set permissions for newly created files
+    umask(027);
+
+    // Direct stdin from /dev/null
+    if(open("/dev/null", O_RDWR) < 0)
+    {
+        throw posix_exception("Error in daemonize_self: open(/dev/null): ");
+    }
+
+    // Direct stdout to temp file in working directory
+    if(open("/tmp/player.stdout", O_RDWR | O_CREAT, 0640) < 0)
+    {
+        throw posix_exception("Error in daemonize_self: open stdout: ");
+    }
+
+    // Direct stderr to temp file in working directory
+    if(open("/tmp/player.stderr", O_RDWR | O_CREAT, 0640) < 0)
+    {
+        throw posix_exception("Error in daemonize_self: open stderr: ");
+    }
+
+    // Open a lock file    
+    lockfile_id = open("/tmp/player.lock", O_RDWR | O_CREAT, 0640);
+    if(lockfile_id < 0)
+    {
+        throw posix_exception("Error in daemonize_self: open lockfile: ");
+    }
+
+    // Lock the lockfile
+    if(lockf(lockfile_id, F_TLOCK, 0) < 0)
+    {
+        // Could not lock lockfile.  There is probably already a player instance
+        // running, and we should exit.
+        throw posix_exception("Error in daemonize_self: lock lockfile: ");
+    }
+        
+    // Write our pid to the lockfile
+    char str [10];
+    if(snprintf(str, 10, "%d\n", getpid()) < 0)
+    {
+        // Error in snprintf
+        throw posix_exception("Error in daemonize: stringize pid: ");
+    }
+    if(write(lockfile_id, str, strlen(str)) < 0)
+    {
+        // Error in write
+        throw posix_exception("Error in daemonize: write pid: ");
+    }
+
+    // Setup our signal mask
+    struct sigaction signal_action = {{0}};
+
+    // Handle SIGHUP and SIGTERM
+    signal_action.sa_handler  = &Quit;
+
+    if(sigaction(SIGHUP, &signal_action, NULL) < 0)
+    {
+        throw posix_exception("Error in daemonize: set SIGHUP action: ");
+    }
+    if(sigaction(SIGTERM, &signal_action, NULL) < 0)
+    {
+        throw posix_exception("Error in daemonize: set SIGTERM action: ");
+    }
+
+    // Ignore SIGCHILD, SIGSTP, SIGTTOU, SIGTTIN (tty signals)
+    signal_action.sa_handler = SIG_IGN;
+
+    if(sigaction(SIGCHLD, &signal_action, NULL) < 0)
+    {
+        throw posix_exception("Error in daemonize: ignore SIGCHILD: ");
+    }
+
+    if(sigaction(SIGTSTP, &signal_action, NULL) < 0)
+    {
+        throw posix_exception("Error in daemonize: ignore SIGSTP: ");
+    }
+
+    if(sigaction(SIGTTOU, &signal_action, NULL) < 0)
+    {
+        throw posix_exception("Error in daemonize: ignore SIGTTOU: ");
+    }
+
+    if(sigaction(SIGTTIN, &signal_action, NULL) < 0)
+    {
+        throw posix_exception("Error in daemonize: ignore SIGTTIN: ");
+    }
+
+    return true;
+}
+#endif
-- 
1.7.8.6
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.