Untested draft of xinetd(8) interface for on-demand standalone servers.
authorshort <>
Mon, 8 Aug 2005 08:34:21 +0000 (08:34 +0000)
committershort <>
Mon, 8 Aug 2005 08:34:21 +0000 (08:34 +0000)
inetdmx.c [new file with mode: 0644]

diff --git a/inetdmx.c b/inetdmx.c
new file mode 100644 (file)
index 0000000..e3c4d96
--- /dev/null
+++ b/inetdmx.c
@@ -0,0 +1,290 @@
+/* $Id$ */
+
+
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+
+#define CONNECT_RETRY_MSEC 100
+
+static long opt_start_timeout=60;
+static int opt_port;
+static int opt_syslog;
+static int opt_stderr;
+
+
+/* /usr/include/glib-2.0/glib/gmacros.h */
+#ifndef G_GNUC_PRINTF
+#if     __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
+#define G_GNUC_PRINTF( format_idx, arg_idx )    \
+  __attribute__((__format__ (__printf__, format_idx, arg_idx)))
+#else   /* !__GNUC__ */
+#define G_GNUC_PRINTF( format_idx, arg_idx )
+#endif  /* !__GNUC__ */
+#endif /* !G_GNUC_PRINTF */
+
+/* /usr/include/glib-2.0/glib/gmacros.h */
+#ifndef G_GNUC_NORETURN
+#if     __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
+#define G_GNUC_NORETURN                         \
+  __attribute__((__noreturn__))
+#else   /* !__GNUC__ */
+#define G_GNUC_NORETURN
+#endif  /* !__GNUC__ */
+#endif /* !G_GNUC_NORETURN */
+
+/* /usr/include/glib-2.0/glib/gmacros.h */
+/* Count the number of elements in an array. The array must be defined
+ * as such; using this with a dynamically allocated array will give
+ * incorrect results.
+ */
+#define G_N_ELEMENTS(arr)              (sizeof (arr) / sizeof ((arr)[0]))
+
+
+static const char *program_name;
+
+static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;
+static void fatal(const char *fmt,...)
+{
+int use_syslog=opt_syslog,use_stderr=opt_stderr;
+va_list ap;
+char *string;
+const char *const error_error="Error printing error message";
+
+       if (!use_syslog && !use_stderr)
+               use_stderr=1;
+       va_start(ap,fmt);
+       if (-1==vasprintf(&string,fmt,ap)) {
+               if (fmt==error_error)
+                       exit(EXIT_FAILURE);
+               fatal(error_error);
+               }
+       if (use_stderr)
+               fprintf(stderr,"%s: %s!\n",program_name,string);
+       if (use_syslog) {
+               openlog(program_name,LOG_PID,LOG_DAEMON);
+               syslog(LOG_DAEMON|LOG_ERR,"%s!",string);
+               closelog();
+               }
+       va_end(ap);
+       exit(EXIT_FAILURE);
+}
+
+static void usage(void)
+{
+       fprintf(stderr,"\
+Syntax: %s [{-T|--start-timeout} <start-timeout] [-S|--syslog] [-e|--stderr]\n\
+        {-p|--port} <server-port> <start-server-command>\n\
+\n\
+Error messages out to stderr by default, -S|--syslog omits stderr output,\n\
+both -S|--syslog and -e|--stderr output the errors by both methods.\n\
+\n",program_name);
+       exit(EXIT_FAILURE);
+}
+
+static const struct option longopts[]={
+       {"start-timeout",1,0,'T'},
+       {"syslog"       ,0,0,'S'},
+       {"stderr"       ,0,0,'e'},
+       {"port"         ,1,0,'p'},
+       {NULL           ,0,0,0  },
+       };
+
+static int connect_try(void)
+{
+int fdtcp;
+struct sockaddr_in sockaddr_tcp;
+
+       if (-1==(fdtcp=socket(PF_INET,SOCK_STREAM,0)))
+               fatal("socket(PF_INET,SOCK_STREAM,0)=%d: %m",fdtcp);
+       memset(&sockaddr_tcp,0,sizeof(sockaddr_tcp));
+       sockaddr_tcp.sin_family=AF_INET;
+       sockaddr_tcp.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
+       sockaddr_tcp.sin_port=htons(opt_port);
+       if (connect(fdtcp,(const struct sockaddr *)&sockaddr_tcp,sizeof(sockaddr_tcp))) {
+               if (errno==ECONNREFUSED)
+                       return -1;
+               fatal("connect(TCP socket,127.0.0.1:%d): %m",opt_port);
+               }
+       return fdtcp;
+}
+
+static void session_transfer(
+               const char *conn0_name,int conn0_fdin,int conn0_fdout,
+               const char *conn1_name,int conn1_fdin,int conn1_fdout)
+               G_GNUC_NORETURN;
+static void session_transfer(
+               const char *conn0_name,int conn0_fdin,int conn0_fdout,
+               const char *conn1_name,int conn1_fdin,int conn1_fdout)
+{
+struct pollfd pollfdi[2];
+int pollfdo[2];
+const char *pollfdi_name[2];
+int fdi,fdo;
+
+       pollfdi[0].fd=conn0_fdin;
+       pollfdi[0].events=POLLIN;
+       pollfdi[1].fd=conn1_fdin;
+       pollfdi[1].events=POLLIN;
+       pollfdo[0]=conn0_fdout;
+       pollfdo[1]=conn1_fdout;
+       pollfdi_name[0]=conn0_name;
+       pollfdi_name[1]=conn1_name;
+       for (;;) {
+               for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
+                       if (pollfdi[fdi].events)
+                               break;
+               if (fdi>=G_N_ELEMENTS(pollfdi))
+                       exit(EXIT_SUCCESS);
+               if (0>=poll(pollfdi,G_N_ELEMENTS(pollfdi),-1))
+                       fatal("poll(%s socket,%s socket): %m",pollfdi_name[0],pollfdi_name[1]);
+               for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++)
+                       if (0
+                                       ||   pollfdi[fdi].revents & (POLLERR|POLLHUP|POLLNVAL)
+                                       || ((pollfdi[fdi].revents & POLLIN) && !(pollfdi[fdi].events & POLLIN))
+                                       )
+                               fatal("poll(%s socket): revents=0x%X (events=0x%X)",
+                                               pollfdi_name[fdi],(unsigned)pollfdi[fdi].revents,
+                                               (unsigned)pollfdi[fdi].events);
+               for (fdi=0;fdi<G_N_ELEMENTS(pollfdi);fdi++) {
+                       for (;;) {
+                               ssize_t got;
+                               char buf[0x1000];
+
+                               if (fcntl(pollfdi[fdi].fd,F_SETFL,O_NONBLOCK))
+                                       fatal("fcntl(%s socket,F_SETFL,O_NONBLOCK): %m",pollfdi_name[fdi]);
+                               got=read(pollfdi[fdi].fd,buf,sizeof(buf));
+                               if (got<0) {
+                                       if (errno==EAGAIN)
+                                               break;
+                                       fatal("read(%s socket): %m",pollfdi_name[fdi]);
+                                       }
+                               if (got==0) {
+                                       pollfdi[fdi].events&=~POLLIN;
+                                       break;
+                                       }
+                               for (fdo=0;fdo<G_N_ELEMENTS(pollfdi);fdo++) {
+                                       if (fdi==fdo)
+                                               continue;
+                                       if (fcntl(pollfdi[fdo].fd,F_SETFL,0 /* !O_NONBLOCK */))
+                                               fatal("fcntl(%s socket,F_SETFL,0 /* !O_NONBLOCK */): %m",
+                                                               pollfdi_name[fdo]);
+                                       if (got!=write(pollfdi[fdo].fd,buf,got))
+                                               fatal("write(%s socket,%ld): %m",
+                                                               pollfdi_name[fdo],(long)got);
+                                       }
+                               }
+                       }
+               }
+}
+
+static void session_try(void)
+{
+int fdtcp;
+
+       if (-1==(fdtcp=connect_try()))
+               return;
+       session_transfer("remote",STDIN_FILENO,STDOUT_FILENO,"local",fdtcp,fdtcp);
+}
+
+static void system_checked(const char *command)
+{
+int rc;
+
+       rc=system(command);
+       if (WIFEXITED(rc) && !WEXITSTATUS(rc))
+               return;
+       if (WIFEXITED(rc))
+               fatal("Error spawning command \"%s\": return code %d!",
+                               command,WEXITSTATUS(rc));
+#ifdef WCOREDUMP
+       if (WIFSIGNALED(rc) && WCOREDUMP(rc))
+               fatal("Error spawning command \"%s\": dumped core (terminating signal %d)!",
+                               command,WTERMSIG(rc));
+#endif /* WCOREDUMP */
+       if (WIFSIGNALED(rc))
+               fatal("Error spawning command \"%s\": terminating signal %d!",
+                               command,WTERMSIG(rc));
+       if (WIFSTOPPED(rc))
+               fatal("Error spawning command \"%s\": stopping signal %d!",
+                               command,WSTOPSIG(rc));
+#ifdef WIFCONTINUED
+       if (WIFCONTINUED(rc))
+               fatal("Error spawning command \"%s\": resumed by SIGCONT!",
+                               command);
+#endif /* WIFCONTINUED */
+       fatal("Error spawning command \"%s\": unknown reason!",
+                       command);
+}
+
+int main(int argc,char **argv)
+{
+int retry;
+const char *command;
+char optc;
+
+       if ((program_name=strrchr(argv[0],'/')))
+               program_name++;
+       else
+               program_name=argv[0];
+
+       optarg=NULL; optind=0;  /* FIXME: Possible portability problem. */
+       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) {
+long l;
+char *endptr;
+
+               case 'T':       /* -T|--start-timeout */
+                       l=strtol(optarg,&endptr,0);
+                       if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
+                               fatal("Invalid -T|--start-timeout values: %s",optarg);
+                       opt_start_timeout=l;
+                       break;
+
+               case 'p':       /* -p|--port */
+                       l=strtol(optarg,&endptr,0);
+                       if (l<=0 || l>=0x10000 || (endptr && *endptr))
+                               fatal("Invalid -p|--port value: %s",optarg);
+                       opt_port=l;
+                       break;
+
+               default:
+                       if (optc!='h')
+                               fatal("Error parsing commandline!");
+                       usage();
+                       break;
+               }
+
+       if (!opt_port)
+               fatal("-p|--port is a required argument!");
+       if (optind>=argc)
+               fatal("<start-server-command> is a required argument!");
+       if (optind+1<argc)
+               fatal("Too many arguments, <start-server-command> needs quoting?");
+       command=argv[optind];
+
+       session_try();
+       system_checked(command);
+
+       for (retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_timeout;retry++) {
+               session_try();
+               if (poll(NULL,0,CONNECT_RETRY_MSEC))
+                       fatal("poll(timeout %dmsec): %m",CONNECT_RETRY_MSEC);
+               }
+       fatal("Timed out after %ld seconds connecting to port %d after spawned: %s",
+                       opt_start_timeout,opt_port,command);
+}