/* * $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CONNECT_RETRY_MSEC 100 #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 #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 */ #ifndef G_GNUC_PRINTF #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) #define G_GNUC_PRINTF( format_idx, arg_idx ) \ __attribute__((__format__ (__printf__, format_idx, arg_idx))) #else /* !__GNUC__ */ #define G_GNUC_PRINTF( format_idx, arg_idx ) #endif /* !__GNUC__ */ #endif /* !G_GNUC_PRINTF */ /* /usr/include/glib-2.0/glib/gmacros.h */ #ifndef G_GNUC_NORETURN #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) #define G_GNUC_NORETURN \ __attribute__((__noreturn__)) #else /* !__GNUC__ */ #define G_GNUC_NORETURN #endif /* !__GNUC__ */ #endif /* !G_GNUC_NORETURN */ /* /usr/include/glib-2.0/glib/gmacros.h */ /* Count the number of elements in an array. The array must be defined * as such; using this with a dynamically allocated array will give * incorrect results. */ #define G_N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) 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 int opt_ignore_spawned_command_output; 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) { 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)) { if (fmt==double_error) exit(EXIT_FAILURE); fatal(double_error); } if (use_stderr) 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); 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'}, {"ignore-spawned-command-output",0,0,'I'}, {"help" ,0,0,'h'}, {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; } 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 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_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); 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 LOCK_CREATE_ALREADY_LOCKED; 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 LOCK_CREATE_MYSELF_LOCKED; } 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 || !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; struct sockaddr_in sockaddr_tcp; if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0))) fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp); memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp)); sockaddr_tcp.sin_family=AF_INET; sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK); sockaddr_tcp.sin_port=htons(opt_port); if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) { if (errno==ECONNREFUSED) return -1; fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port); } return fdtcp; } static void session_transfer( const char *conn0_name,int conn0_fdin,int conn0_fdout, const char *conn1_name,int conn1_fdin,int conn1_fdout) G_GNUC_NORETURN; static void session_transfer( const char *conn0_name,int conn0_fdin,int conn0_fdout, const char *conn1_name,int conn1_fdin,int conn1_fdout) { struct pollfd pollfdi[2]; int pollfdo[2]; const char *pollfdi_name[2]; int fdi,fdo; pollfdi[0].fd=conn0_fdin; pollfdi[0].events=POLLIN; pollfdi[1].fd=conn1_fdin; pollfdi[1].events=POLLIN; pollfdo[0]=conn0_fdout; pollfdo[1]=conn1_fdout; pollfdi_name[0]=conn0_name; pollfdi_name[1]=conn1_name; for (;;) { 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&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)) 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)", command,WTERMSIG(rc)); #endif /* WCOREDUMP */ if (WIFSIGNALED(rc)) fatal("Error spawning command \"%s\": terminating signal %d", command,WTERMSIG(rc)); if (WIFSTOPPED(rc)) fatal("Error spawning command \"%s\": stopping signal %d", command,WSTOPSIG(rc)); #ifdef WIFCONTINUED if (WIFCONTINUED(rc)) fatal("Error spawning command \"%s\": resumed by SIGCONT", command); #endif /* WIFCONTINUED */ fatal("Error spawning command \"%s\": unknown reason", command); } static void start(void) G_GNUC_NORETURN; static void start(void) { int retry; 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=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) { 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) { /* 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"); lock_open_and_return_if_stale(LOCK_SH); lock_close(); lock_open_and_return_if_stale(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) { 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; char *endptr; 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 -i|--idle-server-timeout value: %s",optarg); opt_idle_server_timeout=l; break; case 'S': /* -S|--syslog */ opt_syslog=1; break; case 'e': /* -e|--stderr */ opt_stderr=1; break; 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); if (l<=0 || l>=0x10000 || (endptr && *endptr)) fatal("Invalid -p|--port value: %s",optarg); opt_port=l; break; default: if (optc!='h') fatal("Error parsing commandline"); usage(); break; } if (!opt_start && !opt_stop) fatal("At least one of -1|--start or -0|--stop is required"); if ( opt_start && opt_stop) fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously"); if (optind>=argc) fatal(" is a required argument"); 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(); if (opt_stop) stop(); assert(0); }