Fixed --stop if the lock file does not exist.
[inetdmx.git] / inetdmx.c
index d1e49c1..99077b9 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
@@ -29,6 +46,8 @@
 #define DEFAULT_IDLE_SERVER_TIMEOUT (90*60)
 #define FLOCK_TIMEOUT_OVER_START_TIMEOUT 2
 #define SESSION_BUFFER_SIZE 0x1000
+#define SYSTEM_CHECKED_BUFFER_SIZE_MIN 0x1000
+#define SYSTEM_CHECKED_BUFFER_SIZE (LINE_MAX > SYSTEM_CHECKED_BUFFER_SIZE_MIN ? LINE_MAX : SYSTEM_CHECKED_BUFFER_SIZE_MIN)
 
 
 /* /usr/include/glib-2.0/glib/gmacros.h */
@@ -69,62 +88,118 @@ static int opt_port;
 static int opt_syslog;
 static int opt_stderr;
 static const char *opt_lock;
-static const char *opt_command;
+static int opt_ignore_spawned_command_output;
+static char *opt_command;
+
 
 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
-static void fatal(const char *fmt,...)
+
+/* 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)
 {
 int use_syslog=opt_syslog,use_stderr=opt_stderr;
-va_list ap;
 char *string;
-const char *const error_error="Error printing error message";
+const char *const double_error="Error printing error message";
 
+       if (verror_quiet)
+               return;
        if (!use_syslog && !use_stderr)
                use_stderr=1;
-       va_start(ap,fmt);
        if (-1==vasprintf(&string,fmt,ap)) {
-               if (fmt==error_error)
+               if (fmt==double_error)
                        exit(EXIT_FAILURE);
-               fatal(error_error);
+               fatal(double_error);
                }
        if (use_stderr)
-               fprintf(stderr,"%s: %s!\n",program_name,string);
+               fprintf(stderr,"%s: %s\n",program_name,string);
        if (use_syslog) {
                openlog(program_name,LOG_PID,LOG_DAEMON);
-               syslog(LOG_DAEMON|LOG_ERR,"%s!",string);
+               syslog(LOG_DAEMON|LOG_ERR,"%s",string);
                closelog();
                }
+}
+
+static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
+static void error(const char *fmt,...)
+{
+va_list ap;
+
+       va_start(ap,fmt);
+       verror(fmt,ap);
+       va_end(ap);
+}
+
+static void fatal(const char *fmt,...)
+{
+va_list ap;
+
+       va_start(ap,fmt);
+       verror(fmt,ap);
        va_end(ap);
        exit(EXIT_FAILURE);
 }
 
+static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
+static char *asprintf_checked(const char *fmt,...)
+{
+char *string;
+va_list ap;
+int rc;
+
+       va_start(ap,fmt);
+       rc=vasprintf(&string,fmt,ap);
+       va_end(ap);
+       if (rc==-1)
+               fatal("Error formatting string using formatstring: %s",fmt);
+       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{-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<stop-server-command>\n\
 \n\
 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\
+-I|--ignore-spawned-command-output will no longer warn of any stdout/stderr\n\
+output of <*-server-command>s but it will no longer stuck if they held their\n\
+output descriptors open.\n\
 \n",program_name,program_name);
        exit(EXIT_FAILURE);
 }
 
 static const struct option longopts[]={
-       {"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  },
+       {"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'},
+       {"ignore-spawned-command-output",0,0,'I'},
+       {"help"                         ,0,0,'h'},
+       {NULL                           ,0,0,0  },
        };
 
 static int lock_fd=-1;
@@ -163,7 +238,7 @@ retry:
                        return 0;
                fatal("Error locking lock file \"%s\": %m",opt_lock);
                }
-       if (!access(opt_lock,R_OK|W_OK)) {
+       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))
@@ -184,13 +259,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;
@@ -232,13 +319,6 @@ int fdi,fdo;
        pollfdi_name[0]=conn0_name;
        pollfdi_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]);
                for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
@@ -263,8 +343,8 @@ int fdi,fdo;
                                        fatal("read(%s socket): %m",pollfdi_name[fdi]);
                                        }
                                if (got==0) {
-                                       pollfdi[fdi].events&=~POLLIN;
-                                       break;
+                                       lock_close();
+                                       exit(EXIT_SUCCESS);
                                        }
                                for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
                                        if (fdi==fdo)
@@ -290,11 +370,52 @@ int fdtcp;
        session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
 }
 
+static int popen_pclose_checked(const char *command)
+{
+FILE *f;
+char buf[SYSTEM_CHECKED_BUFFER_SIZE];
+size_t got;
+
+       if (!(f=popen(command,"r")))
+               fatal("Error opening spawned command \"%s\": %m",command);
+       setlinebuf(f);
+       while ((got=fread(buf,1,sizeof(buf)-1,f))) {
+char *s,*s_next;
+
+               assert(got<sizeof(buf));
+               buf[got]=0;
+               for (s=buf;*s;s=s_next) {
+                       if ((s_next=strchr(s,'\n'))) {
+                               *s_next++=0;
+                               error("Error line of spawned \"%s\": %s",command,s);
+                               }
+                       else {
+                               s_next=s+strlen(s);
+                               error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
+                               }
+                       }
+               }
+       if (ferror(f))
+               fatal("Error reading output of spawned \"%s\"",command);
+       if (!feof(f))
+               fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
+       return pclose(f);
+}
+
 static void system_checked(const char *command)
 {
 int rc;
 
-       rc=system(command);
+       if (!opt_ignore_spawned_command_output)
+               rc=popen_pclose_checked(opt_stderr
+                               ? command       /* differentiate ourself-dumped stdout from stderr */
+                               : asprintf_checked("(%s) 2>&1",command)
+                               );
+       else
+               rc=system(opt_stderr
+                               ? asprintf_checked("(%s) >&2",command)
+                               : asprintf_checked("(%s) &>/dev/null",command)
+                               );
        if (WIFEXITED(rc) && !WEXITSTATUS(rc))
                return;
        if (WIFEXITED(rc))
