Some updates, still nothing interesting here.
[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
22
23 #define CONNECT_RETRY_MSEC 100
24
25 static long opt_start_timeout=60;
26 static int opt_port;
27 static int opt_syslog;
28 static int opt_stderr;
29 static const char *opt_lock;
30
31
32 /* /usr/include/glib-2.0/glib/gmacros.h */
33 #ifndef G_GNUC_PRINTF
34 #if     __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
35 #define G_GNUC_PRINTF( format_idx, arg_idx )    \
36   __attribute__((__format__ (__printf__, format_idx, arg_idx)))
37 #else   /* !__GNUC__ */
38 #define G_GNUC_PRINTF( format_idx, arg_idx )
39 #endif  /* !__GNUC__ */
40 #endif /* !G_GNUC_PRINTF */
41
42 /* /usr/include/glib-2.0/glib/gmacros.h */
43 #ifndef G_GNUC_NORETURN
44 #if     __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
45 #define G_GNUC_NORETURN                         \
46   __attribute__((__noreturn__))
47 #else   /* !__GNUC__ */
48 #define G_GNUC_NORETURN
49 #endif  /* !__GNUC__ */
50 #endif /* !G_GNUC_NORETURN */
51
52 /* /usr/include/glib-2.0/glib/gmacros.h */
53 /* Count the number of elements in an array. The array must be defined
54  * as such; using this with a dynamically allocated array will give
55  * incorrect results.
56  */
57 #define G_N_ELEMENTS(arr)               (sizeof (arr) / sizeof ((arr)[0]))
58
59
60 static const char *program_name;
61
62 static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
63 static void fatal(const char *fmt,...)
64 {
65 int use_syslog=opt_syslog,use_stderr=opt_stderr;
66 va_list ap;
67 char *string;
68 const char *const error_error="Error printing error message";
69
70         if (!use_syslog && !use_stderr)
71                 use_stderr=1;
72         va_start(ap,fmt);
73         if (-1==vasprintf(&string,fmt,ap)) {
74                 if (fmt==error_error)
75                         exit(EXIT_FAILURE);
76                 fatal(error_error);
77                 }
78         if (use_stderr)
79                 fprintf(stderr,"%s: %s!\n",program_name,string);
80         if (use_syslog) {
81                 openlog(program_name,LOG_PID,LOG_DAEMON);
82                 syslog(LOG_DAEMON|LOG_ERR,"%s!",string);
83                 closelog();
84                 }
85         va_end(ap);
86         exit(EXIT_FAILURE);
87 }
88
89 static void usage(void)
90 {
91         fprintf(stderr,"\
92 Syntax: %s [{-T|--start-timeout} <start-timeout] [-S|--syslog] [-e|--stderr]\n\
93         [{-l|--lock} <filename>]\n\
94         {-p|--port} <server-port> <start-server-command>\n\
95 \n\
96 Error messages out to stderr by default, -S|--syslog omits stderr output,\n\
97 both -S|--syslog and -e|--stderr output the errors by both methods.\n\
98 \n",program_name);
99         exit(EXIT_FAILURE);
100 }
101
102 static const struct option longopts[]={
103         {"start-timeout",1,0,'T'},
104         {"syslog"       ,0,0,'S'},
105         {"stderr"       ,0,0,'e'},
106         {"lock"         ,1,0,'l'},
107         {"port"         ,1,0,'p'},
108         {NULL           ,0,0,0  },
109         };
110
111 static int connect_try(void)
112 {
113 int fdtcp;
114 struct sockaddr_in sockaddr_tcp;
115
116         if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
117                 fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
118         memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
119         sockaddr_tcp.sin_family=AF_INET;
120         sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
121         sockaddr_tcp.sin_port=htons(opt_port);
122         if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
123                 if (errno==ECONNREFUSED)
124                         return -1;
125                 fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
126                 }
127         return fdtcp;
128 }
129
130 static void session_transfer(
131                 const char *conn0_name,int conn0_fdin,int conn0_fdout,
132                 const char *conn1_name,int conn1_fdin,int conn1_fdout)
133                 G_GNUC_NORETURN;
134 static void session_transfer(
135                 const char *conn0_name,int conn0_fdin,int conn0_fdout,
136                 const char *conn1_name,int conn1_fdin,int conn1_fdout)
137 {
138 struct pollfd pollfdi[2];
139 int pollfdo[2];
140 const char *pollfdi_name[2];
141 int fdi,fdo;
142
143         pollfdi[0].fd=conn0_fdin;
144         pollfdi[0].events=POLLIN;
145         pollfdi[1].fd=conn1_fdin;
146         pollfdi[1].events=POLLIN;
147         pollfdo[0]=conn0_fdout;
148         pollfdo[1]=conn1_fdout;
149         pollfdi_name[0]=conn0_name;
150         pollfdi_name[1]=conn1_name;
151         for (;;) {
152                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
153                         if (pollfdi[fdi].events)
154                                 break;
155                 if (fdi>=G_N_ELEMENTS(pollfdi))
156                         exit(EXIT_SUCCESS);
157                 if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
158                         fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
159                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
160                         if (0
161                                         ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
162                                         || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
163                                         )
164                                 fatal("poll(%s socket): revents=0x%X (events=0x%X)",
165                                                 pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
166                                                 (unsigned)pollfdi[fdi].events);
167                 for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
168                         for (;;) {
169                                 ssize_t got;
170                                 char buf[0x1000];
171
172                                 if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
173                                         fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
174                                 got=read(pollfdi[fdi].fd,buf,sizeof(buf));
175                                 if (got<0) {
176                                         if (errno==EAGAIN)
177                                                 break;
178                                         fatal("read(%s socket): %m",pollfdi_name[fdi]);
179                                         }
180                                 if (got==0) {
181                                         pollfdi[fdi].events&=~POLLIN;
182                                         break;
183                                         }
184                                 for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
185                                         if (fdi==fdo)
186                                                 continue;
187                                         if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
188                                                 fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
189                                                                 pollfdi_name[fdo]);
190                                         if (got!=write(pollfdi[fdo].fd,buf,got))
191                                                 fatal("write(%s socket,%ld): %m",
192                                                                 pollfdi_name[fdo],(long)got);
193                                         }
194                                 }
195                         }
196                 }
197 }
198
199 static void session_try(void)
200 {
201 int fdtcp;
202
203         if (-1==(fdtcp=connect_try()))
204                 return;
205         session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
206 }
207
208 static void system_checked(const char *command)
209 {
210 int rc;
211
212         rc=system(command);
213         if (WIFEXITED(rc) && !WEXITSTATUS(rc))
214                 return;
215         if (WIFEXITED(rc))
216                 fatal("Error spawning command \"%s\": return code %d!",
217                                 command,WEXITSTATUS(rc));
218 #ifdef WCOREDUMP
219         if (WIFSIGNALED(rc) && WCOREDUMP(rc))
220                 fatal("Error spawning command \"%s\": dumped core (terminating signal %d)!",
221                                 command,WTERMSIG(rc));
222 #endif /* WCOREDUMP */
223         if (WIFSIGNALED(rc))
224                 fatal("Error spawning command \"%s\": terminating signal %d!",
225                                 command,WTERMSIG(rc));
226         if (WIFSTOPPED(rc))
227                 fatal("Error spawning command \"%s\": stopping signal %d!",
228                                 command,WSTOPSIG(rc));
229 #ifdef WIFCONTINUED
230         if (WIFCONTINUED(rc))
231                 fatal("Error spawning command \"%s\": resumed by SIGCONT!",
232                                 command);
233 #endif /* WIFCONTINUED */
234         fatal("Error spawning command \"%s\": unknown reason!",
235                         command);
236 }
237
238 static int lock_fd=-1;
239
240 static void lock_create(void)
241 {
242 int retries=3;
243
244         if (!opt_lock)
245                 return;
246 retry:
247         if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
248                 fatal("Error creating lock file \"%s\": %m",opt_lock);
249         if (flock(lock_fd,LOCK_EX))
250                 fatal("Error locking lock file \"%s\": %m",opt_lock);
251         if (!access(opt_lock,O_RD|O_WR)) {
252                 if (retries--<=0)
253                         fatal("Racing for the lock file \"%s\", giving up");
254                 if (close(lock_fd))
255                         fatal("Error closing lock file \"%s\": %m",opt_lock);
256                 lock_fd=-1;
257                 goto retry;
258                 }
259         if (utime(opt_lock,NULL))
260                 fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
261 }
262
263 static void lock_close(void)
264 {
265         if (lock_fd==-1)
266                 return;
267         if (close(lock_fd))
268                 fatal("Error closing lock file \"%s\": %m",opt_lock);
269         lock_fd=-1;
270 }
271
272 int main(int argc,char **argv)
273 {
274 int retry;
275 const char *command;
276 char optc;
277
278         if ((program_name=strrchr(argv[0],'/')))
279                 program_name++;
280         else
281                 program_name=argv[0];
282
283         optarg=NULL; optind=0;  /* FIXME: Possible portability problem. */
284         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) {
285 long l;
286 char *endptr;
287
288                 case 'T':       /* -T|--start-timeout */
289                         l=strtol(optarg,&endptr,0);
290                         if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
291                                 fatal("Invalid -T|--start-timeout values: %s",optarg);
292                         opt_start_timeout=l;
293                         break;
294
295                 case 'S':       /* -S|--syslog */
296                         opt_syslog=1;
297                         break;
298
299                 case 'e':       /* -e|--stderr */
300                         opt_stderr=1;
301                         break;
302
303                 case 'l':       /* -l|--lock */
304                         opt_lock=optarg;
305
306                 case 'p':       /* -p|--port */
307                         l=strtol(optarg,&endptr,0);
308                         if (l<=0 || l>=0x10000 || (endptr && *endptr))
309                                 fatal("Invalid -p|--port value: %s",optarg);
310                         opt_port=l;
311                         break;
312
313                 default:
314                         if (optc!='h')
315                                 fatal("Error parsing commandline!");
316                         usage();
317                         break;
318                 }
319
320         if (!opt_port)
321                 fatal("-p|--port is a required argument!");
322         if (optind>=argc)
323                 fatal("<start-server-command> is a required argument!");
324         if (optind+1<argc)
325                 fatal("Too many arguments, <start-server-command> needs quoting?");
326         command=argv[optind];
327
328         lock_create();
329         session_try();
330         system_checked(command);
331
332         for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_timeout;retry++) {
333                 session_try();
334                 if (poll(NULL,0,CONNECT_RETRY_MSEC))
335                         fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
336                 }
337         fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
338                         opt_start_timeout,opt_port,command);
339 }