Somehow 'packaged'.
[inetdmx.git] / inetdmx.c
index ebab3bf..c6d961b 100644 (file)
--- a/inetdmx.c
+++ b/inetdmx.c
@@ -1,4 +1,21 @@
-/* $Id$ */
+/*
+ * $Id$
+ * Latest:
+ *     http://cvs.jankratochvil.net/viewcvs/nethome/src/inetdmx.c?rev=HEAD
+ *
+ * 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; you must use version 2 of the License.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
 
 
 #define _GNU_SOURCE 1
@@ -72,11 +89,14 @@ static int opt_syslog;
 static int opt_stderr;
 static const char *opt_lock;
 static int opt_ignore_spawned_command_output;
-static const char *opt_command;
+static char *opt_command;
 
 
 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
 
+/* for atexit(3) function */
+static int verror_quiet;
+
 static void verror(const char *fmt,va_list ap) G_GNUC_PRINTF(1,0);
 static void verror(const char *fmt,va_list ap)
 {
@@ -84,6 +104,8 @@ int use_syslog=opt_syslog,use_stderr=opt_stderr;
 char *string;
 const char *const double_error="Error printing error message";
 
+       if (verror_quiet)
+               return;
        if (!use_syslog && !use_stderr)
                use_stderr=1;
        if (-1==vasprintf(&string,fmt,ap)) {
@@ -135,16 +157,25 @@ int rc;
        return string;
 }
 
+static void *xmalloc(size_t size)
+{
+void *r;
+
+       if ((r=malloc(size)))
+               return r;
+       fatal("Error allocing %lu bytes",(unsigned long)size);
+}
+
 static void usage(void)
 {
        fprintf(stderr,"\
 Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
         \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
-        \t\[-I|--ignore-spawned-command-output]\n\
+        \t[-I|--ignore-spawned-command-output]\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\[-I|--ignore-spawned-command-output]\n\
+        \t[-I|--ignore-spawned-command-output]\n\
         \t<stop-server-command>\n\
 \n\
 Error messages are printed to stderr by default,\n\
@@ -179,21 +210,35 @@ static void sighandler_flock_timeout(int signo)
        sighandler_flock_timeout_hit=1;
 }
 
-static int lock_create(int lock_mode)
+enum lock_create_rc {
+       LOCK_CREATE_NO_LOCK_FILENAME,
+       LOCK_CREATE_FILE_NOT_FOUND,             /* only if: lock_mode&LOCK_NB */
+       LOCK_CREATE_ALREADY_LOCKED,             /* only if: lock_mode&LOCK_NB */
+       LOCK_CREATE_MYSELF_LOCKED,              /* only for: lock_create() */
+       LOCK_CREATE_MYSELF_LOCKED_AND_FRESH,    /* only for: lock_open_and_time_check() */
+       LOCK_CREATE_MYSELF_LOCKED_AND_STALE,    /* only for: lock_open_and_time_check() */
+       };
+/* It will never create the lock file if: lock_mode&LOCK_NB */
+static enum lock_create_rc lock_create(int lock_mode)
 {
 int retries=3;
 sighandler_t sighandler_alrm_orig;
 int flock_rc;
 
        if (!opt_lock)
-               return 1;
+               return LOCK_CREATE_NO_LOCK_FILENAME;
        /* 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)))
+               if (-1==(lock_fd=open(opt_lock,
+                               O_RDWR | (lock_mode&LOCK_NB ? 0 : O_CREAT),
+                               0600))) {
+                       if (errno==ENOENT && lock_mode&LOCK_NB)
+                               return LOCK_CREATE_FILE_NOT_FOUND;
                        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);
@@ -204,7 +249,7 @@ retry:
                fatal("Timeout locking lock file \"%s\": %m",opt_lock);
        if (flock_rc) {
                if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
-                       return 0;
+                       return LOCK_CREATE_ALREADY_LOCKED;
                fatal("Error locking lock file \"%s\": %m",opt_lock);
                }
        if (access(opt_lock,R_OK|W_OK)) {
@@ -215,7 +260,7 @@ retry:
                lock_fd=-1;
                goto retry;
                }
-       return 1;
+       return LOCK_CREATE_MYSELF_LOCKED;
 }
 
 static void lock_touch(void)
@@ -228,13 +273,25 @@ static void lock_touch(void)
 
 static void lock_close(void)
 {
-       if (lock_fd==-1)
+       if (lock_fd==-1 || !opt_lock)
                return;
+       /* It should not be needed but some stale locks were seen on:
+        *      White Box Linux kernel-smp-2.6.9-5.0.5.EL
+        */
+       if (flock(lock_fd,LOCK_UN|LOCK_NB))
+               fatal("Error unlocking lock file \"%s\": %m",opt_lock);
        if (close(lock_fd))
                fatal("Error closing lock file \"%s\": %m",opt_lock);
        lock_fd=-1;
 }
 
+static void lock_close_atexit(void)
+{
+       /* Prevent some crashes of malloc(3) etc. */
+       verror_quiet=1;
+       lock_close();
+}
+
 static int connect_try(void)
 {
 int fdtcp;
@@ -264,7 +321,7 @@ static void session_transfer(
 {
 struct pollfd pollfdi[2];
 int pollfdo[2];
-const char *pollfdi_name[2];
+const char *pollfd_name[2];
 int fdi,fdo;
 
        pollfdi[0].fd=conn0_fdin;
@@ -273,25 +330,18 @@ int fdi,fdo;
        pollfdi[1].events=POLLIN;
        pollfdo[0]=conn0_fdout;
        pollfdo[1]=conn1_fdout;
-       pollfdi_name[0]=conn0_name;
-       pollfdi_name[1]=conn1_name;
+       pollfd_name[0]=conn0_name;
+       pollfd_name[1]=conn1_name;
        for (;;) {
-               for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
-                       if (pollfdi[fdi].events)
-                               break;
-               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]);
+                       fatal("poll(%s socket,%s socket): %m",pollfd_name[0],pollfd_name[1]);
                for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
                        if (0
                                        ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
                                        || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
                                        )
                                fatal("poll(%s socket): revents=0x%X (events=0x%X)",
-                                               pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
+                                               pollfd_name[fdi],(unsigned)pollfdi[fdi].revents,
                                                (unsigned)pollfdi[fdi].events);
                for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
                        for (;;) {
@@ -299,26 +349,26 @@ int fdi,fdo;
                                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]);
+                                       fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfd_name[fdi]);
                                got=read(pollfdi[fdi].fd,buf,sizeof(buf));
                                if (got<0) {
                                        if (errno==EAGAIN)
                                                break;
-                                       fatal("read(%s socket): %m",pollfdi_name[fdi]);
+                                       fatal("read(%s socket): %m",pollfd_name[fdi]);
                                        }
                                if (got==0) {
-                                       pollfdi[fdi].events&=~POLLIN;
-                                       break;
+                                       lock_close();
+                                       exit(EXIT_SUCCESS);
                                        }
-                               for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
+                               for (fdo=0;fdo<G_N_ELEMENTS(pollfdo);fdo++) {
                                        if (fdi==fdo)
                                                continue;
-                                       if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
+                                       if (fcntl(pollfdo[fdo],F_SETFL,0 /* !O_NONBLOCK */))
                                                fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
-                                                               pollfdi_name[fdo]);
-                                       if (got!=write(pollfdi[fdo].fd,buf,got))
+                                                               pollfd_name[fdo]);
+                                       if (got!=write(pollfdo[fdo],buf,got))
                                                fatal("write(%s socket,%ld): %m",
-                                                               pollfdi_name[fdo],(long)got);
+                                                               pollfd_name[fdo],(long)got);
                                        }
                                }
                        }
