X-Git-Url: https://git.jankratochvil.net/?p=inetdmx.git;a=blobdiff_plain;f=inetdmx.c;h=ebab3bf2b0a34bff752249f9b2b3fb23214d8fd7;hp=4029438cbef54c175131c596ef3b7f3576cfb392;hb=009beb6c39a09220131eed12a9d79a83e140e245;hpb=f692db7f13fda5aefb49fcde0caa34f96118e44a diff --git a/inetdmx.c b/inetdmx.c index 4029438..ebab3bf 100644 --- a/inetdmx.c +++ b/inetdmx.c @@ -18,15 +18,19 @@ #include #include #include +#include +#include +#include +#include #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 +#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 */ @@ -59,55 +63,178 @@ 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 int opt_ignore_spawned_command_output; +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,...) + +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 (!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 usage(void) { fprintf(stderr,"\ -Syntax: %s [{-T|--start-timeout} ]\n\ - {-p|--port} \n\ +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 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\ +-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-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'}, + {"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; +} + +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 +279,10 @@ int fdi,fdo; for (fdi=0;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&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!", + 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; + + 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"); -static void lock_create(void) + 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; } -static void lock_close(void) +static void lock_delete_and_close(void) { - if (lock_fd==-1) - return; - if (close(lock_fd)) - fatal("Error closing lock file \"%s\": %m",opt_lock); - lock_fd=-1; + 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 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],'/'))) @@ -281,15 +493,30 @@ char optc; program_name=argv[0]; 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; - 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 */ @@ -302,6 +529,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); @@ -312,28 +544,28 @@ 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|--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!"); + fatal(" is a required argument"); if (optind+1 needs quoting?"); - command=argv[optind]; + fatal("Too many arguments, may need quoting"); + opt_command=argv[optind]; - lock_create(); - session_try(); - system_checked(command); + if (!opt_syslog && !opt_stderr) + opt_stderr=1; - for (retry=0;retry*CONNECT_RETRY_MSEC/1000