Fixed --stop if the lock file does not exist.
[inetdmx.git] / inetdmx.c
1 /*
2  * $Id$
3  * Latest:
4  *      http://cvs.jankratochvil.net/viewcvs/nethome/src/inetdmx.c?rev=HEAD
5  *
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.
9  *
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.
14  *
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.
18  */
19
20
21 #define _GNU_SOURCE 1
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <sys/poll.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <errno.h>
32 #include <netinet/in.h>
33 #include <string.h>
34 #include <getopt.h>
35 #include <sys/wait.h>
36 #include <stdarg.h>
37 #include <syslog.h>
38 #include <sys/file.h>
39 #include <utime.h>
40 #include <time.h>
41 #include <assert.h>
42
43
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)
51
52
53 /* /usr/include/glib-2.0/glib/gmacros.h */
54 #ifndef G_GNUC_PRINTF
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)))
58 #else   /* !__GNUC__ */
59 #define G_GNUC_PRINTF( format_idx, arg_idx )
60 #endif  /* !__GNUC__ */
61 #endif /* !G_GNUC_PRINTF */
62
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__))
68 #else   /* !__GNUC__ */
69 #define G_GNUC_NORETURN
70 #endif  /* !__GNUC__ */
71 #endif /* !G_GNUC_NORETURN */
72
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
76  * incorrect results.
77  */
78 #define G_N_ELEMENTS(arr)               (sizeof (arr) / sizeof ((arr)[0]))
79
80
81 static const char *program_name;
82
83 static int opt_start;
84 static int opt_stop;
85 static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
86 static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
87 static int opt_port;
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;
93
94
95 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
96
97 /* for atexit(3) function */
98 static int verror_quiet;
99
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)
102 {
103 int use_syslog=opt_syslog,use_stderr=opt_stderr;
104 char *string;
105 const char *const double_error="Error printing error message";
106
107         if (verror_quiet)
108                 return;
109         if (!use_syslog && !use_stderr)
110                 use_stderr=1;
111         if (-1==vasprintf(&string,fmt,ap)) {
112                 if (fmt==double_error)
113                         exit(EXIT_FAILURE);
114                 fatal(double_error);
115                 }
116         if (use_stderr)
117                 fprintf(stderr,"%s: %s\n",program_name,string);
118         if (use_syslog) {
119                 openlog(program_name,LOG_PID,LOG_DAEMON);
120                 syslog(LOG_DAEMON|LOG_ERR,"%s",string);
121                 closelog();
122                 }
123 }
124
125 static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
126 static void error(const char *fmt,...)
127 {
128 va_list ap;
129
130         va_start(ap,fmt);
131         verror(fmt,ap);
132         va_end(ap);
133 }
134
135 static void fatal(const char *fmt,...)
136 {
137 va_list ap;
138
139         va_start(ap,fmt);
140         verror(fmt,ap);
141         va_end(ap);
142         exit(EXIT_FAILURE);
143 }
144
145 static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
146 static char *asprintf_checked(const char *fmt,...)
147 {
148 char *string;
149 va_list ap;
150 int rc;
151
152         va_start(ap,fmt);
153         rc=vasprintf(&string,fmt,ap);
154         va_end(ap);
155         if (rc==-1)
156                 fatal("Error formatting string using formatstring: %s",fmt);
157         return string;
158 }
159
160 static void *xmalloc(size_t size)
161 {
162 void *r;
163
164         if ((r=malloc(size)))
165                 return r;
166         fatal("Error allocing %lu bytes",(unsigned long)size);
167 }
168
169 static void usage(void)
170 {
171         fprintf(stderr,"\
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\
180 \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);
188         exit(EXIT_FAILURE);
189 }
190
191 static const struct option longopts[]={
192         {"start"                        ,0,0,'1'},
193         {"stop"                         ,0,0,'0'},
194         {"start-command-timeout"        ,1,0,'T'},
195         {"idle-server-timeout"          ,1,0,'i'},
196         {"syslog"                       ,0,0,'S'},
197         {"stderr"                       ,0,0,'e'},
198         {"lock"                         ,1,0,'l'},
199         {"port"                         ,1,0,'p'},
200         {"ignore-spawned-command-output",0,0,'I'},
201         {"help"                         ,0,0,'h'},
202         {NULL                           ,0,0,0  },
203         };
204
205 static int lock_fd=-1;
206
207 static int sighandler_flock_timeout_hit;
208 static void sighandler_flock_timeout(int signo)
209 {
210         sighandler_flock_timeout_hit=1;
211 }
212
213 static int lock_create(int lock_mode)
214 {
215 int retries=3;
216 sighandler_t sighandler_alrm_orig;
217 int flock_rc;
218
219         if (!opt_lock)
220                 return 1;
221         /* Never drop the lock if the lock is already being held. */
222         if (lock_fd!=-1)
223                 retries=-1;
224 retry:
225         if (lock_fd==-1) {
226                 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
227                         fatal("Error creating lock file \"%s\": %m",opt_lock);
228                 }
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);
232         alarm(0);
233         signal(SIGALRM,sighandler_alrm_orig);
234         if (sighandler_flock_timeout_hit)
235                 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
236         if (flock_rc) {
237                 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
238                         return 0;
239                 fatal("Error locking lock file \"%s\": %m",opt_lock);
240                 }
241         if (access(opt_lock,R_OK|W_OK)) {
242                 if (retries--<=0)
243                         fatal("Racing for the lock file \"%s\", giving up",opt_lock);
244                 if (close(lock_fd))
245                         fatal("Error closing lock file \"%s\": %m",opt_lock);
246                 lock_fd=-1;
247                 goto retry;
248                 }
249         return 1;
250 }
251
252 static void lock_touch(void)
253 {
254         if (!opt_lock || lock_fd==-1)
255                 return;
256         if (utime(opt_lock,NULL))
257                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
258 }
259
260 static void lock_close(void)
261 {
262         if (lock_fd==-1 || !opt_lock)
263                 return;
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
266          */
267         if (flock(lock_fd,LOCK_UN|LOCK_NB))
268                 fatal("Error unlocking lock file \"%s\": %m",opt_lock);
269         if (close(lock_fd))
270                 fatal("Error closing lock file \"%s\": %m",opt_lock);
271         lock_fd=-1;
272 }
273
274 static void lock_close_atexit(void)
275 {
276         /* Prevent some crashes of malloc(3) etc. */
277         verror_quiet=1;
278         lock_close();
279 }
280
281 static int connect_try(void)
282 {
283 int fdtcp;
284 struct sockaddr_in sockaddr_tcp;
285
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)
294                         return -1;
295                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
296                 }
297         return fdtcp;
298 }
299
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)
303                 G_GNUC_NORETURN;
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)
307 {
308 struct pollfd pollfdi[2];
309 int pollfdo[2];
310 const char *pollfdi_name[2];
311 int fdi,fdo;
312
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;
321         for (;;) {
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++)
325                         if (0
326                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
327                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
328                                         )
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++) {
333                         for (;;) {
334                                 ssize_t got;
335                                 char buf[SESSION_BUFFER_SIZE];
336
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));
340                                 if (got<0) {
341                                         if (errno==EAGAIN)
342                                                 break;
343                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
344                                         }
345                                 if (got==0) {
346                                         lock_close();
347                                         exit(EXIT_SUCCESS);
348                                         }
349                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
350                                         if (fdi==fdo)
351                                                 continue;
352                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
353                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
354                                                                 pollfdi_name[fdo]);
355                                         if (got!=write(pollfdi[fdo].fd,buf,got))
356                                                 fatal("write(%s socket,%ld): %m",
357                                                                 pollfdi_name[fdo],(long)got);
358                                         }
359                                 }
360                         }
361                 }
362 }
363
364 static void session_try(void)
365 {
366 int fdtcp;
367
368         if (-1==(fdtcp=connect_try()))
369                 return;
370         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
371 }
372
373 static int popen_pclose_checked(const char *command)
374 {
375 FILE *f;
376 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
377 size_t got;
378
379         if (!(f=popen(command,"r")))
380                 fatal("Error opening spawned command \"%s\": %m",command);
381         setlinebuf(f);
382         while ((got=fread(buf,1,sizeof(buf)-1,f))) {
383 char *s,*s_next;
384
385                 assert(got<sizeof(buf));
386                 buf[got]=0;
387                 for (s=buf;*s;s=s_next) {
388                         if ((s_next=strchr(s,'\n'))) {
389                                 *s_next++=0;
390                                 error("Error line of spawned \"%s\": %s",command,s);
391                                 }
392                         else {
393                                 s_next=s+strlen(s);
394                                 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
395                                 }
396                         }
397                 }
398         if (ferror(f))
399                 fatal("Error reading output of spawned \"%s\"",command);
400         if (!feof(f))
401                 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
402         return pclose(f);
403 }
404
405 static void system_checked(const char *command)
406 {
407 int rc;
408
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)
413                                 );
414         else
415                 rc=system(opt_stderr
416                                 ? asprintf_checked("(%s) >&2",command)
417                                 : asprintf_checked("(%s) &>/dev/null",command)
418                                 );
419         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
420                 return;
421         if (WIFEXITED(rc))
422                 fatal("Error spawning command \"%s\": return code %d",
423                                 command,WEXITSTATUS(rc));
424 #ifdef WCOREDUMP
425         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
426                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
427                                 command,WTERMSIG(rc));
428 #endif /* WCOREDUMP */
429         if (WIFSIGNALED(rc))
430                 fatal("Error spawning command \"%s\": terminating signal %d",
431                                 command,WTERMSIG(rc));
432         if (WIFSTOPPED(rc))
433                 fatal("Error spawning command \"%s\": stopping signal %d",
434                                 command,WSTOPSIG(rc));
435 #ifdef WIFCONTINUED
436         if (WIFCONTINUED(rc))
437                 fatal("Error spawning command \"%s\": resumed by SIGCONT",
438                                 command);
439 #endif /* WIFCONTINUED */
440         fatal("Error spawning command \"%s\": unknown reason",
441                         command);
442 }
443
444 static void start(void) G_GNUC_NORETURN;
445 static void start(void)
446 {
447 int retry;
448
449         if (!opt_port)
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");
453
454         lock_create(LOCK_SH);
455         lock_touch();
456         session_try();
457         lock_close();
458
459         lock_create(LOCK_EX);
460         system_checked(opt_command);
461         lock_create(LOCK_SH);
462
463         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
464                 lock_touch();
465                 session_try();
466                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
467                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
468                 }
469         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
470                         opt_start_command_timeout,opt_port,opt_command);
471 }
472
473 /* Returns: Is fresh or does not exist? */
474 static int lock_open_and_time_check(int lock_mode)
475 {
476 struct stat statbuf;
477
478         if (!opt_lock)
479                 return 0;
480
481         if (access(opt_lock,R_OK|W_OK)) {
482                 if (errno==ENOENT)
483                         return 1;
484                 fatal("Error checking existance of the lock file \"%s\": %m",opt_lock);
485                 }
486         if (!lock_create(lock_mode|LOCK_NB))
487                 return 0;
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;
491 }
492
493 static void lock_delete_and_close(void)
494 {
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);
498         lock_close();
499 }
500
501 static void stop(void) G_GNUC_NORETURN;
502 static void stop(void)
503 {
504 int is_fresh;
505
506         /* Lock still being held! */
507         if (opt_port)
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");
513
514         is_fresh=lock_open_and_time_check(LOCK_SH);
515         lock_close();
516         if (is_fresh)
517                 exit(EXIT_SUCCESS);
518
519         lock_open_and_time_check(LOCK_EX);
520         system_checked(opt_command);
521         lock_delete_and_close();
522
523         exit(EXIT_SUCCESS);
524 }
525
526 int main(int argc,char **argv) G_GNUC_NORETURN;
527 int main(int argc,char **argv)
528 {
529 char optc;
530 size_t opt_command_len;
531 int i;
532 char *s;
533
534         if ((program_name=strrchr(argv[0],'/')))
535                 program_name++;
536         else
537                 program_name=argv[0];
538
539         atexit(lock_close_atexit);
540
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) {
543 long l;
544 char *endptr;
545
546                 case '1':       /* -1|--start */
547                         opt_start=1;
548                         break;
549
550                 case '0':       /* -0|--stop */
551                         opt_stop=1;
552                         break;
553
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;
559                         break;
560
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;
566                         break;
567
568                 case 'S':       /* -S|--syslog */
569                         opt_syslog=1;
570                         break;
571
572                 case 'e':       /* -e|--stderr */
573                         opt_stderr=1;
574                         break;
575
576                 case 'l':       /* -l|--lock */
577                         opt_lock=optarg;
578                         break;
579
580                 case 'I':       /* -I|--ignore-spawned-command-output */
581                         opt_ignore_spawned_command_output=1;
582                         break;
583
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);
588                         opt_port=l;
589                         break;
590
591                 default:
592                         if (optc!='h')
593                                 fatal("Error parsing commandline");
594                         usage();
595                         break;
596                 }
597
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");
602
603         if (optind>=argc)
604                 fatal("<start-server-command/stop-server-command> is a required argument");
605         opt_command_len=0;
606         for (i=optind;i<argc;i++)
607                 opt_command_len+=strlen(argv[i])+1;
608         opt_command=xmalloc(opt_command_len);
609         s=opt_command;
610         for (i=optind;i<argc;i++) {
611 size_t argv_i_len=strlen(argv[i]);
612
613                 if (s>opt_command)
614                         *s++=' ';
615                 memcpy(s,argv[i],argv_i_len);
616                 s+=argv_i_len;
617                 }
618         *s++=0;
619         assert(s==opt_command+opt_command_len);
620
621         if (!opt_syslog && !opt_stderr)
622                 opt_stderr=1;
623
624         if (opt_start)
625                 start();
626         if (opt_stop)
627                 stop();
628         assert(0);
629 }