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