8 #include <sys/socket.h>
15 #include <netinet/in.h>
27 #define CONNECT_RETRY_MSEC 100
28 #define DEFAULT_START_COMMAND_TIMEOUT 60
29 #define DEFAULT_IDLE_SERVER_TIMEOUT (90*60)
30 #define FLOCK_TIMEOUT_OVER_START_TIMEOUT 2
31 #define SESSION_BUFFER_SIZE 0x1000
32 #define SYSTEM_CHECKED_BUFFER_SIZE_MIN 0x1000
33 #define SYSTEM_CHECKED_BUFFER_SIZE (LINE_MAX > SYSTEM_CHECKED_BUFFER_SIZE_MIN ? LINE_MAX : SYSTEM_CHECKED_BUFFER_SIZE_MIN)
36 /* /usr/include/glib-2.0/glib/gmacros.h */
38 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
39 #define G_GNUC_PRINTF( format_idx, arg_idx ) \
40 __attribute__((__format__ (__printf__, format_idx, arg_idx)))
42 #define G_GNUC_PRINTF( format_idx, arg_idx )
43 #endif /* !__GNUC__ */
44 #endif /* !G_GNUC_PRINTF */
46 /* /usr/include/glib-2.0/glib/gmacros.h */
47 #ifndef G_GNUC_NORETURN
48 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
49 #define G_GNUC_NORETURN \
50 __attribute__((__noreturn__))
52 #define G_GNUC_NORETURN
53 #endif /* !__GNUC__ */
54 #endif /* !G_GNUC_NORETURN */
56 /* /usr/include/glib-2.0/glib/gmacros.h */
57 /* Count the number of elements in an array. The array must be defined
58 * as such; using this with a dynamically allocated array will give
61 #define G_N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0]))
64 static const char *program_name;
68 static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
69 static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
71 static int opt_syslog;
72 static int opt_stderr;
73 static const char *opt_lock;
74 static int opt_ignore_spawned_command_output;
75 static char *opt_command;
78 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
80 /* for atexit(3) function */
81 static int verror_quiet;
83 static void verror(const char *fmt,va_list ap) G_GNUC_PRINTF(1,0);
84 static void verror(const char *fmt,va_list ap)
86 int use_syslog=opt_syslog,use_stderr=opt_stderr;
88 const char *const double_error="Error printing error message";
92 if (!use_syslog && !use_stderr)
94 if (-1==vasprintf(&string,fmt,ap)) {
95 if (fmt==double_error)
100 fprintf(stderr,"%s: %s\n",program_name,string);
102 openlog(program_name,LOG_PID,LOG_DAEMON);
103 syslog(LOG_DAEMON|LOG_ERR,"%s",string);
108 static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
109 static void error(const char *fmt,...)
118 static void fatal(const char *fmt,...)
128 static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
129 static char *asprintf_checked(const char *fmt,...)
136 rc=vasprintf(&string,fmt,ap);
139 fatal("Error formatting string using formatstring: %s",fmt);
143 static void *xmalloc(size_t size)
147 if ((r=malloc(size)))
149 fatal("Error allocing %lu bytes",(unsigned long)size);
152 static void usage(void)
155 Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
156 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
157 \t\[-I|--ignore-spawned-command-output]\n\
158 \t{-p|--port} <server-port> <start-server-command>\n\
159 or %s {-0|--stop} [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
160 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
161 \t\[-I|--ignore-spawned-command-output]\n\
162 \t<stop-server-command>\n\
164 Error messages are printed to stderr by default,\n\
165 -S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
166 the errors by both methods.\n\
167 -I|--ignore-spawned-command-output will no longer warn of any stdout/stderr\n\
168 output of <*-server-command>s but it will no longer stuck if they held their\n\
169 output descriptors open.\n\
170 \n",program_name,program_name);
174 static const struct option longopts[]={
177 {"start-command-timeout" ,1,0,'T'},
178 {"idle-server-timeout" ,1,0,'i'},
183 {"ignore-spawned-command-output",0,0,'I'},
188 static int lock_fd=-1;
190 static int sighandler_flock_timeout_hit;
191 static void sighandler_flock_timeout(int signo)
193 sighandler_flock_timeout_hit=1;
196 static int lock_create(int lock_mode)
199 sighandler_t sighandler_alrm_orig;
204 /* Never drop the lock if the lock is already being held. */
209 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
210 fatal("Error creating lock file \"%s\": %m",opt_lock);
212 sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
213 alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
214 flock_rc=flock(lock_fd,lock_mode);
216 signal(SIGALRM,sighandler_alrm_orig);
217 if (sighandler_flock_timeout_hit)
218 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
220 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
222 fatal("Error locking lock file \"%s\": %m",opt_lock);
224 if (access(opt_lock,R_OK|W_OK)) {
226 fatal("Racing for the lock file \"%s\", giving up",opt_lock);
228 fatal("Error closing lock file \"%s\": %m",opt_lock);
235 static void lock_touch(void)
237 if (!opt_lock || lock_fd==-1)
239 if (utime(opt_lock,NULL))
240 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
243 static void lock_close(void)
245 if (lock_fd==-1 || !opt_lock)
247 /* It should not be needed but some stale locks were seen on:
248 * White Box Linux kernel-smp-2.6.9-5.0.5.EL
250 if (flock(lock_fd,LOCK_UN|LOCK_NB))
251 fatal("Error unlocking lock file \"%s\": %m",opt_lock);
253 fatal("Error closing lock file \"%s\": %m",opt_lock);
257 static void lock_close_atexit(void)
259 /* Prevent some crashes of malloc(3) etc. */
264 static int connect_try(void)
267 struct sockaddr_in sockaddr_tcp;
269 if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
270 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
271 memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
272 sockaddr_tcp.sin_family=AF_INET;
273 sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
274 sockaddr_tcp.sin_port=htons(opt_port);
275 if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
276 if (errno==ECONNREFUSED)
278 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
283 static void session_transfer(
284 const char *conn0_name,int conn0_fdin,int conn0_fdout,
285 const char *conn1_name,int conn1_fdin,int conn1_fdout)
287 static void session_transfer(
288 const char *conn0_name,int conn0_fdin,int conn0_fdout,
289 const char *conn1_name,int conn1_fdin,int conn1_fdout)
291 struct pollfd pollfdi[2];
293 const char *pollfdi_name[2];
296 pollfdi[0].fd=conn0_fdin;
297 pollfdi[0].events=POLLIN;
298 pollfdi[1].fd=conn1_fdin;
299 pollfdi[1].events=POLLIN;
300 pollfdo[0]=conn0_fdout;
301 pollfdo[1]=conn1_fdout;
302 pollfdi_name[0]=conn0_name;
303 pollfdi_name[1]=conn1_name;
305 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
306 fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
307 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
309 || pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
310 || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
312 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
313 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
314 (unsigned)pollfdi[fdi].events);
315 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
318 char buf[SESSION_BUFFER_SIZE];
320 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
321 fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
322 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
326 fatal("read(%s socket): %m",pollfdi_name[fdi]);
332 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
335 if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
336 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
338 if (got!=write(pollfdi[fdo].fd,buf,got))
339 fatal("write(%s socket,%ld): %m",
340 pollfdi_name[fdo],(long)got);
347 static void session_try(void)
351 if (-1==(fdtcp=connect_try()))
353 session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
356 static int popen_pclose_checked(const char *command)
359 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
362 if (!(f=popen(command,"r")))
363 fatal("Error opening spawned command \"%s\": %m",command);
365 while ((got=fread(buf,1,sizeof(buf)-1,f))) {
368 assert(got<sizeof(buf));
370 for (s=buf;*s;s=s_next) {
371 if ((s_next=strchr(s,'\n'))) {
373 error("Error line of spawned \"%s\": %s",command,s);
377 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
382 fatal("Error reading output of spawned \"%s\"",command);
384 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
388 static void system_checked(const char *command)
392 if (!opt_ignore_spawned_command_output)
393 rc=popen_pclose_checked(opt_stderr
394 ? command /* differentiate ourself-dumped stdout from stderr */
395 : asprintf_checked("(%s) 2>&1",command)
399 ? asprintf_checked("(%s) >&2",command)
400 : asprintf_checked("(%s) &>/dev/null",command)
402 if (WIFEXITED(rc) && !WEXITSTATUS(rc))
405 fatal("Error spawning command \"%s\": return code %d",
406 command,WEXITSTATUS(rc));
408 if (WIFSIGNALED(rc) && WCOREDUMP(rc))
409 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
410 command,WTERMSIG(rc));
411 #endif /* WCOREDUMP */
413 fatal("Error spawning command \"%s\": terminating signal %d",
414 command,WTERMSIG(rc));
416 fatal("Error spawning command \"%s\": stopping signal %d",
417 command,WSTOPSIG(rc));
419 if (WIFCONTINUED(rc))
420 fatal("Error spawning command \"%s\": resumed by SIGCONT",
422 #endif /* WIFCONTINUED */
423 fatal("Error spawning command \"%s\": unknown reason",
427 static void start(void) G_GNUC_NORETURN;
428 static void start(void)
433 fatal("-p|--port is a required argument for -1|--start");
434 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
435 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
437 lock_create(LOCK_SH);
442 lock_create(LOCK_EX);
443 system_checked(opt_command);
444 lock_create(LOCK_SH);
446 for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
449 if (poll(NULL,0,CONNECT_RETRY_MSEC))
450 fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
452 fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
453 opt_start_command_timeout,opt_port,opt_command);
456 /* Returns: Is fresh? */
457 static int lock_create_and_time_check(int lock_mode)
464 if (!lock_create(lock_mode|LOCK_NB))
466 if (lock_fd==-1 || fstat(lock_fd,&statbuf))
467 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
468 return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
471 static void lock_delete_and_close(void)
473 if (opt_lock && lock_fd!=-1)
474 if (unlink(opt_lock))
475 fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
479 static void stop(void) G_GNUC_NORETURN;
480 static void stop(void)
484 /* Lock still being held! */
486 fatal("-p|--port is a forbidden argument for -0|--stop");
487 if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
488 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
489 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
490 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
492 is_fresh=lock_create_and_time_check(LOCK_SH);
497 lock_create_and_time_check(LOCK_EX);
498 system_checked(opt_command);
499 lock_delete_and_close();
504 int main(int argc,char **argv) G_GNUC_NORETURN;
505 int main(int argc,char **argv)
508 size_t opt_command_len;
512 if ((program_name=strrchr(argv[0],'/')))
515 program_name=argv[0];
517 atexit(lock_close_atexit);
519 optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
520 while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
524 case '1': /* -1|--start */
528 case '0': /* -0|--stop */
532 case 'T': /* -T|--start-command-timeout */
533 l=strtol(optarg,&endptr,0);
534 if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
535 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
536 opt_start_command_timeout=l;
539 case 'i': /* -i|--idle-server-timeout */
540 l=strtol(optarg,&endptr,0);
541 if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
542 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
543 opt_idle_server_timeout=l;
546 case 'S': /* -S|--syslog */
550 case 'e': /* -e|--stderr */
554 case 'l': /* -l|--lock */
558 case 'I': /* -I|--ignore-spawned-command-output */
559 opt_ignore_spawned_command_output=1;
562 case 'p': /* -p|--port */
563 l=strtol(optarg,&endptr,0);
564 if (l<=0 || l>=0x10000 || (endptr && *endptr))
565 fatal("Invalid -p|--port value: %s",optarg);
571 fatal("Error parsing commandline");
576 if (!opt_start && !opt_stop)
577 fatal("At least one of -1|--start or -0|--stop is required");
578 if ( opt_start && opt_stop)
579 fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
582 fatal("<start-server-command/stop-server-command> is a required argument");
584 for (i=optind;i<argc;i++)
585 opt_command_len+=strlen(argv[i])+1;
586 opt_command=xmalloc(opt_command_len);
588 for (i=optind;i<argc;i++) {
589 size_t argv_i_len=strlen(argv[i]);
593 memcpy(s,argv[i],argv_i_len);
597 assert(s==opt_command+opt_command_len);
599 if (!opt_syslog && !opt_stderr)