ebab3bf2b0a34bff752249f9b2b3fb23214d8fd7
[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 const 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 usage(void)
139 {
140         fprintf(stderr,"\
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\
149 \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);
157         exit(EXIT_FAILURE);
158 }
159
160 static const struct option longopts[]={
161         {"start"                        ,0,0,'1'},
162         {"stop"                         ,0,0,'0'},
163         {"start-command-timeout"        ,1,0,'T'},
164         {"idle-server-timeout"          ,1,0,'i'},
165         {"syslog"                       ,0,0,'S'},
166         {"stderr"                       ,0,0,'e'},
167         {"lock"                         ,1,0,'l'},
168         {"port"                         ,1,0,'p'},
169         {"ignore-spawned-command-output",0,0,'I'},
170         {"help"                         ,0,0,'h'},
171         {NULL                           ,0,0,0  },
172         };
173
174 static int lock_fd=-1;
175
176 static int sighandler_flock_timeout_hit;
177 static void sighandler_flock_timeout(int signo)
178 {
179         sighandler_flock_timeout_hit=1;
180 }
181
182 static int lock_create(int lock_mode)
183 {
184 int retries=3;
185 sighandler_t sighandler_alrm_orig;
186 int flock_rc;
187
188         if (!opt_lock)
189                 return 1;
190         /* Never drop the lock if the lock is already being held. */
191         if (lock_fd!=-1)
192                 retries=-1;
193 retry:
194         if (lock_fd==-1) {
195                 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
196                         fatal("Error creating lock file \"%s\": %m",opt_lock);
197                 }
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);
201         alarm(0);
202         signal(SIGALRM,sighandler_alrm_orig);
203         if (sighandler_flock_timeout_hit)
204                 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
205         if (flock_rc) {
206                 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
207                         return 0;
208                 fatal("Error locking lock file \"%s\": %m",opt_lock);
209                 }
210         if (access(opt_lock,R_OK|W_OK)) {
211                 if (retries--<=0)
212                         fatal("Racing for the lock file \"%s\", giving up",opt_lock);
213                 if (close(lock_fd))
214                         fatal("Error closing lock file \"%s\": %m",opt_lock);
215                 lock_fd=-1;
216                 goto retry;
217                 }
218         return 1;
219 }
220
221 static void lock_touch(void)
222 {
223         if (!opt_lock || lock_fd==-1)
224                 return;
225         if (utime(opt_lock,NULL))
226                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
227 }
228
229 static void lock_close(void)
230 {
231         if (lock_fd==-1)
232                 return;
233         if (close(lock_fd))
234                 fatal("Error closing lock file \"%s\": %m",opt_lock);
235         lock_fd=-1;
236 }
237
238 static int connect_try(void)
239 {
240 int fdtcp;
241 struct sockaddr_in sockaddr_tcp;
242
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)
251                         return -1;
252                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
253                 }
254         return fdtcp;
255 }
256
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)
260                 G_GNUC_NORETURN;
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)
264 {
265 struct pollfd pollfdi[2];
266 int pollfdo[2];
267 const char *pollfdi_name[2];
268 int fdi,fdo;
269
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;
278         for (;;) {
279                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
280                         if (pollfdi[fdi].events)
281                                 break;
282                 if (fdi>=G_N_ELEMENTS(pollfdi)) {
283                         lock_close();
284                         exit(EXIT_SUCCESS);
285                         }
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++)
289                         if (0
290                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
291                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
292                                         )
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++) {
297                         for (;;) {
298                                 ssize_t got;
299                                 char buf[SESSION_BUFFER_SIZE];
300
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));
304                                 if (got<0) {
305                                         if (errno==EAGAIN)
306                                                 break;
307                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
308                                         }
309                                 if (got==0) {
310                                         pollfdi[fdi].events&=~POLLIN;
311                                         break;
312                                         }
313                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
314                                         if (fdi==fdo)
315                                                 continue;
316                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
317                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
318                                                                 pollfdi_name[fdo]);
319                                         if (got!=write(pollfdi[fdo].fd,buf,got))
320                                                 fatal("write(%s socket,%ld): %m",
321                                                                 pollfdi_name[fdo],(long)got);
322                                         }
323                                 }
324                         }
325                 }
326 }
327
328 static void session_try(void)
329 {
330 int fdtcp;
331
332         if (-1==(fdtcp=connect_try()))
333                 return;
334         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
335 }
336
337 static int popen_pclose_checked(const char *command)
338 {
339 FILE *f;
340 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
341 size_t got;
342
343         if (!(f=popen(command,"r")))
344                 fatal("Error opening spawned command \"%s\": %m",command);
345         setlinebuf(f);
346         while ((got=fread(buf,1,sizeof(buf)-1,f))) {
347 char *s,*s_next;
348
349                 assert(got<sizeof(buf));
350                 buf[got]=0;
351                 for (s=buf;*s;s=s_next) {
352                         if ((s_next=strchr(s,'\n'))) {
353                                 *s_next++=0;
354                                 error("Error line of spawned \"%s\": %s",command,s);
355                                 }
356                         else {
357                                 s_next=s+strlen(s);
358                                 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
359                                 }
360                         }
361                 }
362         if (ferror(f))
363                 fatal("Error reading output of spawned \"%s\"",command);
364         if (!feof(f))
365                 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
366         return pclose(f);
367 }
368
369 static void system_checked(const char *command)
370 {
371 int rc;
372
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)
377                                 );
378         else
379                 rc=system(opt_stderr
380                                 ? asprintf_checked("(%s) >&2",command)
381                                 : asprintf_checked("(%s) &>/dev/null",command)
382                                 );
383         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
384                 return;
385         if (WIFEXITED(rc))
386                 fatal("Error spawning command \"%s\": return code %d",
387                                 command,WEXITSTATUS(rc));
388 #ifdef WCOREDUMP
389         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
390                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
391                                 command,WTERMSIG(rc));
392 #endif /* WCOREDUMP */
393         if (WIFSIGNALED(rc))
394                 fatal("Error spawning command \"%s\": terminating signal %d",
395                                 command,WTERMSIG(rc));
396         if (WIFSTOPPED(rc))
397                 fatal("Error spawning command \"%s\": stopping signal %d",
398                                 command,WSTOPSIG(rc));
399 #ifdef WIFCONTINUED
400         if (WIFCONTINUED(rc))
401                 fatal("Error spawning command \"%s\": resumed by SIGCONT",
402                                 command);
403 #endif /* WIFCONTINUED */
404         fatal("Error spawning command \"%s\": unknown reason",
405                         command);
406 }
407
408 static void start(void) G_GNUC_NORETURN;
409 static void start(void)
410 {
411 int retry;
412
413         if (!opt_port)
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");
417
418         lock_create(LOCK_SH);
419         lock_touch();
420         session_try();
421         lock_close();
422
423         lock_create(LOCK_EX);
424         system_checked(opt_command);
425         lock_create(LOCK_SH);
426
427         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
428                 lock_touch();
429                 session_try();
430                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
431                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
432                 }
433         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
434                         opt_start_command_timeout,opt_port,opt_command);
435 }
436
437 /* Returns: Is fresh? */
438 static int lock_create_and_time_check(int lock_mode)
439 {
440 struct stat statbuf;
441
442         if (!opt_lock)
443                 return 0;
444
445         if (!lock_create(lock_mode|LOCK_NB))
446                 exit(EXIT_SUCCESS);
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;
450 }
451
452 static void lock_delete_and_close(void)
453 {
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);
457         lock_close();
458 }
459
460 static void stop(void) G_GNUC_NORETURN;
461 static void stop(void)
462 {
463 int is_fresh;
464
465         /* Lock still being held! */
466         if (opt_port)
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");
472
473         is_fresh=lock_create_and_time_check(LOCK_SH);
474         lock_close();
475         if (is_fresh)
476                 exit(EXIT_SUCCESS);
477
478         lock_create_and_time_check(LOCK_EX);
479         system_checked(opt_command);
480         lock_delete_and_close();
481
482         exit(EXIT_SUCCESS);
483 }
484
485 int main(int argc,char **argv) G_GNUC_NORETURN;
486 int main(int argc,char **argv)
487 {
488 char optc;
489
490         if ((program_name=strrchr(argv[0],'/')))
491                 program_name++;
492         else
493                 program_name=argv[0];
494
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) {
497 long l;
498 char *endptr;
499
500                 case '1':       /* -1|--start */
501                         opt_start=1;
502                         break;
503
504                 case '0':       /* -0|--stop */
505                         opt_stop=1;
506                         break;
507
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;
513                         break;
514
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;
520                         break;
521
522                 case 'S':       /* -S|--syslog */
523                         opt_syslog=1;
524                         break;
525
526                 case 'e':       /* -e|--stderr */
527                         opt_stderr=1;
528                         break;
529
530                 case 'l':       /* -l|--lock */
531                         opt_lock=optarg;
532                         break;
533
534                 case 'I':       /* -I|--ignore-spawned-command-output */
535                         opt_ignore_spawned_command_output=1;
536                         break;
537
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);
542                         opt_port=l;
543                         break;
544
545                 default:
546                         if (optc!='h')
547                                 fatal("Error parsing commandline");
548                         usage();
549                         break;
550                 }
551
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");
556
557         if (optind>=argc)
558                 fatal("<start-server-command/stop-server-command> is a required argument");
559         if (optind+1<argc)
560                 fatal("Too many arguments, <start-server-command/stop-server-command> may need quoting");
561         opt_command=argv[optind];
562
563         if (!opt_syslog && !opt_stderr)
564                 opt_stderr=1;
565
566         if (opt_start)
567                 start();
568         if (opt_stop)
569                 stop();
570         assert(0);
571 }