X-Git-Url: https://git.jankratochvil.net/?p=inetdmx.git;a=blobdiff_plain;f=inetdmx.c;h=c6d961b4fb88a19f4e8eeb26cd817ba558af4a1e;hp=d1e49c1c375dd5b47dd55e3b56d054a7a19d3dc4;hb=8f730a373fdd5ffa92ebd25b556a6bf1240f6d59;hpb=c5c5ff166e1d46726458533f5238e4323b23a8df diff --git a/inetdmx.c b/inetdmx.c index d1e49c1..c6d961b 100644 --- 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} ]\n\ \t[{-l|--lock} ] [-S|--syslog] [-e|--stderr]\n\ + \t[-I|--ignore-spawned-command-output]\n\ \t{-p|--port} \n\ or %s {-0|--stop} [{-i|--idle-server-timeout} ]\n\ \t[{-l|--lock} ] [-S|--syslog] [-e|--stderr]\n\ + \t[-I|--ignore-spawned-command-output]\n\ \t\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; @@ -135,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); @@ -160,10 +249,10 @@ 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)) { + 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)) @@ -171,7 +260,7 @@ retry: lock_fd=-1; goto retry; } - return 1; + return LOCK_CREATE_MYSELF_LOCKED; } static void lock_touch(void) @@ -184,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; @@ -220,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; @@ -229,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)) { - 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&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,35 +484,43 @@ 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 1; - - if (!lock_create(lock_mode|LOCK_NB)) - exit(EXIT_SUCCESS); - if (lock_fd==-1 || !fstat(lock_fd,&statbuf)) + 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) { - /* 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(); } +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) @@ -387,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(); @@ -403,14 +544,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 +592,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 +613,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(" is a required argument"); - if (optind+1 may need quoting"); - opt_command=argv[optind]; + opt_command_len=0; + for (i=optind;iopt_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();