@@ -434,19 +484,17 @@ int retry;
                        opt_start_command_timeout,opt_port,opt_command);
 }
 
-/* Returns: Is fresh? */
-static int lock_create_and_time_check(int lock_mode)
+static enum lock_create_rc lock_open_and_time_check(int lock_mode)
 {
+enum lock_create_rc rc;
 struct stat statbuf;
 
-       if (!opt_lock)
-               return 0;
-
-       if (!lock_create(lock_mode|LOCK_NB))
-               exit(EXIT_SUCCESS);
+       if (LOCK_CREATE_MYSELF_LOCKED!=(rc=lock_create(lock_mode|LOCK_NB)))
+               return rc;
        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;
+       return (statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout
+                       ? LOCK_CREATE_MYSELF_LOCKED_AND_FRESH : LOCK_CREATE_MYSELF_LOCKED_AND_STALE);
 }
 
 static void lock_delete_and_close(void)
@@ -457,10 +505,22 @@ static void lock_delete_and_close(void)
        lock_close();
 }
 
+static void lock_open_and_return_if_stale(int lock_mode)
+{
+       switch (lock_open_and_time_check(lock_mode)) {
+               case LOCK_CREATE_NO_LOCK_FILENAME:        return;
+               case LOCK_CREATE_FILE_NOT_FOUND:          exit(EXIT_SUCCESS);
+               case LOCK_CREATE_ALREADY_LOCKED:          exit(EXIT_SUCCESS);
+               case LOCK_CREATE_MYSELF_LOCKED:           assert(0);
+               case LOCK_CREATE_MYSELF_LOCKED_AND_FRESH: exit(EXIT_SUCCESS);
+               case LOCK_CREATE_MYSELF_LOCKED_AND_STALE: return;
+               }
+       assert(0);
+}
+
 static void stop(void) G_GNUC_NORETURN;
 static void stop(void)
 {
-int is_fresh;
 
        /* Lock still being held! */
        if (opt_port)
@@ -470,12 +530,10 @@ int is_fresh;
        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_open_and_return_if_stale(LOCK_SH);
        lock_close();
-       if (is_fresh)
-               exit(EXIT_SUCCESS);
 
-       lock_create_and_time_check(LOCK_EX);
+       lock_open_and_return_if_stale(LOCK_EX);
        system_checked(opt_command);
        lock_delete_and_close();
 
@@ -486,12 +544,17 @@ int main(int argc,char **argv) G_GNUC_NORETURN;
 int main(int argc,char **argv)
 {
 char optc;
+size_t opt_command_len;
+int i;
+char *s;
 
        if ((program_name=strrchr(argv[0],'/')))
                program_name++;
        else
                program_name=argv[0];
 
+       atexit(lock_close_atexit);
+
        optarg=NULL; optind=0;  /* FIXME: Possible portability problem. */
        while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
 long l;
@@ -556,9 +619,21 @@ char *endptr;
 
        if (optind>=argc)
                fatal("<start-server-command/stop-server-command> is a required argument");
-       if (optind+1<argc)
-               fatal("Too many arguments, <start-server-command/stop-server-command> may need quoting");
-       opt_command=argv[optind];
+       opt_command_len=0;
+       for (i=optind;i<argc;i++)
+               opt_command_len+=strlen(argv[i])+1;
+       opt_command=xmalloc(opt_command_len);
+       s=opt_command;
+       for (i=optind;i<argc;i++) {
+size_t argv_i_len=strlen(argv[i]);
+
+               if (s>opt_command)
+                       *s++=' ';
+               memcpy(s,argv[i],argv_i_len);
+               s+=argv_i_len;
+               }
+       *s++=0;
+       assert(s==opt_command+opt_command_len);
 
        if (!opt_syslog && !opt_stderr)
                opt_stderr=1;