13efc7fd7f2fa4fc23be29bccb1a0c805d2392b2
[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 static void verror(const char *fmt,va_list ap) G_GNUC_PRINTF(1,0);
81 static void verror(const char *fmt,va_list ap)
82 {
83 int use_syslog=opt_syslog,use_stderr=opt_stderr;
84 char *string;
85 const char *const double_error="Error printing error message";
86
87         if (!use_syslog && !use_stderr)
88                 use_stderr=1;
89         if (-1==vasprintf(&string,fmt,ap)) {
90                 if (fmt==double_error)
91                         exit(EXIT_FAILURE);
92                 fatal(double_error);
93                 }
94         if (use_stderr)
95                 fprintf(stderr,"%s: %s\n",program_name,string);
96         if (use_syslog) {
97                 openlog(program_name,LOG_PID,LOG_DAEMON);
98                 syslog(LOG_DAEMON|LOG_ERR,"%s",string);
99                 closelog();
100                 }
101 }
102
103 static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
104 static void error(const char *fmt,...)
105 {
106 va_list ap;
107
108         va_start(ap,fmt);
109         verror(fmt,ap);
110         va_end(ap);
111 }
112
113 static void fatal(const char *fmt,...)
114 {
115 va_list ap;
116
117         va_start(ap,fmt);
118         verror(fmt,ap);
119         va_end(ap);
120         exit(EXIT_FAILURE);
121 }
122
123 static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
124 static char *asprintf_checked(const char *fmt,...)
125 {
126 char *string;
127 va_list ap;
128 int rc;
129
130         va_start(ap,fmt);
131         rc=vasprintf(&string,fmt,ap);
132         va_end(ap);
133         if (rc==-1)
134                 fatal("Error formatting string using formatstring: %s",fmt);
135         return string;
136 }
137
138 static void *xmalloc(size_t size)
139 {
140 void *r;
141
142         if ((r=malloc(size)))
143                 return r;
144         fatal("Error allocing %lu bytes",(unsigned long)size);
145 }
146
147 static void usage(void)
148 {
149         fprintf(stderr,"\
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\
158 \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);
166         exit(EXIT_FAILURE);
167 }
168
169 static const struct option longopts[]={
170         {"start"                        ,0,0,'1'},
171         {"stop"                         ,0,0,'0'},
172         {"start-command-timeout"        ,1,0,'T'},
173         {"idle-server-timeout"          ,1,0,'i'},
174         {"syslog"                       ,0,0,'S'},
175         {"stderr"                       ,0,0,'e'},
176         {"lock"                         ,1,0,'l'},
177         {"port"                         ,1,0,'p'},
178         {"ignore-spawned-command-output",0,0,'I'},
179         {"help"                         ,0,0,'h'},
180         {NULL                           ,0,0,0  },
181         };
182
183 static int lock_fd=-1;
184
185 static int sighandler_flock_timeout_hit;
186 static void sighandler_flock_timeout(int signo)
187 {
188         sighandler_flock_timeout_hit=1;
189 }
190
191 static int lock_create(int lock_mode)
192 {
193 int retries=3;
194 sighandler_t sighandler_alrm_orig;
195 int flock_rc;
196
197         if (!opt_lock)
198                 return 1;
199         /* Never drop the lock if the lock is already being held. */
200         if (lock_fd!=-1)
201                 retries=-1;
202 retry:
203         if (lock_fd==-1) {
204                 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
205                         fatal("Error creating lock file \"%s\": %m",opt_lock);
206                 }
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);
210         alarm(0);
211         signal(SIGALRM,sighandler_alrm_orig);
212         if (sighandler_flock_timeout_hit)
213                 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
214         if (flock_rc) {
215                 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
216                         return 0;
217                 fatal("Error locking lock file \"%s\": %m",opt_lock);
218                 }
219         if (access(opt_lock,R_OK|W_OK)) {
220                 if (retries--<=0)
221                         fatal("Racing for the lock file \"%s\", giving up",opt_lock);
222                 if (close(lock_fd))
223                         fatal("Error closing lock file \"%s\": %m",opt_lock);
224                 lock_fd=-1;
225                 goto retry;
226                 }
227         return 1;
228 }
229
230 static void lock_touch(void)
231 {
232         if (!opt_lock || lock_fd==-1)
233                 return;
234         if (utime(opt_lock,NULL))
235                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
236 }
237
238 static void lock_close(void)
239 {
240         if (lock_fd==-1)
241                 return;
242         if (close(lock_fd))
243                 fatal("Error closing lock file \"%s\": %m",opt_lock);
244         lock_fd=-1;
245 }
246
247 static int connect_try(void)
248 {
249 int fdtcp;
250 struct sockaddr_in sockaddr_tcp;
251
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)
260                         return -1;
261                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
262                 }
263         return fdtcp;
264 }
265
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)
269                 G_GNUC_NORETURN;
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)
273 {
274 struct pollfd pollfdi[2];
275 int pollfdo[2];
276 const char *pollfdi_name[2];
277 int fdi,fdo;
278
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;
287         for (;;) {
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++)
291                         if (0
292                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
293                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
294                                         )
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++) {
299                         for (;;) {
300                                 ssize_t got;
301                                 char buf[SESSION_BUFFER_SIZE];
302
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));
306                                 if (got<0) {
307                                         if (errno==EAGAIN)
308                                                 break;
309                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
310                                         }
311                                 if (got==0) {
312                                         lock_close();
313                                         exit(EXIT_SUCCESS);
314                                         }
315                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
316                                         if (fdi==fdo)
317                                                 continue;
318                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
319                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
320                                                                 pollfdi_name[fdo]);
321                                         if (got!=write(pollfdi[fdo].fd,buf,got))
322                                                 fatal("write(%s socket,%ld): %m",
323                                                                 pollfdi_name[fdo],(long)got);
324                                         }
325                                 }
326                         }
327                 }
328 }
329
330 static void session_try(void)
331 {
332 int fdtcp;
333
334         if (-1==(fdtcp=connect_try()))
335                 return;
336         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
337 }
338
339 static int popen_pclose_checked(const char *command)
340 {
341 FILE *f;
342 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
343 size_t got;
344
345         if (!(f=popen(command,"r")))
346                 fatal("Error opening spawned command \"%s\": %m",command);
347         setlinebuf(f);
348         while ((got=fread(buf,1,sizeof(buf)-1,f))) {
349 char *s,*s_next;
350
351                 assert(got<sizeof(buf));
352                 buf[got]=0;
353                 for (s=buf;*s;s=s_next) {
354                         if ((s_next=strchr(s,'\n'))) {
355                                 *s_next++=0;
356                                 error("Error line of spawned \"%s\": %s",command,s);
357                                 }
358                         else {
359                                 s_next=s+strlen(s);
360                                 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
361                                 }
362                         }
363                 }
364         if (ferror(f))
365                 fatal("Error reading output of spawned \"%s\"",command);
366         if (!feof(f))
367                 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
368         return pclose(f);
369 }
370
371 static void system_checked(const char *command)
372 {
373 int rc;
374
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)
379                                 );
380         else
381                 rc=system(opt_stderr
382                                 ? asprintf_checked("(%s) >&2",command)
383                                 : asprintf_checked("(%s) &>/dev/null",command)
384                                 );
385         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
386                 return;
387         if (WIFEXITED(rc))
388                 fatal("Error spawning command \"%s\": return code %d",
389                                 command,WEXITSTATUS(rc));
390 #ifdef WCOREDUMP
391         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
392                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
393                                 command,WTERMSIG(rc));
394 #endif /* WCOREDUMP */
395         if (WIFSIGNALED(rc))
396                 fatal("Error spawning command \"%s\": terminating signal %d",
397                                 command,WTERMSIG(rc));
398         if (WIFSTOPPED(rc))
399                 fatal("Error spawning command \"%s\": stopping signal %d",
400                                 command,WSTOPSIG(rc));
401 #ifdef WIFCONTINUED
402         if (WIFCONTINUED(rc))
403                 fatal("Error spawning command \"%s\": resumed by SIGCONT",
404                                 command);
405 #endif /* WIFCONTINUED */
406         fatal("Error spawning command \"%s\": unknown reason",
407                         command);
408 }
409
410 static void start(void) G_GNUC_NORETURN;
411 static void start(void)
412 {
413 int retry;
414
415         if (!opt_port)
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");
419
420         lock_create(LOCK_SH);
421         lock_touch();
422         session_try();
423         lock_close();
424
425         lock_create(LOCK_EX);
426         system_checked(opt_command);
427         lock_create(LOCK_SH);
428
429         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
430                 lock_touch();
431                 session_try();
432                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
433                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
434                 }
435         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
436                         opt_start_command_timeout,opt_port,opt_command);
437 }
438
439 /* Returns: Is fresh? */
440 static int lock_create_and_time_check(int lock_mode)
441 {
442 struct stat statbuf;
443
444         if (!opt_lock)
445                 return 0;
446
447         if (!lock_create(lock_mode|LOCK_NB))
448                 exit(EXIT_SUCCESS);
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;
452 }
453
454 static void lock_delete_and_close(void)
455 {
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);
459         lock_close();
460 }
461
462 static void stop(void) G_GNUC_NORETURN;
463 static void stop(void)
464 {
465 int is_fresh;
466
467         /* Lock still being held! */
468         if (opt_port)
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");
474
475         is_fresh=lock_create_and_time_check(LOCK_SH);
476         lock_close();
477         if (is_fresh)
478                 exit(EXIT_SUCCESS);
479
480         lock_create_and_time_check(LOCK_EX);
481         system_checked(opt_command);
482         lock_delete_and_close();
483
484         exit(EXIT_SUCCESS);
485 }
486
487 int main(int argc,char **argv) G_GNUC_NORETURN;
488 int main(int argc,char **argv)
489 {
490 char optc;
491 size_t opt_command_len;
492 int i;
493 char *s;
494
495         if ((program_name=strrchr(argv[0],'/')))
496                 program_name++;
497         else
498                 program_name=argv[0];
499
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) {
502 long l;
503 char *endptr;
504
505                 case '1':       /* -1|--start */
506                         opt_start=1;
507                         break;
508
509                 case '0':       /* -0|--stop */
510                         opt_stop=1;
511                         break;
512
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;
518                         break;
519
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;
525                         break;
526
527                 case 'S':       /* -S|--syslog */
528                         opt_syslog=1;
529                         break;
530
531                 case 'e':       /* -e|--stderr */
532                         opt_stderr=1;
533                         break;
534
535                 case 'l':       /* -l|--lock */
536                         opt_lock=optarg;
537                         break;
538
539                 case 'I':       /* -I|--ignore-spawned-command-output */
540                         opt_ignore_spawned_command_output=1;
541                         break;
542
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);
547                         opt_port=l;
548                         break;
549
550                 default:
551                         if (optc!='h')
552                                 fatal("Error parsing commandline");
553                         usage();
554                         break;
555                 }
556
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");
561
562         if (optind>=argc)
563                 fatal("<start-server-command/stop-server-command> is a required argument");
564         opt_command_len=0;
565         for (i=optind;i<argc;i++)
566                 opt_command_len+=strlen(argv[i])+1;
567         opt_command=xmalloc(opt_command_len);
568         s=opt_command;
569         for (i=optind;i<argc;i++) {
570 size_t argv_i_len=strlen(argv[i]);
571
572                 if (s>opt_command)
573                         *s++=' ';
574                 memcpy(s,argv[i],argv_i_len);
575                 s+=argv_i_len;
576                 }
577         *s++=0;
578         assert(s==opt_command+opt_command_len);
579
580         if (!opt_syslog && !opt_stderr)
581                 opt_stderr=1;
582
583         if (opt_start)
584                 start();
585         if (opt_stop)
586                 stop();
587         assert(0);
588 }