0c5d89a7392b1ebac4f25773877a18273c79faa2
[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 enum lock_create_rc {
214         LOCK_CREATE_NO_LOCK_FILENAME,
215         LOCK_CREATE_FILE_NOT_FOUND,             /* only if: lock_mode&LOCK_NB */
216         LOCK_CREATE_ALREADY_LOCKED,             /* only if: lock_mode&LOCK_NB */
217         LOCK_CREATE_MYSELF_LOCKED,              /* only for: lock_create() */
218         LOCK_CREATE_MYSELF_LOCKED_AND_FRESH,    /* only for: lock_open_and_time_check() */
219         LOCK_CREATE_MYSELF_LOCKED_AND_STALE,    /* only for: lock_open_and_time_check() */
220         };
221 /* It will never create the lock file if: lock_mode&LOCK_NB */
222 static enum lock_create_rc lock_create(int lock_mode)
223 {
224 int retries=3;
225 sighandler_t sighandler_alrm_orig;
226 int flock_rc;
227
228         if (!opt_lock)
229                 return LOCK_CREATE_NO_LOCK_FILENAME;
230         /* Never drop the lock if the lock is already being held. */
231         if (lock_fd!=-1)
232                 retries=-1;
233 retry:
234         if (lock_fd==-1) {
235                 if (-1==(lock_fd=open(opt_lock,
236                                 O_RDWR | (lock_mode&LOCK_NB ? 0 : O_CREAT),
237                                 0600))) {
238                         if (errno==ENOENT && lock_mode&LOCK_NB)
239                                 return LOCK_CREATE_FILE_NOT_FOUND;
240                         fatal("Error creating lock file \"%s\": %m",opt_lock);
241                         }
242                 }
243         sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
244         alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
245         flock_rc=flock(lock_fd,lock_mode);
246         alarm(0);
247         signal(SIGALRM,sighandler_alrm_orig);
248         if (sighandler_flock_timeout_hit)
249                 fatal("Timeout locking lock file \"%s\": %m",opt_lock);
250         if (flock_rc) {
251                 if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
252                         return LOCK_CREATE_ALREADY_LOCKED;
253                 fatal("Error locking lock file \"%s\": %m",opt_lock);
254                 }
255         if (access(opt_lock,R_OK|W_OK)) {
256                 if (retries--<=0)
257                         fatal("Racing for the lock file \"%s\", giving up",opt_lock);
258                 if (close(lock_fd))
259                         fatal("Error closing lock file \"%s\": %m",opt_lock);
260                 lock_fd=-1;
261                 goto retry;
262                 }
263         return LOCK_CREATE_MYSELF_LOCKED;
264 }
265
266 static void lock_touch(void)
267 {
268         if (!opt_lock || lock_fd==-1)
269                 return;
270         if (utime(opt_lock,NULL))
271                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
272 }
273
274 static void lock_close(void)
275 {
276         if (lock_fd==-1 || !opt_lock)
277                 return;
278         /* It should not be needed but some stale locks were seen on:
279          *      White Box Linux kernel-smp-2.6.9-5.0.5.EL
280          */
281         if (flock(lock_fd,LOCK_UN|LOCK_NB))
282                 fatal("Error unlocking lock file \"%s\": %m",opt_lock);
283         if (close(lock_fd))
284                 fatal("Error closing lock file \"%s\": %m",opt_lock);
285         lock_fd=-1;
286 }
287
288 static void lock_close_atexit(void)
289 {
290         /* Prevent some crashes of malloc(3) etc. */
291         verror_quiet=1;
292         lock_close();
293 }
294
295 static int connect_try(void)
296 {
297 int fdtcp;
298 struct sockaddr_in sockaddr_tcp;
299
300         if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
301                 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
302         memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
303         sockaddr_tcp.sin_family=AF_INET;
304         sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
305         sockaddr_tcp.sin_port=htons(opt_port);
306         if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
307                 if (errno==ECONNREFUSED)
308                         return -1;
309                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
310                 }
311         return fdtcp;
312 }
313
314 static void session_transfer(
315                 const char *conn0_name,int conn0_fdin,int conn0_fdout,
316                 const char *conn1_name,int conn1_fdin,int conn1_fdout)
317                 G_GNUC_NORETURN;
318 static void session_transfer(
319                 const char *conn0_name,int conn0_fdin,int conn0_fdout,
320                 const char *conn1_name,int conn1_fdin,int conn1_fdout)
321 {
322 struct pollfd pollfdi[2];
323 int pollfdo[2];
324 const char *pollfdi_name[2];
325 int fdi,fdo;
326
327         pollfdi[0].fd=conn0_fdin;
328         pollfdi[0].events=POLLIN;
329         pollfdi[1].fd=conn1_fdin;
330         pollfdi[1].events=POLLIN;
331         pollfdo[0]=conn0_fdout;
332         pollfdo[1]=conn1_fdout;
333         pollfdi_name[0]=conn0_name;
334         pollfdi_name[1]=conn1_name;
335         for (;;) {
336                 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
337                         fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
338                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
339                         if (0
340                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
341                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
342                                         )
343                                 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
344                                                 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
345                                                 (unsigned)pollfdi[fdi].events);
346                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
347                         for (;;) {
348                                 ssize_t got;
349                                 char buf[SESSION_BUFFER_SIZE];
350
351                                 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
352                                         fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
353                                 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
354                                 if (got<0) {
355                                         if (errno==EAGAIN)
356                                                 break;
357                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
358                                         }
359                                 if (got==0) {
360                                         lock_close();
361                                         exit(EXIT_SUCCESS);
362                                         }
363                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
364                                         if (fdi==fdo)
365                                                 continue;
366                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
367                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
368                                                                 pollfdi_name[fdo]);
369                                         if (got!=write(pollfdi[fdo].fd,buf,got))
370                                                 fatal("write(%s socket,%ld): %m",
371                                                                 pollfdi_name[fdo],(long)got);
372                                         }
373                                 }
374                         }
375                 }
376 }
377
378 static void session_try(void)
379 {
380 int fdtcp;
381
382         if (-1==(fdtcp=connect_try()))
383                 return;
384         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
385 }
386
387 static int popen_pclose_checked(const char *command)
388 {
389 FILE *f;
390 char buf[SYSTEM_CHECKED_BUFFER_SIZE];
391 size_t got;
392
393         if (!(f=popen(command,"r")))
394                 fatal("Error opening spawned command \"%s\": %m",command);
395         setlinebuf(f);
396         while ((got=fread(buf,1,sizeof(buf)-1,f))) {
397 char *s,*s_next;
398
399                 assert(got<sizeof(buf));
400                 buf[got]=0;
401                 for (s=buf;*s;s=s_next) {
402                         if ((s_next=strchr(s,'\n'))) {
403                                 *s_next++=0;
404                                 error("Error line of spawned \"%s\": %s",command,s);
405                                 }
406                         else {
407                                 s_next=s+strlen(s);
408                                 error("Error line of spawned \"%s\" too long, string cut: %s",command,s);
409                                 }
410                         }
411                 }
412         if (ferror(f))
413                 fatal("Error reading output of spawned \"%s\"",command);
414         if (!feof(f))
415                 fatal("Error reaching end-of-file of messages of spawned \"%s\"",command);
416         return pclose(f);
417 }
418
419 static void system_checked(const char *command)
420 {
421 int rc;
422
423         if (!opt_ignore_spawned_command_output)
424                 rc=popen_pclose_checked(opt_stderr
425                                 ? command       /* differentiate ourself-dumped stdout from stderr */
426                                 : asprintf_checked("(%s) 2>&1",command)
427                                 );
428         else
429                 rc=system(opt_stderr
430                                 ? asprintf_checked("(%s) >&2",command)
431                                 : asprintf_checked("(%s) &>/dev/null",command)
432                                 );
433         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
434                 return;
435         if (WIFEXITED(rc))
436                 fatal("Error spawning command \"%s\": return code %d",
437                                 command,WEXITSTATUS(rc));
438 #ifdef WCOREDUMP
439         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
440                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)",
441                                 command,WTERMSIG(rc));
442 #endif /* WCOREDUMP */
443         if (WIFSIGNALED(rc))
444                 fatal("Error spawning command \"%s\": terminating signal %d",
445                                 command,WTERMSIG(rc));
446         if (WIFSTOPPED(rc))
447                 fatal("Error spawning command \"%s\": stopping signal %d",
448                                 command,WSTOPSIG(rc));
449 #ifdef WIFCONTINUED
450         if (WIFCONTINUED(rc))
451                 fatal("Error spawning command \"%s\": resumed by SIGCONT",
452                                 command);
453 #endif /* WIFCONTINUED */
454         fatal("Error spawning command \"%s\": unknown reason",
455                         command);
456 }
457
458 static void start(void) G_GNUC_NORETURN;
459 static void start(void)
460 {
461 int retry;
462
463         if (!opt_port)
464                 fatal("-p|--port is a required argument for -1|--start");
465         if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
466                 fatal("-i|--idle-server-timeout is a forbidden argument for -1|--start");
467
468         lock_create(LOCK_SH);
469         lock_touch();
470         session_try();
471         lock_close();
472
473         lock_create(LOCK_EX);
474         system_checked(opt_command);
475         lock_create(LOCK_SH);
476
477         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
478                 lock_touch();
479                 session_try();
480                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
481                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
482                 }
483         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
484                         opt_start_command_timeout,opt_port,opt_command);
485 }
486
487 static enum lock_create_rc lock_open_and_time_check(int lock_mode)
488 {
489 enum lock_create_rc rc;
490 struct stat statbuf;
491
492         if (LOCK_CREATE_MYSELF_LOCKED!=(rc=lock_create(lock_mode|LOCK_NB)))
493                 return rc;
494         if (lock_fd==-1 || fstat(lock_fd,&statbuf))
495                 fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
496         return (statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout
497                         ? LOCK_CREATE_MYSELF_LOCKED_AND_FRESH : LOCK_CREATE_MYSELF_LOCKED_AND_STALE);
498 }
499
500 static void lock_delete_and_close(void)
501 {
502         if (opt_lock && lock_fd!=-1)
503                 if (unlink(opt_lock))
504                         fatal("Error deleting no longer used lock file \"%s\": %m",opt_lock);
505         lock_close();
506 }
507
508 static void lock_open_and_return_if_stale(int lock_mode)
509 {
510         switch (lock_open_and_time_check(lock_mode)) {
511                 case LOCK_CREATE_NO_LOCK_FILENAME:        return;
512                 case LOCK_CREATE_FILE_NOT_FOUND:          exit(EXIT_SUCCESS);
513                 case LOCK_CREATE_ALREADY_LOCKED:          exit(EXIT_SUCCESS);
514                 case LOCK_CREATE_MYSELF_LOCKED:           assert(0);
515                 case LOCK_CREATE_MYSELF_LOCKED_AND_FRESH: exit(EXIT_SUCCESS);
516                 case LOCK_CREATE_MYSELF_LOCKED_AND_STALE: return;
517                 }
518         assert(0);
519 }
520
521 static void stop(void) G_GNUC_NORETURN;
522 static void stop(void)
523 {
524
525         /* Lock still being held! */
526         if (opt_port)
527                 fatal("-p|--port is a forbidden argument for -0|--stop");
528         if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
529                 fatal("-T|--start-command-timeout is a forbidden argument for -0|--stop");
530         if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
531                 fatal("-l|--lock is a required argument for -i|--idle-server-timeout of -1|--start");
532
533         lock_open_and_return_if_stale(LOCK_SH);
534         lock_close();
535
536         lock_open_and_return_if_stale(LOCK_EX);
537         system_checked(opt_command);
538         lock_delete_and_close();
539
540         exit(EXIT_SUCCESS);
541 }
542
543 int main(int argc,char **argv) G_GNUC_NORETURN;
544 int main(int argc,char **argv)
545 {
546 char optc;
547 size_t opt_command_len;
548 int i;
549 char *s;
550
551         if ((program_name=strrchr(argv[0],'/')))
552                 program_name++;
553         else
554                 program_name=argv[0];
555
556         atexit(lock_close_atexit);
557
558         optarg=NULL; optind=0;  /* FIXME: Possible portability problem. */
559         while ((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch (optc) {
560 long l;
561 char *endptr;
562
563                 case '1':       /* -1|--start */
564                         opt_start=1;
565                         break;
566
567                 case '0':       /* -0|--stop */
568                         opt_stop=1;
569                         break;
570
571                 case 'T':       /* -T|--start-command-timeout */
572                         l=strtol(optarg,&endptr,0);
573                         if (l<=0 || l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
574                                 fatal("Invalid -T|--start-command-timeout value: %s",optarg);
575                         opt_start_command_timeout=l;
576                         break;
577
578                 case 'i':       /* -i|--idle-server-timeout */
579                         l=strtol(optarg,&endptr,0);
580                         if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
581                                 fatal("Invalid -i|--idle-server-timeout value: %s",optarg);
582                         opt_idle_server_timeout=l;
583                         break;
584
585                 case 'S':       /* -S|--syslog */
586                         opt_syslog=1;
587                         break;
588
589                 case 'e':       /* -e|--stderr */
590                         opt_stderr=1;
591                         break;
592
593                 case 'l':       /* -l|--lock */
594                         opt_lock=optarg;
595                         break;
596
597                 case 'I':       /* -I|--ignore-spawned-command-output */
598                         opt_ignore_spawned_command_output=1;
599                         break;
600
601                 case 'p':       /* -p|--port */
602                         l=strtol(optarg,&endptr,0);
603                         if (l<=0 || l>=0x10000 || (endptr && *endptr))
604                                 fatal("Invalid -p|--port value: %s",optarg);
605                         opt_port=l;
606                         break;
607
608                 default:
609                         if (optc!='h')
610                                 fatal("Error parsing commandline");
611                         usage();
612                         break;
613                 }
614
615         if (!opt_start && !opt_stop)
616                 fatal("At least one of -1|--start or -0|--stop is required");
617         if ( opt_start &&  opt_stop)
618                 fatal("Both modes -1|--start and -0|--stop can never be specified simultaneously");
619
620         if (optind>=argc)
621                 fatal("<start-server-command/stop-server-command> is a required argument");
622         opt_command_len=0;
623         for (i=optind;i<argc;i++)
624                 opt_command_len+=strlen(argv[i])+1;
625         opt_command=xmalloc(opt_command_len);
626         s=opt_command;
627         for (i=optind;i<argc;i++) {
628 size_t argv_i_len=strlen(argv[i]);
629
630                 if (s>opt_command)
631                         *s++=' ';
632                 memcpy(s,argv[i],argv_i_len);
633                 s+=argv_i_len;
634                 }
635         *s++=0;
636         assert(s==opt_command+opt_command_len);
637
638         if (!opt_syslog && !opt_stderr)
639                 opt_stderr=1;
640
641         if (opt_start)
642                 start();
643         if (opt_stop)
644                 stop();
645         assert(0);
646 }