@@ -349,28 +470,31 @@ int retry;
                        opt_start_command_timeout,opt_port,opt_command);
 }
 
-/* Returns: Is fresh? */
-static int lock_create_and_time_check(int lock_mode)
+/* Returns: Is fresh or does not exist? */
+static int lock_open_and_time_check(int lock_mode)
 {
 struct stat statbuf;
 
        if (!opt_lock)
-               return 1;
+               return 0;
 
+       if (access(opt_lock,R_OK|W_OK)) {
+               if (errno==ENOENT)
+                       return 1;
+               fatal("Error checking existance of the lock file \"%s\": %m",opt_lock);
+               }
        if (!lock_create(lock_mode|LOCK_NB))
-               exit(EXIT_SUCCESS);
-       if (lock_fd==-1 || !fstat(lock_fd,&statbuf))
+               return 0;
+       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_delete_and_close(void)
 {
-       /* Should not happen. */
-       if (!opt_lock || lock_fd==-1)
-               return;
-       if (unlink(opt_lock))
-               fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
+       if (opt_lock && lock_fd!=-1)
+               if (unlink(opt_lock))
+                       fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
        lock_close();
 }
 
@@ -387,12 +511,12 @@ 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);
+       is_fresh=lock_open_and_time_check(LOCK_SH);
        lock_close();
        if (is_fresh)
                exit(EXIT_SUCCESS);
 
-       lock_create_and_time_check(LOCK_EX);
+       lock_open_and_time_check(LOCK_EX);
        system_checked(opt_command);
        lock_delete_and_close();
 
@@ -403,14 +527,19 @@ 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,"c:d:L:l:b:xCM:P:s:m:r:t:T:w:fvhV",longopts,NULL))!=EOF) switch (optc) {
+       while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
 long l;
 char *endptr;
 
@@ -446,6 +575,11 @@ char *endptr;
 
                case 'l':       /* -l|--lock */
                        opt_lock=optarg;
+                       break;
+
+               case 'I':       /* -I|--ignore-spawned-command-output */
+                       opt_ignore_spawned_command_output=1;
+                       break;
 
                case 'p':       /* -p|--port */
                        l=strtol(optarg,&endptr,0);
@@ -462,15 +596,30 @@ char *endptr;
                }
 
        if (!opt_start && !opt_stop)
-               fatal("At least one of -1|--opt-start or -0|--opt-stop is required");
+               fatal("At least one of -1|--start or -0|--stop is required");
        if ( opt_start &&  opt_stop)
-               fatal("Both modes -1|--opt-start and -0|--opt-stop can never be specified simultaneously");
+               fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
 
        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;
 
        if (opt_start)
                start();