First final but buggy code.
[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
33
34 /* /usr/include/glib-2.0/glib/gmacros.h */
35 #ifndef G_GNUC_PRINTF
36 #if     __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
37 #define G_GNUC_PRINTF( format_idx, arg_idx )    \
38   __attribute__((__format__ (__printf__, format_idx, arg_idx)))
39 #else   /* !__GNUC__ */
40 #define G_GNUC_PRINTF( format_idx, arg_idx )
41 #endif  /* !__GNUC__ */
42 #endif /* !G_GNUC_PRINTF */
43
44 /* /usr/include/glib-2.0/glib/gmacros.h */
45 #ifndef G_GNUC_NORETURN
46 #if     __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
47 #define G_GNUC_NORETURN                         \
48   __attribute__((__noreturn__))
49 #else   /* !__GNUC__ */
50 #define G_GNUC_NORETURN
51 #endif  /* !__GNUC__ */
52 #endif /* !G_GNUC_NORETURN */
53
54 /* /usr/include/glib-2.0/glib/gmacros.h */
55 /* Count the number of elements in an array. The array must be defined
56  * as such; using this with a dynamically allocated array will give
57  * incorrect results.
58  */
59 #define G_N_ELEMENTS(arr)               (sizeof (arr) / sizeof ((arr)[0]))
60
61
62 static const char *program_name;
63
64 static int opt_start;
65 static int opt_stop;
66 static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
67 static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
68 static int opt_port;
69 static int opt_syslog;
70 static int opt_stderr;
71 static const char *opt_lock;
72 static const char *opt_command;
73
74 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
75 static void fatal(const char *fmt,...)
76 {
77 int use_syslog=opt_syslog,use_stderr=opt_stderr;
78 va_list ap;
79 char *string;
80 const char *const error_error="Error printing error message";
81
82         if (!use_syslog && !use_stderr)
83                 use_stderr=1;
84         va_start(ap,fmt);
85         if (-1==vasprintf(&string,fmt,ap)) {
86                 if (fmt==error_error)
87                         exit(EXIT_FAILURE);
88                 fatal(error_error);
89                 }
90         if (use_stderr)
91                 fprintf(stderr,"%s: %s!\n",program_name,string);
92         if (use_syslog) {
93                 openlog(program_name,LOG_PID,LOG_DAEMON);
94                 syslog(LOG_DAEMON|LOG_ERR,"%s!",string);
95                 closelog();
96                 }
97         va_end(ap);
98         exit(EXIT_FAILURE);
99 }
100
101 static void usage(void)
102 {
103         fprintf(stderr,"\
104 Syntax: %s {-1|--start} [{-T|--start-command-timeout} <start-command-timeout>]\n\
105         \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
106         \t{-p|--port} <server-port> <start-server-command>\n\
107      or %s {-0|--stop}  [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
108         \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
109         \t<stop-server-command>\n\
110 \n\
111 Error messages are printed to stderr by default,\n\
112 -S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
113 the errors by both methods.\n\
114 \n",program_name,program_name);
115         exit(EXIT_FAILURE);
116 }
117
118 static const struct option longopts[]={
119         {"start"                ,0,0,'1'},
120         {"stop"                 ,0,0,'0'},
121         {"start-command-timeout",1,0,'T'},
122         {"idle-server-timeout"  ,1,0,'i'},
123         {"syslog"               ,0,0,'S'},
124         {"stderr"               ,0,0,'e'},
125         {"lock"                 ,1,0,'l'},
126         {"port"                 ,1,0,'p'},
127         {NULL                   ,0,0,0  },
128         };
129
130 static int lock_fd=-1;
131
132 static int sighandler_flock_timeout_hit;
133 static void sighandler_flock_timeout(int signo)
134 {
135         sighandler_flock_timeout_hit=1;
136 }
137
138 static int lock_create(int lock_mode)
139 {
140 int retries=3;
141 sighandler_t sighandler_alrm_orig;
142 int flock_rc;
143
144         if (!opt_lock)
145                 return 1;
146         /* Never drop the lock if the lock is already being held. */
147         if (lock_fd!=-1)
148                 retries=-1;
149 retry:
150         if (lock_fd==-1) {
151                 if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
152                         fatal("Error creating lock file \"%s\": %m",opt_lock);
153                 }
154         sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
155         alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
156         flock_rc=flock(lock_fd,lock_mode);
157         alarm(0);
158         signal(SIGALRM,sighandler_alrm_orig);
159         if (sighandler_flock_timeout_hit)
160                 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
161         if (flock_rc) {
162                 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
163                         return 0;
164                 fatal("Error locking lock file \"%s\": %m",opt_lock);
165                 }
166         if (!access(opt_lock,R_OK|W_OK)) {
167                 if (retries--<=0)
168                         fatal("Racing for the lock file \"%s\", giving up",opt_lock);
169                 if (close(lock_fd))
170                         fatal("Error closing lock file \"%s\": %m",opt_lock);
171                 lock_fd=-1;
172                 goto retry;
173                 }
174         return 1;
175 }
176
177 static void lock_touch(void)
178 {
179         if (!opt_lock || lock_fd==-1)
180                 return;
181         if (utime(opt_lock,NULL))
182                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
183 }
184
185 static void lock_close(void)
186 {
187         if (lock_fd==-1)
188                 return;
189         if (close(lock_fd))
190                 fatal("Error closing lock file \"%s\": %m",opt_lock);
191         lock_fd=-1;
192 }
193
194 static int connect_try(void)
195 {
196 int fdtcp;
197 struct sockaddr_in sockaddr_tcp;
198
199         if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
200                 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
201         memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
202         sockaddr_tcp.sin_family=AF_INET;
203         sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
204         sockaddr_tcp.sin_port=htons(opt_port);
205         if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
206                 if (errno==ECONNREFUSED)
207                         return -1;
208                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
209                 }
210         return fdtcp;
211 }
212
213 static void session_transfer(
214                 const char *conn0_name,int conn0_fdin,int conn0_fdout,
215                 const char *conn1_name,int conn1_fdin,int conn1_fdout)
216                 G_GNUC_NORETURN;
217 static void session_transfer(
218                 const char *conn0_name,int conn0_fdin,int conn0_fdout,
219                 const char *conn1_name,int conn1_fdin,int conn1_fdout)
220 {
221 struct pollfd pollfdi[2];
222 int pollfdo[2];
223 const char *pollfdi_name[2];
224 int fdi,fdo;
225
226         pollfdi[0].fd=conn0_fdin;
227         pollfdi[0].events=POLLIN;
228         pollfdi[1].fd=conn1_fdin;
229         pollfdi[1].events=POLLIN;
230         pollfdo[0]=conn0_fdout;
231         pollfdo[1]=conn1_fdout;
232         pollfdi_name[0]=conn0_name;
233         pollfdi_name[1]=conn1_name;
234         for (;;) {
235                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
236                         if (pollfdi[fdi].events)
237                                 break;
238                 if (fdi>=G_N_ELEMENTS(pollfdi)) {
239                         lock_close();
240                         exit(EXIT_SUCCESS);
241                         }
242                 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
243                         fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
244                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
245                         if (0
246                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
247                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
248                                         )
249                                 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
250                                                 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
251                                                 (unsigned)pollfdi[fdi].events);
252                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
253                         for (;;) {
254                                 ssize_t got;
255                                 char buf[SESSION_BUFFER_SIZE];
256
257                                 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
258                                         fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
259                                 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
260                                 if (got<0) {
261                                         if (errno==EAGAIN)
262                                                 break;
263                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
264                                         }
265                                 if (got==0) {
266                                         pollfdi[fdi].events&=~POLLIN;
267                                         break;
268                                         }
269                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
270                                         if (fdi==fdo)
271                                                 continue;
272                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
273                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
274                                                                 pollfdi_name[fdo]);
275                                         if (got!=write(pollfdi[fdo].fd,buf,got))
276                                                 fatal("write(%s socket,%ld): %m",
277                                                                 pollfdi_name[fdo],(long)got);
278                                         }
279                                 }
280                         }
281                 }
282 }
283
284 static void session_try(void)
285 {
286 int fdtcp;
287
288         if (-1==(fdtcp=connect_try()))
289                 return;
290         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
291 }
292
293 static void system_checked(const char *command)
294 {
295 int rc;
296
297         rc=system(command);
298         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
299                 return;
300         if (WIFEXITED(rc))
301                 fatal("Error spawning command \"%s\": return code %d",
302                                 command,WEXITSTATUS(rc));
303 #ifdef WCOREDUMP
304         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
305                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
306                                 command,WTERMSIG(rc));
307 #endif /* WCOREDUMP */
308         if (WIFSIGNALED(rc))
309                 fatal("Error spawning command \"%s\": terminating signal %d",
310                                 command,WTERMSIG(rc));
311         if (WIFSTOPPED(rc))
312                 fatal("Error spawning command \"%s\": stopping signal %d",
313                                 command,WSTOPSIG(rc));
314 #ifdef WIFCONTINUED
315         if (WIFCONTINUED(rc))
316                 fatal("Error spawning command \"%s\": resumed by SIGCONT",
317                                 command);
318 #endif /* WIFCONTINUED */
319         fatal("Error spawning command \"%s\": unknown reason",
320                         command);
321 }
322
323 static void start(void) G_GNUC_NORETURN;
324 static void start(void)
325 {
326 int retry;
327
328         if (!opt_port)
329                 fatal("-p|--port is a required argument for -1|--start");
330         if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
331                 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
332
333         lock_create(LOCK_SH);
334         lock_touch();
335         session_try();
336         lock_close();
337
338         lock_create(LOCK_EX);
339         system_checked(opt_command);
340         lock_create(LOCK_SH);
341
342         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
343                 lock_touch();
344                 session_try();
345                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
346                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
347                 }
348         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
349                         opt_start_command_timeout,opt_port,opt_command);
350 }
351
352 /* Returns: Is fresh? */
353 static int lock_create_and_time_check(int lock_mode)
354 {
355 struct stat statbuf;
356
357         if (!opt_lock)
358                 return 1;
359
360         if (!lock_create(lock_mode|LOCK_NB))
361                 exit(EXIT_SUCCESS);
362         if (lock_fd==-1 || !fstat(lock_fd,&statbuf))
363                 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
364         return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
365 }
366
367 static void lock_delete_and_close(void)
368 {
369         /* Should not happen. */
370         if (!opt_lock || lock_fd==-1)
371                 return;
372         if (unlink(opt_lock))
373                 fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
374         lock_close();
375 }
376
377 static void stop(void) G_GNUC_NORETURN;
378 static void stop(void)
379 {
380 int is_fresh;
381
382         /* Lock still being held! */
383         if (opt_port)
384                 fatal("-p|--port is a forbidden argument for -0|--stop");
385         if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
386                 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
387         if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
388                 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
389
390         is_fresh=lock_create_and_time_check(LOCK_SH);
391         lock_close();
392         if (is_fresh)
393                 exit(EXIT_SUCCESS);
394
395         lock_create_and_time_check(LOCK_EX);
396         system_checked(opt_command);
397         lock_delete_and_close();
398
399         exit(EXIT_SUCCESS);
400 }
401
402 int main(int argc,char **argv) G_GNUC_NORETURN;
403 int main(int argc,char **argv)
404 {
405 char optc;
406
407         if ((program_name=strrchr(argv[0],'/')))
408                 program_name++;
409         else
410                 program_name=argv[0];
411
412         optarg=NULL; optind=0;  /* FIXME: Possible portability problem. */
413         while ((optc=getopt_long(argc,argv,"c:d:L:l:b:xCM:P:s:m:r:t:T:w:fvhV",longopts,NULL))!=EOF) switch (optc) {
414 long l;
415 char *endptr;
416
417                 case '1':       /* -1|--start */
418                         opt_start=1;
419                         break;
420
421                 case '0':       /* -0|--stop */
422                         opt_stop=1;
423                         break;
424
425                 case 'T':       /* -T|--start-command-timeout */
426                         l=strtol(optarg,&endptr,0);
427                         if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
428                                 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
429                         opt_start_command_timeout=l;
430                         break;
431
432                 case 'i':       /* -i|--idle-server-timeout */
433                         l=strtol(optarg,&endptr,0);
434                         if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
435                                 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
436                         opt_idle_server_timeout=l;
437                         break;
438
439                 case 'S':       /* -S|--syslog */
440                         opt_syslog=1;
441                         break;
442
443                 case 'e':       /* -e|--stderr */
444                         opt_stderr=1;
445                         break;
446
447                 case 'l':       /* -l|--lock */
448                         opt_lock=optarg;
449
450                 case 'p':       /* -p|--port */
451                         l=strtol(optarg,&endptr,0);
452                         if (l<=0 || l>=0x10000 || (endptr && *endptr))
453                                 fatal("Invalid -p|--port value: %s",optarg);
454                         opt_port=l;
455                         break;
456
457                 default:
458                         if (optc!='h')
459                                 fatal("Error parsing commandline");
460                         usage();
461                         break;
462                 }
463
464         if (!opt_start && !opt_stop)
465                 fatal("At least one of -1|--opt-start or -0|--opt-stop is required");
466         if ( opt_start &&  opt_stop)
467                 fatal("Both modes -1|--opt-start and -0|--opt-stop can never be specified simultaneously");
468
469         if (optind>=argc)
470                 fatal("<start-server-command/stop-server-command> is a required argument");
471         if (optind+1<argc)
472                 fatal("Too many arguments, <start-server-command/stop-server-command> may need quoting");
473         opt_command=argv[optind];
474
475         if (opt_start)
476                 start();
477         if (opt_stop)
478                 stop();
479         assert(0);
480 }