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 const 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 usage(void)
141 Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
142 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
143 \t\[-I|--ignore-spawned-command-output]\n\
144 \t{-p|--port} <server-port> <start-server-command>\n\
145 or %s {-0|--stop} [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
146 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
147 \t\[-I|--ignore-spawned-command-output]\n\
148 \t<stop-server-command>\n\
150 Error messages are printed to stderr by default,\n\
151 -S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
152 the errors by both methods.\n\
153 -I|--ignore-spawned-command-output will no longer warn of any stdout/stderr\n\
154 output of <*-server-command>s but it will no longer stuck if they held their\n\
155 output descriptors open.\n\
156 \n",program_name,program_name);
160 static const struct option longopts[]={
163 {"start-command-timeout" ,1,0,'T'},
164 {"idle-server-timeout" ,1,0,'i'},
169 {"ignore-spawned-command-output",0,0,'I'},
174 static int lock_fd=-1;
176 static int sighandler_flock_timeout_hit;
177 static void sighandler_flock_timeout(int signo)
179 sighandler_flock_timeout_hit=1;
182 static int lock_create(int lock_mode)
185 sighandler_t sighandler_alrm_orig;
190 /* Never drop the lock if the lock is already being held. */
195 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
196 fatal("Error creating lock file \"%s\": %m",opt_lock);
198 sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
199 alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
200 flock_rc=flock(lock_fd,lock_mode);
202 signal(SIGALRM,sighandler_alrm_orig);
203 if (sighandler_flock_timeout_hit)
204 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
206 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
208 fatal("Error locking lock file \"%s\": %m",opt_lock);
210 if (access(opt_lock,R_OK|W_OK)) {
212 fatal("Racing for the lock file \"%s\", giving up",opt_lock);
214 fatal("Error closing lock file \"%s\": %m",opt_lock);
221 static void lock_touch(void)
223 if (!opt_lock || lock_fd==-1)
225 if (utime(opt_lock,NULL))
226 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
229 static void lock_close(void)
234 fatal("Error closing lock file \"%s\": %m",opt_lock);
238 static int connect_try(void)
241 struct sockaddr_in sockaddr_tcp;
243 if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
244 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
245 memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
246 sockaddr_tcp.sin_family=AF_INET;
247 sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
248 sockaddr_tcp.sin_port=htons(opt_port);
249 if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
250 if (errno==ECONNREFUSED)
252 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
257 static void session_transfer(
258 const char *conn0_name,int conn0_fdin,int conn0_fdout,
259 const char *conn1_name,int conn1_fdin,int conn1_fdout)
261 static void session_transfer(
262 const char *conn0_name,int conn0_fdin,int conn0_fdout,
263 const char *conn1_name,int conn1_fdin,int conn1_fdout)
265 struct pollfd pollfdi[2];
267 const char *pollfdi_name[2];
270 pollfdi[0].fd=conn0_fdin;
271 pollfdi[0].events=POLLIN;
272 pollfdi[1].fd=conn1_fdin;
273 pollfdi[1].events=POLLIN;
274 pollfdo[0]=conn0_fdout;
275 pollfdo[1]=conn1_fdout;
276 pollfdi_name[0]=conn0_name;
277 pollfdi_name[1]=conn1_name;
279 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
280 if (pollfdi[fdi].events)
282 if (fdi>=G_N_ELEMENTS(pollfdi)) {
286 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
287 fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
288 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
290 || pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
291 || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
293 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
294 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
295 (unsigned)pollfdi[fdi].events);
296 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
299 char buf[SESSION_BUFFER_SIZE];
301 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
302 fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
303 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
307 fatal("read(%s socket): %m",pollfdi_name[fdi]);
310 pollfdi[fdi].events&=~POLLIN;
313 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
316 if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
317 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
319 if (got!=write(pollfdi[fdo].fd,buf,got))
320 fatal("write(%s socket,%ld): %m",
321 pollfdi_name[fdo],(long)got);
328 static void session_try(void)
332 if (-1==(fdtcp=connect_try()))
334 session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
337 static int popen_pclose_checked(const char *command)
340 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
343 if (!(f=popen(command,"r")))
344 fatal("Error opening spawned command \"%s\": %m",command);
346 while ((got=fread(buf,1,sizeof(buf)-1,f))) {
349 assert(got<sizeof(buf));
351 for (s=buf;*s;s=s_next) {
352 if ((s_next=strchr(s,'\n'))) {
354 error("Error line of spawned \"%s\": %s",command,s);
358 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
363 fatal("Error reading output of spawned \"%s\"",command);
365 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
369 static void system_checked(const char *command)
373 if (!opt_ignore_spawned_command_output)
374 rc=popen_pclose_checked(opt_stderr
375 ? command /* differentiate ourself-dumped stdout from stderr */
376 : asprintf_checked("(%s) 2>&1",command)
380 ? asprintf_checked("(%s) >&2",command)
381 : asprintf_checked("(%s) &>/dev/null",command)
383 if (WIFEXITED(rc) && !WEXITSTATUS(rc))
386 fatal("Error spawning command \"%s\": return code %d",
387 command,WEXITSTATUS(rc));
389 if (WIFSIGNALED(rc) && WCOREDUMP(rc))
390 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
391 command,WTERMSIG(rc));
392 #endif /* WCOREDUMP */
394 fatal("Error spawning command \"%s\": terminating signal %d",
395 command,WTERMSIG(rc));
397 fatal("Error spawning command \"%s\": stopping signal %d",
398 command,WSTOPSIG(rc));
400 if (WIFCONTINUED(rc))
401 fatal("Error spawning command \"%s\": resumed by SIGCONT",
403 #endif /* WIFCONTINUED */
404 fatal("Error spawning command \"%s\": unknown reason",
408 static void start(void) G_GNUC_NORETURN;
409 static void start(void)
414 fatal("-p|--port is a required argument for -1|--start");
415 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
416 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
418 lock_create(LOCK_SH);
423 lock_create(LOCK_EX);
424 system_checked(opt_command);
425 lock_create(LOCK_SH);
427 for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
430 if (poll(NULL,0,CONNECT_RETRY_MSEC))
431 fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
433 fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
434 opt_start_command_timeout,opt_port,opt_command);
437 /* Returns: Is fresh? */
438 static int lock_create_and_time_check(int lock_mode)
445 if (!lock_create(lock_mode|LOCK_NB))
447 if (lock_fd==-1 || fstat(lock_fd,&statbuf))
448 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
449 return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
452 static void lock_delete_and_close(void)
454 if (opt_lock && lock_fd!=-1)
455 if (unlink(opt_lock))
456 fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
460 static void stop(void) G_GNUC_NORETURN;
461 static void stop(void)
465 /* Lock still being held! */
467 fatal("-p|--port is a forbidden argument for -0|--stop");
468 if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
469 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
470 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
471 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
473 is_fresh=lock_create_and_time_check(LOCK_SH);
478 lock_create_and_time_check(LOCK_EX);
479 system_checked(opt_command);
480 lock_delete_and_close();
485 int main(int argc,char **argv) G_GNUC_NORETURN;
486 int main(int argc,char **argv)
490 if ((program_name=strrchr(argv[0],'/')))
493 program_name=argv[0];
495 optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
496 while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
500 case '1': /* -1|--start */
504 case '0': /* -0|--stop */
508 case 'T': /* -T|--start-command-timeout */
509 l=strtol(optarg,&endptr,0);
510 if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
511 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
512 opt_start_command_timeout=l;
515 case 'i': /* -i|--idle-server-timeout */
516 l=strtol(optarg,&endptr,0);
517 if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
518 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
519 opt_idle_server_timeout=l;
522 case 'S': /* -S|--syslog */
526 case 'e': /* -e|--stderr */
530 case 'l': /* -l|--lock */
534 case 'I': /* -I|--ignore-spawned-command-output */
535 opt_ignore_spawned_command_output=1;
538 case 'p': /* -p|--port */
539 l=strtol(optarg,&endptr,0);
540 if (l<=0 || l>=0x10000 || (endptr && *endptr))
541 fatal("Invalid -p|--port value: %s",optarg);
547 fatal("Error parsing commandline");
552 if (!opt_start && !opt_stop)
553 fatal("At least one of -1|--start or -0|--stop is required");
554 if ( opt_start && opt_stop)
555 fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
558 fatal("<start-server-command/stop-server-command> is a required argument");
560 fatal("Too many arguments, <start-server-command/stop-server-command> may need quoting");
561 opt_command=argv[optind];
563 if (!opt_syslog && !opt_stderr)