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 static int lock_create(int lock_mode)
216 sighandler_t sighandler_alrm_orig;
221 /* Never drop the lock if the lock is already being held. */
226 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
227 fatal("Error creating lock file \"%s\": %m",opt_lock);
229 sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
230 alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
231 flock_rc=flock(lock_fd,lock_mode);
233 signal(SIGALRM,sighandler_alrm_orig);
234 if (sighandler_flock_timeout_hit)
235 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
237 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
239 fatal("Error locking lock file \"%s\": %m",opt_lock);
241 if (access(opt_lock,R_OK|W_OK)) {
243 fatal("Racing for the lock file \"%s\", giving up",opt_lock);
245 fatal("Error closing lock file \"%s\": %m",opt_lock);
252 static void lock_touch(void)
254 if (!opt_lock || lock_fd==-1)
256 if (utime(opt_lock,NULL))
257 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
260 static void lock_close(void)
262 if (lock_fd==-1 || !opt_lock)
264 /* It should not be needed but some stale locks were seen on:
265 * White Box Linux kernel-smp-2.6.9-5.0.5.EL
267 if (flock(lock_fd,LOCK_UN|LOCK_NB))
268 fatal("Error unlocking lock file \"%s\": %m",opt_lock);
270 fatal("Error closing lock file \"%s\": %m",opt_lock);
274 static void lock_close_atexit(void)
276 /* Prevent some crashes of malloc(3) etc. */
281 static int connect_try(void)
284 struct sockaddr_in sockaddr_tcp;
286 if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
287 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
288 memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
289 sockaddr_tcp.sin_family=AF_INET;
290 sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
291 sockaddr_tcp.sin_port=htons(opt_port);
292 if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
293 if (errno==ECONNREFUSED)
295 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
300 static void session_transfer(
301 const char *conn0_name,int conn0_fdin,int conn0_fdout,
302 const char *conn1_name,int conn1_fdin,int conn1_fdout)
304 static void session_transfer(
305 const char *conn0_name,int conn0_fdin,int conn0_fdout,
306 const char *conn1_name,int conn1_fdin,int conn1_fdout)
308 struct pollfd pollfdi[2];
310 const char *pollfdi_name[2];
313 pollfdi[0].fd=conn0_fdin;
314 pollfdi[0].events=POLLIN;
315 pollfdi[1].fd=conn1_fdin;
316 pollfdi[1].events=POLLIN;
317 pollfdo[0]=conn0_fdout;
318 pollfdo[1]=conn1_fdout;
319 pollfdi_name[0]=conn0_name;
320 pollfdi_name[1]=conn1_name;
322 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
323 fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
324 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
326 || pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
327 || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
329 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
330 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
331 (unsigned)pollfdi[fdi].events);
332 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
335 char buf[SESSION_BUFFER_SIZE];
337 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
338 fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
339 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
343 fatal("read(%s socket): %m",pollfdi_name[fdi]);
349 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
352 if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
353 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
355 if (got!=write(pollfdi[fdo].fd,buf,got))
356 fatal("write(%s socket,%ld): %m",
357 pollfdi_name[fdo],(long)got);
364 static void session_try(void)
368 if (-1==(fdtcp=connect_try()))
370 session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
373 static int popen_pclose_checked(const char *command)
376 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
379 if (!(f=popen(command,"r")))
380 fatal("Error opening spawned command \"%s\": %m",command);
382 while ((got=fread(buf,1,sizeof(buf)-1,f))) {
385 assert(got<sizeof(buf));
387 for (s=buf;*s;s=s_next) {
388 if ((s_next=strchr(s,'\n'))) {
390 error("Error line of spawned \"%s\": %s",command,s);
394 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
399 fatal("Error reading output of spawned \"%s\"",command);
401 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
405 static void system_checked(const char *command)
409 if (!opt_ignore_spawned_command_output)
410 rc=popen_pclose_checked(opt_stderr
411 ? command /* differentiate ourself-dumped stdout from stderr */
412 : asprintf_checked("(%s) 2>&1",command)
416 ? asprintf_checked("(%s) >&2",command)
417 : asprintf_checked("(%s) &>/dev/null",command)
419 if (WIFEXITED(rc) && !WEXITSTATUS(rc))
422 fatal("Error spawning command \"%s\": return code %d",
423 command,WEXITSTATUS(rc));
425 if (WIFSIGNALED(rc) && WCOREDUMP(rc))
426 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
427 command,WTERMSIG(rc));
428 #endif /* WCOREDUMP */
430 fatal("Error spawning command \"%s\": terminating signal %d",
431 command,WTERMSIG(rc));
433 fatal("Error spawning command \"%s\": stopping signal %d",
434 command,WSTOPSIG(rc));
436 if (WIFCONTINUED(rc))
437 fatal("Error spawning command \"%s\": resumed by SIGCONT",
439 #endif /* WIFCONTINUED */
440 fatal("Error spawning command \"%s\": unknown reason",
444 static void start(void) G_GNUC_NORETURN;
445 static void start(void)
450 fatal("-p|--port is a required argument for -1|--start");
451 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
452 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
454 lock_create(LOCK_SH);
459 lock_create(LOCK_EX);
460 system_checked(opt_command);
461 lock_create(LOCK_SH);
463 for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
466 if (poll(NULL,0,CONNECT_RETRY_MSEC))
467 fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
469 fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
470 opt_start_command_timeout,opt_port,opt_command);
473 /* Returns: Is fresh or does not exist? */
474 static int lock_open_and_time_check(int lock_mode)
481 if (access(opt_lock,R_OK|W_OK)) {
484 fatal("Error checking existance of the lock file \"%s\": %m",opt_lock);
486 if (!lock_create(lock_mode|LOCK_NB))
488 if (lock_fd==-1 || fstat(lock_fd,&statbuf))
489 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
490 return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
493 static void lock_delete_and_close(void)
495 if (opt_lock && lock_fd!=-1)
496 if (unlink(opt_lock))
497 fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
501 static void stop(void) G_GNUC_NORETURN;
502 static void stop(void)
506 /* Lock still being held! */
508 fatal("-p|--port is a forbidden argument for -0|--stop");
509 if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
510 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
511 if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
512 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
514 is_fresh=lock_open_and_time_check(LOCK_SH);
519 lock_open_and_time_check(LOCK_EX);
520 system_checked(opt_command);
521 lock_delete_and_close();
526 int main(int argc,char **argv) G_GNUC_NORETURN;
527 int main(int argc,char **argv)
530 size_t opt_command_len;
534 if ((program_name=strrchr(argv[0],'/')))
537 program_name=argv[0];
539 atexit(lock_close_atexit);
541 optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
542 while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
546 case '1': /* -1|--start */
550 case '0': /* -0|--stop */
554 case 'T': /* -T|--start-command-timeout */
555 l=strtol(optarg,&endptr,0);
556 if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
557 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
558 opt_start_command_timeout=l;
561 case 'i': /* -i|--idle-server-timeout */
562 l=strtol(optarg,&endptr,0);
563 if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
564 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
565 opt_idle_server_timeout=l;
568 case 'S': /* -S|--syslog */
572 case 'e': /* -e|--stderr */
576 case 'l': /* -l|--lock */
580 case 'I': /* -I|--ignore-spawned-command-output */
581 opt_ignore_spawned_command_output=1;
584 case 'p': /* -p|--port */
585 l=strtol(optarg,&endptr,0);
586 if (l<=0 || l>=0x10000 || (endptr && *endptr))
587 fatal("Invalid -p|--port value: %s",optarg);
593 fatal("Error parsing commandline");
598 if (!opt_start && !opt_stop)
599 fatal("At least one of -1|--start or -0|--stop is required");
600 if ( opt_start && opt_stop)
601 fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
604 fatal("<start-server-command/stop-server-command> is a required argument");
606 for (i=optind;i<argc;i++)
607 opt_command_len+=strlen(argv[i])+1;
608 opt_command=xmalloc(opt_command_len);
610 for (i=optind;i<argc;i++) {
611 size_t argv_i_len=strlen(argv[i]);
615 memcpy(s,argv[i],argv_i_len);
619 assert(s==opt_command+opt_command_len);
621 if (!opt_syslog && !opt_stderr)