First final but buggy code.
[inetdmx.git] / inetdmx.c
index 4029438..d1e49c1 100644 (file)
--- a/inetdmx.c
+++ b/inetdmx.c
 #include <sys/wait.h>
 #include <stdarg.h>
 #include <syslog.h>
+#include <sys/file.h>
+#include <utime.h>
+#include <time.h>
+#include <assert.h>
 
 
 #define CONNECT_RETRY_MSEC 100
-
-static long opt_start_timeout=60;
-static int opt_port;
-static int opt_syslog;
-static int opt_stderr;
-static const char *opt_lock;
+#define DEFAULT_START_COMMAND_TIMEOUT 60
+#define DEFAULT_IDLE_SERVER_TIMEOUT (90*60)
+#define FLOCK_TIMEOUT_OVER_START_TIMEOUT 2
+#define SESSION_BUFFER_SIZE 0x1000
 
 
 /* /usr/include/glib-2.0/glib/gmacros.h */
@@ -59,6 +61,16 @@ static const char *opt_lock;
 
 static const char *program_name;
 
+static int opt_start;
+static int opt_stop;
+static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
+static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
+static int opt_port;
+static int opt_syslog;
+static int opt_stderr;
+static const char *opt_lock;
+static const char *opt_command;
+
 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
 static void fatal(const char *fmt,...)
 {
@@ -89,25 +101,96 @@ const char *const error_error="Error printing error message";
 static void usage(void)
 {
        fprintf(stderr,"\
-Syntax: %s [{-T|--start-timeout} <start-timeout] [-S|--syslog] [-e|--stderr]\n\
-        [{-l|--lock} <filename>]\n\
-        {-p|--port} <server-port> <start-server-command>\n\
+Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
+        \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
+        \t{-p|--port} <server-port> <start-server-command>\n\
+     or %s {-0|--stop}  [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
+        \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
+        \t<stop-server-command>\n\
 \n\
-Error messages out to stderr by default, -S|--syslog omits stderr output,\n\
-both -S|--syslog and -e|--stderr output the errors by both methods.\n\
-\n",program_name);
+Error messages are printed to stderr by default,\n\
+-S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
+the errors by both methods.\n\
+\n",program_name,program_name);
        exit(EXIT_FAILURE);
 }
 
 static const struct option longopts[]={
-       {"start-timeout",1,0,'T'},
-       {"syslog"       ,0,0,'S'},
-       {"stderr"       ,0,0,'e'},
-       {"lock"         ,1,0,'l'},
-       {"port"         ,1,0,'p'},
-       {NULL           ,0,0,0  },
+       {"start"                ,0,0,'1'},
+       {"stop"                 ,0,0,'0'},
+       {"start-command-timeout",1,0,'T'},
+       {"idle-server-timeout"  ,1,0,'i'},
+       {"syslog"               ,0,0,'S'},
+       {"stderr"               ,0,0,'e'},
+       {"lock"                 ,1,0,'l'},
+       {"port"                 ,1,0,'p'},
+       {NULL                   ,0,0,0  },
        };
 
+static int lock_fd=-1;
+
+static int sighandler_flock_timeout_hit;
+static void sighandler_flock_timeout(int signo)
+{
+       sighandler_flock_timeout_hit=1;
+}
+
+static int lock_create(int lock_mode)
+{
+int retries=3;
+sighandler_t sighandler_alrm_orig;
+int flock_rc;
+
+       if (!opt_lock)
+               return 1;
+       /* Never drop the lock if the lock is already being held. */
+       if (lock_fd!=-1)
+               retries=-1;
+retry:
+       if (lock_fd==-1) {
+               if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
+                       fatal("Error creating lock file \"%s\": %m",opt_lock);
+               }
+       sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
+       alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
+       flock_rc=flock(lock_fd,lock_mode);
+       alarm(0);
+       signal(SIGALRM,sighandler_alrm_orig);
+       if (sighandler_flock_timeout_hit)
+               fatal("Timeout locking lock file \"%s\": %m",opt_lock);
+       if (flock_rc) {
+               if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
+                       return 0;
+               fatal("Error locking lock file \"%s\": %m",opt_lock);
+               }
+       if (!access(opt_lock,R_OK|W_OK)) {
+               if (retries--<=0)
+                       fatal("Racing for the lock file \"%s\", giving up",opt_lock);
+               if (close(lock_fd))
+                       fatal("Error closing lock file \"%s\": %m",opt_lock);
+               lock_fd=-1;
+               goto retry;
+               }
+       return 1;
+}
+
+static void lock_touch(void)
+{
+       if (!opt_lock || lock_fd==-1)
+               return;
+       if (utime(opt_lock,NULL))
+               fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
+}
+
+static void lock_close(void)
+{
+       if (lock_fd==-1)
+               return;
+       if (close(lock_fd))
+               fatal("Error closing lock file \"%s\": %m",opt_lock);
+       lock_fd=-1;
+}
+
 static int connect_try(void)
 {
 int fdtcp;
@@ -152,8 +235,10 @@ int fdi,fdo;
                for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
                        if (pollfdi[fdi].events)
                                break;
-               if (fdi>=G_N_ELEMENTS(pollfdi))
+               if (fdi>=G_N_ELEMENTS(pollfdi)) {
+                       lock_close();
                        exit(EXIT_SUCCESS);
+                       }
                if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
                        fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
                for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
@@ -167,7 +252,7 @@ int fdi,fdo;
                for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
                        for (;;) {
                                ssize_t got;
-                               char buf[0x1000];
+                               char buf[SESSION_BUFFER_SIZE];
 
                                if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
                                        fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
@@ -213,66 +298,110 @@ int rc;
        if (WIFEXITED(rc) && !WEXITSTATUS(rc))
                return;
        if (WIFEXITED(rc))
-               fatal("Error spawning command \"%s\": return code %d!",
+               fatal("Error spawning command \"%s\": return code %d",
                                command,WEXITSTATUS(rc));
 #ifdef WCOREDUMP
        if (WIFSIGNALED(rc) && WCOREDUMP(rc))
-               fatal("Error spawning command \"%s\": dumped core (terminating signal %d)!",
+               fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
                                command,WTERMSIG(rc));
 #endif /* WCOREDUMP */
        if (WIFSIGNALED(rc))
-               fatal("Error spawning command \"%s\": terminating signal %d!",
+               fatal("Error spawning command \"%s\": terminating signal %d",
                                command,WTERMSIG(rc));
        if (WIFSTOPPED(rc))
-               fatal("Error spawning command \"%s\": stopping signal %d!",
+               fatal("Error spawning command \"%s\": stopping signal %d",
                                command,WSTOPSIG(rc));
 #ifdef WIFCONTINUED
        if (WIFCONTINUED(rc))
-               fatal("Error spawning command \"%s\": resumed by SIGCONT!",
+               fatal("Error spawning command \"%s\": resumed by SIGCONT",
                                command);
 #endif /* WIFCONTINUED */
-       fatal("Error spawning command \"%s\": unknown reason!",
+       fatal("Error spawning command \"%s\": unknown reason",
                        command);
 }
 
-static int lock_fd=-1;
+static void start(void) G_GNUC_NORETURN;
+static void start(void)
+{
+int retry;
 
-static void lock_create(void)
+       if (!opt_port)
+               fatal("-p|--port is a required argument for -1|--start");
+       if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
+               fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
+
+       lock_create(LOCK_SH);
+       lock_touch();
+       session_try();
+       lock_close();
+
+       lock_create(LOCK_EX);
+       system_checked(opt_command);
+       lock_create(LOCK_SH);
+
+       for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
+               lock_touch();
+               session_try();
+               if (poll(NULL,0,CONNECT_RETRY_MSEC))
+                       fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
+               }
+       fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
+                       opt_start_command_timeout,opt_port,opt_command);
+}
+
+/* Returns: Is fresh? */
+static int lock_create_and_time_check(int lock_mode)
 {
-int retries=3;
+struct stat statbuf;
 
        if (!opt_lock)
-               return;
-retry:
-       if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
-               fatal("Error creating lock file \"%s\": %m",opt_lock);
-       if (flock(lock_fd,LOCK_EX))
-               fatal("Error locking lock file \"%s\": %m",opt_lock);
-       if (!access(opt_lock,O_RD|O_WR)) {
-               if (retries--<=0)
-                       fatal("Racing for the lock file \"%s\", giving up");
-               if (close(lock_fd))
-                       fatal("Error closing lock file \"%s\": %m",opt_lock);
-               lock_fd=-1;
-               goto retry;
-               }
-       if (utime(opt_lock,NULL))
-               fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
+               return 1;
+
+       if (!lock_create(lock_mode|LOCK_NB))
+               exit(EXIT_SUCCESS);
+       if (lock_fd==-1 || !fstat(lock_fd,&statbuf))
+               fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
+       return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
 }
 
-static void lock_close(void)
+static void lock_delete_and_close(void)
 {
-       if (lock_fd==-1)
+       /* Should not happen. */
+       if (!opt_lock || lock_fd==-1)
                return;
-       if (close(lock_fd))
-               fatal("Error closing lock file \"%s\": %m",opt_lock);
-       lock_fd=-1;
+       if (unlink(opt_lock))
+               fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
+       lock_close();
+}
+
+static void stop(void) G_GNUC_NORETURN;
+static void stop(void)
+{
+int is_fresh;
+
+       /* Lock still being held! */
+       if (opt_port)
+               fatal("-p|--port is a forbidden argument for -0|--stop");
+       if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
+               fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
+       if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
+               fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
+
+       is_fresh=lock_create_and_time_check(LOCK_SH);
+       lock_close();
+       if (is_fresh)
+               exit(EXIT_SUCCESS);
+
+       lock_create_and_time_check(LOCK_EX);
+       system_checked(opt_command);
+       lock_delete_and_close();
+
+       exit(EXIT_SUCCESS);
 }
 
+int main(int argc,char **argv) G_GNUC_NORETURN;
 int main(int argc,char **argv)
 {
-int retry;
-const char *command;
 char optc;
 
        if ((program_name=strrchr(argv[0],'/')))
@@ -285,11 +414,26 @@ char optc;
 long l;
 char *endptr;
 
-               case 'T':       /* -T|--start-timeout */
+               case '1':       /* -1|--start */
+                       opt_start=1;
+                       break;
+
+               case '0':       /* -0|--stop */
+                       opt_stop=1;
+                       break;
+
+               case 'T':       /* -T|--start-command-timeout */
+                       l=strtol(optarg,&endptr,0);
+                       if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
+                               fatal("Invalid -T|--start-command-timeout value: %s",optarg);
+                       opt_start_command_timeout=l;
+                       break;
+
+               case 'i':       /* -i|--idle-server-timeout */
                        l=strtol(optarg,&endptr,0);
                        if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
-                               fatal("Invalid -T|--start-timeout values: %s",optarg);
-                       opt_start_timeout=l;
+                               fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
+                       opt_idle_server_timeout=l;
                        break;
 
                case 'S':       /* -S|--syslog */
@@ -312,28 +456,25 @@ char *endptr;
 
                default:
                        if (optc!='h')
-                               fatal("Error parsing commandline!");
+                               fatal("Error parsing commandline");
                        usage();
                        break;
                }
 
-       if (!opt_port)
-               fatal("-p|--port is a required argument!");
+       if (!opt_start && !opt_stop)
+               fatal("At least one of -1|--opt-start or -0|--opt-stop is required");
+       if ( opt_start &&  opt_stop)
+               fatal("Both modes -1|--opt-start and -0|--opt-stop can never be specified simultaneously");
+
        if (optind>=argc)
-               fatal("<start-server-command> is a required argument!");
+               fatal("<start-server-command/stop-server-command> is a required argument");
        if (optind+1<argc)
-               fatal("Too many arguments, <start-server-command> needs quoting?");
-       command=argv[optind];
-
-       lock_create();
-       session_try();
-       system_checked(command);
-
-       for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_timeout;retry++) {
-               session_try();
-               if (poll(NULL,0,CONNECT_RETRY_MSEC))
-                       fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
-               }
-       fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
-                       opt_start_timeout,opt_port,command);
+               fatal("Too many arguments, <start-server-command/stop-server-command> may need quoting");
+       opt_command=argv[optind];
+
+       if (opt_start)
+               start();
+       if (opt_stop)
+               stop();
+       assert(0);
 }