006a4f2947e233280cd963ccffcea68daf96dfde
[inetdmx.git] / inetdmx.c
1 /* $Id$ */
2
3
4 #define _GNU_SOURCE 1
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9 #include <sys/un.h>
10 #include <sys/poll.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14 #include <errno.h>
15 #include <netinet/in.h>
16 #include <string.h>
17 #include <getopt.h>
18 #include <sys/wait.h>
19 #include <stdarg.h>
20 #include <syslog.h>
21 #include <sys/file.h>
22 #include <utime.h>
23 #include <time.h>
24 #include <assert.h>
25
26
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)
34
35
36 /* /usr/include/glib-2.0/glib/gmacros.h */
37 #ifndef G_GNUC_PRINTF
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)))
41 #else   /* !__GNUC__ */
42 #define G_GNUC_PRINTF( format_idx, arg_idx )
43 #endif  /* !__GNUC__ */
44 #endif /* !G_GNUC_PRINTF */
45
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__))
51 #else   /* !__GNUC__ */
52 #define G_GNUC_NORETURN
53 #endif  /* !__GNUC__ */
54 #endif /* !G_GNUC_NORETURN */
55
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
59  * incorrect results.
60  */
61 #define G_N_ELEMENTS(arr)               (sizeof (arr) / sizeof ((arr)[0]))
62
63
64 static const char *program_name;
65
66 static int opt_start;
67 static int opt_stop;
68 static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
69 static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
70 static int opt_port;
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;
76
77
78 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
79
80 /* for atexit(3) function */
81 static int verror_quiet;
82
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)
85 {
86 int use_syslog=opt_syslog,use_stderr=opt_stderr;
87 char *string;
88 const char *const double_error="Error printing error message";
89
90         if (verror_quiet)
91                 return;
92         if (!use_syslog && !use_stderr)
93                 use_stderr=1;
94         if (-1==vasprintf(&string,fmt,ap)) {
95                 if (fmt==double_error)
96                         exit(EXIT_FAILURE);
97                 fatal(double_error);
98                 }
99         if (use_stderr)
100                 fprintf(stderr,"%s: %s\n",program_name,string);
101         if (use_syslog) {
102                 openlog(program_name,LOG_PID,LOG_DAEMON);
103                 syslog(LOG_DAEMON|LOG_ERR,"%s",string);
104                 closelog();
105                 }
106 }
107
108 static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
109 static void error(const char *fmt,...)
110 {
111 va_list ap;
112
113         va_start(ap,fmt);
114         verror(fmt,ap);
115         va_end(ap);
116 }
117
118 static void fatal(const char *fmt,...)
119 {
120 va_list ap;
121
122         va_start(ap,fmt);
123         verror(fmt,ap);
124         va_end(ap);
125         exit(EXIT_FAILURE);
126 }
127
128 static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
129 static char *asprintf_checked(const char *fmt,...)
130 {
131 char *string;
132 va_list ap;
133 int rc;
134
135         va_start(ap,fmt);
136         rc=vasprintf(&string,fmt,ap);
137         va_end(ap);
138         if (rc==-1)
139                 fatal("Error formatting string using formatstring: %s",fmt);
140         return string;
141 }
142
143 static void *xmalloc(size_t size)
144 {
145 void *r;
146
147         if ((r=malloc(size)))
148                 return r;
149         fatal("Error allocing %lu bytes",(unsigned long)size);
150 }
151
152 static void usage(void)
153 {
154         fprintf(stderr,"\
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\
163 \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);
171         exit(EXIT_FAILURE);
172 }
173
174 static const struct option longopts[]={
175         {"start"                        ,0,0,'1'},
176         {"stop"                         ,0,0,'0'},
177         {"start-command-timeout"        ,1,0,'T'},
178         {"idle-server-timeout"          ,1,0,'i'},
179         {"syslog"                       ,0,0,'S'},
180         {"stderr"                       ,0,0,'e'},
181         {"lock"                         ,1,0,'l'},
182         {"port"                         ,1,0,'p'},
183         {"ignore-spawned-command-output",0,0,'I'},
184         {"help"                         ,0,0,'h'},
185         {NULL                           ,0,0,0  },
186         };
187
188 static int lock_fd=-1;
189
190 static int sighandler_flock_timeout_hit;
191 static void sighandler_flock_timeout(int signo)
192 {
193         sighandler_flock_timeout_hit=1;
194 }
195
196 static int lock_create(int lock_mode)
197 {
198 int retries=3;
199 sighandler_t sighandler_alrm_orig;
200 int flock_rc;
201
202         if (!opt_lock)
203                 return 1;
204         /* Never drop the lock if the lock is already being held. */
205         if (lock_fd!=-1)
206                 retries=-1;
207 retry:
208         if (lock_fd==-1) {
209                 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
210                         fatal("Error creating lock file \"%s\": %m",opt_lock);
211                 }
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);
215         alarm(0);
216         signal(SIGALRM,sighandler_alrm_orig);
217         if (sighandler_flock_timeout_hit)
218                 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
219         if (flock_rc) {
220                 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
221                         return 0;
222                 fatal("Error locking lock file \"%s\": %m",opt_lock);
223                 }
224         if (access(opt_lock,R_OK|W_OK)) {
225                 if (retries--<=0)
226                         fatal("Racing for the lock file \"%s\", giving up",opt_lock);
227                 if (close(lock_fd))
228                         fatal("Error closing lock file \"%s\": %m",opt_lock);
229                 lock_fd=-1;
230                 goto retry;
231                 }
232         return 1;
233 }
234
235 static void lock_touch(void)
236 {
237         if (!opt_lock || lock_fd==-1)
238                 return;
239         if (utime(opt_lock,NULL))
240                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
241 }
242
243 static void lock_close(void)
244 {
245         if (lock_fd==-1 || !opt_lock)
246                 return;
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
249          */
250         if (flock(lock_fd,LOCK_UN|LOCK_NB))
251                 fatal("Error unlocking lock file \"%s\": %m",opt_lock);
252         if (close(lock_fd))
253                 fatal("Error closing lock file \"%s\": %m",opt_lock);
254         lock_fd=-1;
255 }
256
257 static void lock_close_atexit(void)
258 {
259         /* Prevent some crashes of malloc(3) etc. */
260         verror_quiet=1;
261         lock_close();
262 }
263
264 static int connect_try(void)
265 {
266 int fdtcp;
267 struct sockaddr_in sockaddr_tcp;
268
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)
277                         return -1;
278                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
279                 }
280         return fdtcp;
281 }
282
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)
286                 G_GNUC_NORETURN;
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)
290 {
291 struct pollfd pollfdi[2];
292 int pollfdo[2];
293 const char *pollfdi_name[2];
294 int fdi,fdo;
295
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;
304         for (;;) {
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++)
308                         if (0
309                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
310                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
311                                         )
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++) {
316                         for (;;) {
317                                 ssize_t got;
318                                 char buf[SESSION_BUFFER_SIZE];
319
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));
323                                 if (got<0) {
324                                         if (errno==EAGAIN)
325                                                 break;
326                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
327                                         }
328                                 if (got==0) {
329                                         lock_close();
330                                         exit(EXIT_SUCCESS);
331                                         }
332                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
333                                         if (fdi==fdo)
334                                                 continue;
335                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
336                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
337                                                                 pollfdi_name[fdo]);
338                                         if (got!=write(pollfdi[fdo].fd,buf,got))
339                                                 fatal("write(%s socket,%ld): %m",
340                                                                 pollfdi_name[fdo],(long)got);
341                                         }
342                                 }
343                         }
344                 }
345 }
346
347 static void session_try(void)
348 {
349 int fdtcp;
350
351         if (-1==(fdtcp=connect_try()))
352                 return;
353         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
354 }
355
356 static int popen_pclose_checked(const char *command)
357 {
358 FILE *f;
359 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
360 size_t got;
361
362         if (!(f=popen(command,"r")))
363                 fatal("Error opening spawned command \"%s\": %m",command);
364         setlinebuf(f);
365         while ((got=fread(buf,1,sizeof(buf)-1,f))) {
366 char *s,*s_next;
367
368                 assert(got<sizeof(buf));
369                 buf[got]=0;
370                 for (s=buf;*s;s=s_next) {
371                         if ((s_next=strchr(s,'\n'))) {
372                                 *s_next++=0;
373                                 error("Error line of spawned \"%s\": %s",command,s);
374                                 }
375                         else {
376                                 s_next=s+strlen(s);
377                                 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
378                                 }
379                         }
380                 }
381         if (ferror(f))
382                 fatal("Error reading output of spawned \"%s\"",command);
383         if (!feof(f))
384                 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
385         return pclose(f);
386 }
387
388 static void system_checked(const char *command)
389 {
390 int rc;
391
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)
396                                 );
397         else
398                 rc=system(opt_stderr
399                                 ? asprintf_checked("(%s) >&2",command)
400                                 : asprintf_checked("(%s) &>/dev/null",command)
401                                 );
402         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
403                 return;
404         if (WIFEXITED(rc))
405                 fatal("Error spawning command \"%s\": return code %d",
406                                 command,WEXITSTATUS(rc));
407 #ifdef WCOREDUMP
408         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
409                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
410                                 command,WTERMSIG(rc));
411 #endif /* WCOREDUMP */
412         if (WIFSIGNALED(rc))
413                 fatal("Error spawning command \"%s\": terminating signal %d",
414                                 command,WTERMSIG(rc));
415         if (WIFSTOPPED(rc))
416                 fatal("Error spawning command \"%s\": stopping signal %d",
417                                 command,WSTOPSIG(rc));
418 #ifdef WIFCONTINUED
419         if (WIFCONTINUED(rc))
420                 fatal("Error spawning command \"%s\": resumed by SIGCONT",
421                                 command);
422 #endif /* WIFCONTINUED */
423         fatal("Error spawning command \"%s\": unknown reason",
424                         command);
425 }
426
427 static void start(void) G_GNUC_NORETURN;
428 static void start(void)
429 {
430 int retry;
431
432         if (!opt_port)
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");
436
437         lock_create(LOCK_SH);
438         lock_touch();
439         session_try();
440         lock_close();
441
442         lock_create(LOCK_EX);
443         system_checked(opt_command);
444         lock_create(LOCK_SH);
445
446         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
447                 lock_touch();
448                 session_try();
449                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
450                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
451                 }
452         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
453                         opt_start_command_timeout,opt_port,opt_command);
454 }
455
456 /* Returns: Is fresh? */
457 static int lock_create_and_time_check(int lock_mode)
458 {
459 struct stat statbuf;
460
461         if (!opt_lock)
462                 return 0;
463
464         if (!lock_create(lock_mode|LOCK_NB))
465                 exit(EXIT_SUCCESS);
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;
469 }
470
471 static void lock_delete_and_close(void)
472 {
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);
476         lock_close();
477 }
478
479 static void stop(void) G_GNUC_NORETURN;
480 static void stop(void)
481 {
482 int is_fresh;
483
484         /* Lock still being held! */
485         if (opt_port)
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");
491
492         is_fresh=lock_create_and_time_check(LOCK_SH);
493         lock_close();
494         if (is_fresh)
495                 exit(EXIT_SUCCESS);
496
497         lock_create_and_time_check(LOCK_EX);
498         system_checked(opt_command);
499         lock_delete_and_close();
500
501         exit(EXIT_SUCCESS);
502 }
503
504 int main(int argc,char **argv) G_GNUC_NORETURN;
505 int main(int argc,char **argv)
506 {
507 char optc;
508 size_t opt_command_len;
509 int i;
510 char *s;
511
512         if ((program_name=strrchr(argv[0],'/')))
513                 program_name++;
514         else
515                 program_name=argv[0];
516
517         atexit(lock_close_atexit);
518
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) {
521 long l;
522 char *endptr;
523
524                 case '1':       /* -1|--start */
525                         opt_start=1;
526                         break;
527
528                 case '0':       /* -0|--stop */
529                         opt_stop=1;
530                         break;
531
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;
537                         break;
538
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;
544                         break;
545
546                 case 'S':       /* -S|--syslog */
547                         opt_syslog=1;
548                         break;
549
550                 case 'e':       /* -e|--stderr */
551                         opt_stderr=1;
552                         break;
553
554                 case 'l':       /* -l|--lock */
555                         opt_lock=optarg;
556                         break;
557
558                 case 'I':       /* -I|--ignore-spawned-command-output */
559                         opt_ignore_spawned_command_output=1;
560                         break;
561
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);
566                         opt_port=l;
567                         break;
568
569                 default:
570                         if (optc!='h')
571                                 fatal("Error parsing commandline");
572                         usage();
573                         break;
574                 }
575
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");
580
581         if (optind>=argc)
582                 fatal("<start-server-command/stop-server-command> is a required argument");
583         opt_command_len=0;
584         for (i=optind;i<argc;i++)
585                 opt_command_len+=strlen(argv[i])+1;
586         opt_command=xmalloc(opt_command_len);
587         s=opt_command;
588         for (i=optind;i<argc;i++) {
589 size_t argv_i_len=strlen(argv[i]);
590
591                 if (s>opt_command)
592                         *s++=' ';
593                 memcpy(s,argv[i],argv_i_len);
594                 s+=argv_i_len;
595                 }
596         *s++=0;
597         assert(s==opt_command+opt_command_len);
598
599         if (!opt_syslog && !opt_stderr)
600                 opt_stderr=1;
601
602         if (opt_start)
603                 start();
604         if (opt_stop)
605                 stop();
606         assert(0);
607 }