4 * http://cvs.jankratochvil.net/viewcvs/nethome/src/inetdmx.c?rev=HEAD
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; you must use version 2 of the License.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <sys/types.h>
25 #include <sys/socket.h>
32 #include <netinet/in.h>
44 #define CONNECT_RETRY_MSEC 100
45 #define DEFAULT_START_COMMAND_TIMEOUT 60
46 #define DEFAULT_IDLE_SERVER_TIMEOUT (90*60)
47 #define FLOCK_TIMEOUT_OVER_START_TIMEOUT 2
48 #define SESSION_BUFFER_SIZE 0x1000
49 #define SYSTEM_CHECKED_BUFFER_SIZE_MIN 0x1000
50 #define SYSTEM_CHECKED_BUFFER_SIZE (LINE_MAX > SYSTEM_CHECKED_BUFFER_SIZE_MIN ? LINE_MAX : SYSTEM_CHECKED_BUFFER_SIZE_MIN)
53 /* /usr/include/glib-2.0/glib/gmacros.h */
55 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
56 #define G_GNUC_PRINTF( format_idx, arg_idx ) \
57 __attribute__((__format__ (__printf__, format_idx, arg_idx)))
59 #define G_GNUC_PRINTF( format_idx, arg_idx )
60 #endif /* !__GNUC__ */
61 #endif /* !G_GNUC_PRINTF */
63 /* /usr/include/glib-2.0/glib/gmacros.h */
64 #ifndef G_GNUC_NORETURN
65 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
66 #define G_GNUC_NORETURN \
67 __attribute__((__noreturn__))
69 #define G_GNUC_NORETURN
70 #endif /* !__GNUC__ */
71 #endif /* !G_GNUC_NORETURN */
73 /* /usr/include/glib-2.0/glib/gmacros.h */
74 /* Count the number of elements in an array. The array must be defined
75 * as such; using this with a dynamically allocated array will give
78 #define G_N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0]))
81 static const char *program_name;
85 static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
86 static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
88 static int opt_syslog;
89 static int opt_stderr;
90 static const char *opt_lock;
91 static int opt_ignore_spawned_command_output;
92 static char *opt_command;
95 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
97 /* for atexit(3) function */
98 static int verror_quiet;
100 static void verror(const char *fmt,va_list ap) G_GNUC_PRINTF(1,0);
101 static void verror(const char *fmt,va_list ap)
103 int use_syslog=opt_syslog,use_stderr=opt_stderr;
105 const char *const double_error="Error printing error message";
109 if (!use_syslog && !use_stderr)
111 if (-1==vasprintf(&string,fmt,ap)) {
112 if (fmt==double_error)
117 fprintf(stderr,"%s: %s\n",program_name,string);
119 openlog(program_name,LOG_PID,LOG_DAEMON);
120 syslog(LOG_DAEMON|LOG_ERR,"%s",string);
125 static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
126 static void error(const char *fmt,...)
135 static void fatal(const char *fmt,...)
145 static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
146 static char *asprintf_checked(const char *fmt,...)
153 rc=vasprintf(&string,fmt,ap);
156 fatal("Error formatting string using formatstring: %s",fmt);
160 static void *xmalloc(size_t size)
164 if ((r=malloc(size)))
166 fatal("Error allocing %lu bytes",(unsigned long)size);
169 static void usage(void)
172 Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
173 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
174 \t[-I|--ignore-spawned-command-output]\n\
175 \t{-p|--port} <server-port> <start-server-command>\n\
176 or %s {-0|--stop} [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
177 \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
178 \t[-I|--ignore-spawned-command-output]\n\
179 \t<stop-server-command>\n\
181 Error messages are printed to stderr by default,\n\
182 -S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
183 the errors by both methods.\n\
184 -I|--ignore-spawned-command-output will no longer warn of any stdout/stderr\n\
185 output of <*-server-command>s but it will no longer stuck if they held their\n\
186 output descriptors open.\n\
187 \n",program_name,program_name);
191 static const struct option longopts[]={
194 {"start-command-timeout" ,1,0,'T'},
195 {"idle-server-timeout" ,1,0,'i'},
200 {"ignore-spawned-command-output",0,0,'I'},
205 static int lock_fd=-1;
207 static int sighandler_flock_timeout_hit;
208 static void sighandler_flock_timeout(int signo)
210 sighandler_flock_timeout_hit=1;
213 enum lock_create_rc {
214 LOCK_CREATE_NO_LOCK_FILENAME,
215 LOCK_CREATE_FILE_NOT_FOUND, /* only if: lock_mode&LOCK_NB */
216 LOCK_CREATE_ALREADY_LOCKED, /* only if: lock_mode&LOCK_NB */
217 LOCK_CREATE_MYSELF_LOCKED, /* only for: lock_create() */
218 LOCK_CREATE_MYSELF_LOCKED_AND_FRESH, /* only for: lock_open_and_time_check() */
219 LOCK_CREATE_MYSELF_LOCKED_AND_STALE, /* only for: lock_open_and_time_check() */
221 /* It will never create the lock file if: lock_mode&LOCK_NB */
222 static enum lock_create_rc lock_create(int lock_mode)
225 sighandler_t sighandler_alrm_orig;
229 return LOCK_CREATE_NO_LOCK_FILENAME;
230 /* Never drop the lock if the lock is already being held. */
235 if (-1==(lock_fd=open(opt_lock,
236 O_RDWR | (lock_mode&LOCK_NB ? 0 : O_CREAT),
238 if (errno==ENOENT && lock_mode&LOCK_NB)
239 return LOCK_CREATE_FILE_NOT_FOUND;
240 fatal("Error creating lock file \"%s\": %m",opt_lock);
243 sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
244 alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
245 flock_rc=flock(lock_fd,lock_mode);
247 signal(SIGALRM,sighandler_alrm_orig);
248 if (sighandler_flock_timeout_hit)
249 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
251 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
252 return LOCK_CREATE_ALREADY_LOCKED;
253 fatal("Error locking lock file \"%s\": %m",opt_lock);
255 if (access(opt_lock,R_OK|W_OK)) {
257 fatal("Racing for the lock file \"%s\", giving up",opt_lock);
259 fatal("Error closing lock file \"%s\": %m",opt_lock);
263 return LOCK_CREATE_MYSELF_LOCKED;
266 static void lock_touch(void)
268 if (!opt_lock || lock_fd==-1)
270 if (utime(opt_lock,NULL))
271 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
274 static void lock_close(void)
276 if (lock_fd==-1 || !opt_lock)
278 /* It should not be needed but some stale locks were seen on:
279 * White Box Linux kernel-smp-2.6.9-5.0.5.EL
281 if (flock(lock_fd,LOCK_UN|LOCK_NB))
282 fatal("Error unlocking lock file \"%s\": %m",opt_lock);
284 fatal("Error closing lock file \"%s\": %m",opt_lock);
288 static void lock_close_atexit(void)
290 /* Prevent some crashes of malloc(3) etc. */
295 static int connect_try(void)
298 struct sockaddr_in sockaddr_tcp;
300 if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
301 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
302 memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
303 sockaddr_tcp.sin_family=AF_INET;
304 sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
305 sockaddr_tcp.sin_port=htons(opt_port);
306 if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
307 if (errno==ECONNREFUSED)
309 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
314 static void session_transfer(
315 const char *conn0_name,int conn0_fdin,int conn0_fdout,
316 const char *conn1_name,int conn1_fdin,int conn1_fdout)
318 static void session_transfer(
319 const char *conn0_name,int conn0_fdin,int conn0_fdout,
320 const char *conn1_name,int conn1_fdin,int conn1_fdout)
322 struct pollfd pollfdi[2];
324 const char *pollfdi_name[2];
327 pollfdi[0].fd=conn0_fdin;
328 pollfdi[0].events=POLLIN;
329 pollfdi[1].fd=conn1_fdin;
330 pollfdi[1].events=POLLIN;
331 pollfdo[0]=conn0_fdout;
332 pollfdo[1]=conn1_fdout;
333 pollfdi_name[0]=conn0_name;
334 pollfdi_name[1]=conn1_name;
336 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
337 fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
338 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
340 || pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
341 || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
343 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
344 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
345 (unsigned)pollfdi[fdi].events);
346 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
349 char buf[SESSION_BUFFER_SIZE];
351 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
352 fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
353 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
357 fatal("read(%s socket): %m",pollfdi_name[fdi]);
363 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
366 if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
367 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
369 if (got!=write(pollfdi[fdo].fd,buf,got))
370 fatal("write(%s socket,%ld): %m",
371 pollfdi_name[fdo],(long)got);
378 static void session_try(void)
382 if (-1==(fdtcp=connect_try()))
384 session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
387 static int popen_pclose_checked(const char *command)
390 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
393 if (!(f=popen(command,"r")))
394 fatal("Error opening spawned command \"%s\": %m",command);
396 while ((got=fread(buf,1,sizeof(buf)-1,f))) {
399 assert(got<sizeof(buf));
401 for (s=buf;*s;s=s_next) {
402 if ((s_next=strchr(s,'\n'))) {
404 error("Error line of spawned \"%s\": %s",command,s);
408 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
413 fatal("Error reading output of spawned \"%s\"",command);
415 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
419 static void system_checked(const char *command)
423 if (!opt_ignore_spawned_command_output)
424 rc=popen_pclose_checked(opt_stderr
425 ? command /* differentiate ourself-dumped stdout from stderr */
426 : asprintf_checked("(%s) 2>&1",command)
430 ? asprintf_checked("(%s) >&2",command)
431 : asprintf_checked("(%s) &>/dev/null",command)
433 if (WIFEXITED(rc) && !WEXITSTATUS(rc))
436 fatal("Error spawning command \"%s\": return code %d",
437 command,WEXITSTATUS(rc));
439 if (WIFSIGNALED(rc) && WCOREDUMP(rc))
440 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
441 command,WTERMSIG(rc));
442 #endif /* WCOREDUMP */
444 fatal("Error spawning command \"%s\": terminating signal %d",
445 command,WTERMSIG(rc));
447 fatal("Error spawning command \"%s\": stopping signal %d",
448 command,WSTOPSIG(rc));
450 if (WIFCONTINUED(rc))
451 fatal("Error spawning command \"%s\": resumed by SIGCONT",
453 #endif /* WIFCONTINUED */
454 fatal("Error spawning command \"%s\": unknown reason",
458 static void start(void) G_GNUC_NORETURN;
459 static void start(void)
464 fatal("-p|--port is a required argument for -1|--start");
465 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
466 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
468 lock_create(LOCK_SH);
473 lock_create(LOCK_EX);
474 system_checked(opt_command);
475 lock_create(LOCK_SH);
477 for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
480 if (poll(NULL,0,CONNECT_RETRY_MSEC))
481 fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
483 fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
484 opt_start_command_timeout,opt_port,opt_command);
487 static enum lock_create_rc lock_open_and_time_check(int lock_mode)
489 enum lock_create_rc rc;
492 if (LOCK_CREATE_MYSELF_LOCKED!=(rc=lock_create(lock_mode|LOCK_NB)))
494 if (lock_fd==-1 || fstat(lock_fd,&statbuf))
495 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
496 return (statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout
497 ? LOCK_CREATE_MYSELF_LOCKED_AND_FRESH : LOCK_CREATE_MYSELF_LOCKED_AND_STALE);
500 static void lock_delete_and_close(void)
502 if (opt_lock && lock_fd!=-1)
503 if (unlink(opt_lock))
504 fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
508 static void lock_open_and_return_if_stale(int lock_mode)
510 switch (lock_open_and_time_check(lock_mode)) {
511 case LOCK_CREATE_NO_LOCK_FILENAME: return;
512 case LOCK_CREATE_FILE_NOT_FOUND: exit(EXIT_SUCCESS);
513 case LOCK_CREATE_ALREADY_LOCKED: exit(EXIT_SUCCESS);
514 case LOCK_CREATE_MYSELF_LOCKED: assert(0);
515 case LOCK_CREATE_MYSELF_LOCKED_AND_FRESH: exit(EXIT_SUCCESS);
516 case LOCK_CREATE_MYSELF_LOCKED_AND_STALE: return;
521 static void stop(void) G_GNUC_NORETURN;
522 static void stop(void)
525 /* Lock still being held! */
527 fatal("-p|--port is a forbidden argument for -0|--stop");
528 if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
529 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
530 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
531 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
533 lock_open_and_return_if_stale(LOCK_SH);
536 lock_open_and_return_if_stale(LOCK_EX);
537 system_checked(opt_command);
538 lock_delete_and_close();
543 int main(int argc,char **argv) G_GNUC_NORETURN;
544 int main(int argc,char **argv)
547 size_t opt_command_len;
551 if ((program_name=strrchr(argv[0],'/')))
554 program_name=argv[0];
556 atexit(lock_close_atexit);
558 optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
559 while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
563 case '1': /* -1|--start */
567 case '0': /* -0|--stop */
571 case 'T': /* -T|--start-command-timeout */
572 l=strtol(optarg,&endptr,0);
573 if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
574 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
575 opt_start_command_timeout=l;
578 case 'i': /* -i|--idle-server-timeout */
579 l=strtol(optarg,&endptr,0);
580 if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
581 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
582 opt_idle_server_timeout=l;
585 case 'S': /* -S|--syslog */
589 case 'e': /* -e|--stderr */
593 case 'l': /* -l|--lock */
597 case 'I': /* -I|--ignore-spawned-command-output */
598 opt_ignore_spawned_command_output=1;
601 case 'p': /* -p|--port */
602 l=strtol(optarg,&endptr,0);
603 if (l<=0 || l>=0x10000 || (endptr && *endptr))
604 fatal("Invalid -p|--port value: %s",optarg);
610 fatal("Error parsing commandline");
615 if (!opt_start && !opt_stop)
616 fatal("At least one of -1|--start or -0|--stop is required");
617 if ( opt_start && opt_stop)
618 fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
621 fatal("<start-server-command/stop-server-command> is a required argument");
623 for (i=optind;i<argc;i++)
624 opt_command_len+=strlen(argv[i])+1;
625 opt_command=xmalloc(opt_command_len);
627 for (i=optind;i<argc;i++) {
628 size_t argv_i_len=strlen(argv[i]);
632 memcpy(s,argv[i],argv_i_len);
636 assert(s==opt_command+opt_command_len);
638 if (!opt_syslog && !opt_stderr)