-/* $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
#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 */
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;
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);
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))
lock_fd=-1;
goto retry;
}
- return 1;
+ return LOCK_CREATE_MYSELF_LOCKED;
}
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;
{
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;
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 (;;) {
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);
}
}
}
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))
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)
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();
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;
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 (!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();