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 static void verror(const char *fmt,va_list ap) G_GNUC_PRINTF(1,0);
81 static void verror(const char *fmt,va_list ap)
83 int use_syslog=opt_syslog,use_stderr=opt_stderr;
85 const char *const double_error="Error printing error message";
87 if (!use_syslog && !use_stderr)
89 if (-1==vasprintf(&string,fmt,ap)) {
90 if (fmt==double_error)
95 fprintf(stderr,"%s: %s\n",program_name,string);
97 openlog(program_name,LOG_PID,LOG_DAEMON);
98 syslog(LOG_DAEMON|LOG_ERR,"%s",string);
103 static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
104 static void error(const char *fmt,...)
113 static void fatal(const char *fmt,...)
123 static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
124 static char *asprintf_checked(const char *fmt,...)
131 rc=vasprintf(&string,fmt,ap);
134 fatal("Error formatting string using formatstring: %s",fmt);
138 static void *xmalloc(size_t size)
142 if ((r=malloc(size)))
144 fatal("Error allocing %lu bytes",(unsigned long)size);
147 static void usage(void)
150 Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
151 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
152 \t\[-I|--ignore-spawned-command-output]\n\
153 \t{-p|--port} <server-port> <start-server-command>\n\
154 or %s {-0|--stop} [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
155 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
156 \t\[-I|--ignore-spawned-command-output]\n\
157 \t<stop-server-command>\n\
159 Error messages are printed to stderr by default,\n\
160 -S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
161 the errors by both methods.\n\
162 -I|--ignore-spawned-command-output will no longer warn of any stdout/stderr\n\
163 output of <*-server-command>s but it will no longer stuck if they held their\n\
164 output descriptors open.\n\
165 \n",program_name,program_name);
169 static const struct option longopts[]={
172 {"start-command-timeout" ,1,0,'T'},
173 {"idle-server-timeout" ,1,0,'i'},
178 {"ignore-spawned-command-output",0,0,'I'},
183 static int lock_fd=-1;
185 static int sighandler_flock_timeout_hit;
186 static void sighandler_flock_timeout(int signo)
188 sighandler_flock_timeout_hit=1;
191 static int lock_create(int lock_mode)
194 sighandler_t sighandler_alrm_orig;
199 /* Never drop the lock if the lock is already being held. */
204 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
205 fatal("Error creating lock file \"%s\": %m",opt_lock);
207 sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
208 alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
209 flock_rc=flock(lock_fd,lock_mode);
211 signal(SIGALRM,sighandler_alrm_orig);
212 if (sighandler_flock_timeout_hit)
213 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
215 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
217 fatal("Error locking lock file \"%s\": %m",opt_lock);
219 if (access(opt_lock,R_OK|W_OK)) {
221 fatal("Racing for the lock file \"%s\", giving up",opt_lock);
223 fatal("Error closing lock file \"%s\": %m",opt_lock);
230 static void lock_touch(void)
232 if (!opt_lock || lock_fd==-1)
234 if (utime(opt_lock,NULL))
235 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
238 static void lock_close(void)
243 fatal("Error closing lock file \"%s\": %m",opt_lock);
247 static int connect_try(void)
250 struct sockaddr_in sockaddr_tcp;
252 if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
253 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
254 memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
255 sockaddr_tcp.sin_family=AF_INET;
256 sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
257 sockaddr_tcp.sin_port=htons(opt_port);
258 if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
259 if (errno==ECONNREFUSED)
261 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
266 static void session_transfer(
267 const char *conn0_name,int conn0_fdin,int conn0_fdout,
268 const char *conn1_name,int conn1_fdin,int conn1_fdout)
270 static void session_transfer(
271 const char *conn0_name,int conn0_fdin,int conn0_fdout,
272 const char *conn1_name,int conn1_fdin,int conn1_fdout)
274 struct pollfd pollfdi[2];
276 const char *pollfdi_name[2];
279 pollfdi[0].fd=conn0_fdin;
280 pollfdi[0].events=POLLIN;
281 pollfdi[1].fd=conn1_fdin;
282 pollfdi[1].events=POLLIN;
283 pollfdo[0]=conn0_fdout;
284 pollfdo[1]=conn1_fdout;
285 pollfdi_name[0]=conn0_name;
286 pollfdi_name[1]=conn1_name;
288 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
289 fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
290 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
292 || pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
293 || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
295 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
296 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
297 (unsigned)pollfdi[fdi].events);
298 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
301 char buf[SESSION_BUFFER_SIZE];
303 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
304 fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
305 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
309 fatal("read(%s socket): %m",pollfdi_name[fdi]);
315 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
318 if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
319 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
321 if (got!=write(pollfdi[fdo].fd,buf,got))
322 fatal("write(%s socket,%ld): %m",
323 pollfdi_name[fdo],(long)got);
330 static void session_try(void)
334 if (-1==(fdtcp=connect_try()))
336 session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
339 static int popen_pclose_checked(const char *command)
342 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
345 if (!(f=popen(command,"r")))
346 fatal("Error opening spawned command \"%s\": %m",command);
348 while ((got=fread(buf,1,sizeof(buf)-1,f))) {
351 assert(got<sizeof(buf));
353 for (s=buf;*s;s=s_next) {
354 if ((s_next=strchr(s,'\n'))) {
356 error("Error line of spawned \"%s\": %s",command,s);
360 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
365 fatal("Error reading output of spawned \"%s\"",command);
367 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
371 static void system_checked(const char *command)
375 if (!opt_ignore_spawned_command_output)
376 rc=popen_pclose_checked(opt_stderr
377 ? command /* differentiate ourself-dumped stdout from stderr */
378 : asprintf_checked("(%s) 2>&1",command)
382 ? asprintf_checked("(%s) >&2",command)
383 : asprintf_checked("(%s) &>/dev/null",command)
385 if (WIFEXITED(rc) && !WEXITSTATUS(rc))
388 fatal("Error spawning command \"%s\": return code %d",
389 command,WEXITSTATUS(rc));
391 if (WIFSIGNALED(rc) && WCOREDUMP(rc))
392 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
393 command,WTERMSIG(rc));
394 #endif /* WCOREDUMP */
396 fatal("Error spawning command \"%s\": terminating signal %d",
397 command,WTERMSIG(rc));
399 fatal("Error spawning command \"%s\": stopping signal %d",
400 command,WSTOPSIG(rc));
402 if (WIFCONTINUED(rc))
403 fatal("Error spawning command \"%s\": resumed by SIGCONT",
405 #endif /* WIFCONTINUED */
406 fatal("Error spawning command \"%s\": unknown reason",
410 static void start(void) G_GNUC_NORETURN;
411 static void start(void)
416 fatal("-p|--port is a required argument for -1|--start");
417 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
418 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
420 lock_create(LOCK_SH);
425 lock_create(LOCK_EX);
426 system_checked(opt_command);
427 lock_create(LOCK_SH);
429 for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
432 if (poll(NULL,0,CONNECT_RETRY_MSEC))
433 fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
435 fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
436 opt_start_command_timeout,opt_port,opt_command);
439 /* Returns: Is fresh? */
440 static int lock_create_and_time_check(int lock_mode)
447 if (!lock_create(lock_mode|LOCK_NB))
449 if (lock_fd==-1 || fstat(lock_fd,&statbuf))
450 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
451 return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
454 static void lock_delete_and_close(void)
456 if (opt_lock && lock_fd!=-1)
457 if (unlink(opt_lock))
458 fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
462 static void stop(void) G_GNUC_NORETURN;
463 static void stop(void)
467 /* Lock still being held! */
469 fatal("-p|--port is a forbidden argument for -0|--stop");
470 if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
471 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
472 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
473 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
475 is_fresh=lock_create_and_time_check(LOCK_SH);
480 lock_create_and_time_check(LOCK_EX);
481 system_checked(opt_command);
482 lock_delete_and_close();
487 int main(int argc,char **argv) G_GNUC_NORETURN;
488 int main(int argc,char **argv)
491 size_t opt_command_len;
495 if ((program_name=strrchr(argv[0],'/')))
498 program_name=argv[0];
500 optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
501 while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
505 case '1': /* -1|--start */
509 case '0': /* -0|--stop */
513 case 'T': /* -T|--start-command-timeout */
514 l=strtol(optarg,&endptr,0);
515 if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
516 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
517 opt_start_command_timeout=l;
520 case 'i': /* -i|--idle-server-timeout */
521 l=strtol(optarg,&endptr,0);
522 if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
523 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
524 opt_idle_server_timeout=l;
527 case 'S': /* -S|--syslog */
531 case 'e': /* -e|--stderr */
535 case 'l': /* -l|--lock */
539 case 'I': /* -I|--ignore-spawned-command-output */
540 opt_ignore_spawned_command_output=1;
543 case 'p': /* -p|--port */
544 l=strtol(optarg,&endptr,0);
545 if (l<=0 || l>=0x10000 || (endptr && *endptr))
546 fatal("Invalid -p|--port value: %s",optarg);
552 fatal("Error parsing commandline");
557 if (!opt_start && !opt_stop)
558 fatal("At least one of -1|--start or -0|--stop is required");
559 if ( opt_start && opt_stop)
560 fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
563 fatal("<start-server-command/stop-server-command> is a required argument");
565 for (i=optind;i<argc;i++)
566 opt_command_len+=strlen(argv[i])+1;
567 opt_command=xmalloc(opt_command_len);
569 for (i=optind;i<argc;i++) {
570 size_t argv_i_len=strlen(argv[i]);
574 memcpy(s,argv[i],argv_i_len);
578 assert(s==opt_command+opt_command_len);
580 if (!opt_syslog && !opt_stderr)