Hi,

standalone running and only occasionally used server for testing is a bit
costly for me and as suggested by Nick Kew its inetd-spawning each time during
a testing session would be possibly even worse than the former case.

Just FYI attached 'caching' method by xinetd(8) handled connections forwarding
with on-demand server spawning and crond(8) handled idle server shutdowns.
(C used to avoid for this trivia task as any scripting language interpreters
startup is not wised in possibly server overloaded conditions.)


Regards,
Lace


On Fri, 05 Aug 2005 17:20:10 +0900, Nick Kew wrote:
...
> Apache's high startup cost is self-reinforcing.  We know it's a once-only
> thing,
...
On Fri, 05 Aug 2005 17:39:02 +0900, Jan Kratochvil wrote:
...
> External people with mobile devices should be able to use 'production' or
> 'testing' server. The 'testing' server will not be used in common case, 
> though.
> Machine is unfortunately low on memory and currently this unused 'testing'
> server eats 25844KB of memory (free(1),kill(1),free(1),subtract w/o buffers,
> running minimal set - 1 listen httpd, 2 children httpd; mod_perl in use).
...
service httpd-stage-perl-xinetd
{
        disable = no
        port                    = 82
        type                    = UNLISTED
        socket_type             = stream
        protocol                = tcp
        wait                    = no
        user                    = root
        server                  = /root/src/inetdmx
        server_args             = --start --start-command-timeout 60 --lock 
/var/lock/httpd-stage-perl-inetdmx.lock --syslog --port 83 
/etc/init.d/httpd-stage-perl start;true
}
#! /bin/bash
# See also: /etc/xinetd.d/httpd-stage-perl-xinetd
exec /root/src/inetdmx --stop \
                --idle-server-timeout $[30*60] \
                --lock /var/lock/httpd-stage-perl-inetdmx.lock \
                --syslog \
                /etc/init.d/httpd-stage-perl stop
/*
 * $Id: inetdmx.c,v 1.7 2005/08/09 11:08:12 short Exp $ */
 * Latest:
 *      
http://cvs.jankratochvil.net/viewcvs/*checkout*/nethome/src/inetdmx.c?rev=HEAD
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; you must use version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#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>
#include <sys/file.h>
#include <utime.h>
#include <time.h>
#include <assert.h>


#define CONNECT_RETRY_MSEC 100
#define DEFAULT_START_COMMAND_TIMEOUT 60
#define DEFAULT_IDLE_SERVER_TIMEOUT (90*60)
#define FLOCK_TIMEOUT_OVER_START_TIMEOUT 2
#define SESSION_BUFFER_SIZE 0x1000
#define SYSTEM_CHECKED_BUFFER_SIZE_MIN 0x1000
#define SYSTEM_CHECKED_BUFFER_SIZE (LINE_MAX > SYSTEM_CHECKED_BUFFER_SIZE_MIN ? 
LINE_MAX : SYSTEM_CHECKED_BUFFER_SIZE_MIN)


/* /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 int opt_start;
static int opt_stop;
static long opt_start_command_timeout=DEFAULT_START_COMMAND_TIMEOUT;
static long opt_idle_server_timeout=DEFAULT_IDLE_SERVER_TIMEOUT;
static int opt_port;
static int opt_syslog;
static int opt_stderr;
static const char *opt_lock;
static int opt_ignore_spawned_command_output;
static char *opt_command;


static void fatal(const char *fmt,...) G_GNUC_PRINTF(1,2) G_GNUC_NORETURN;

/* for atexit(3) function */
static int verror_quiet;

static void verror(const char *fmt,va_list ap) G_GNUC_PRINTF(1,0);
static void verror(const char *fmt,va_list ap)
{
int use_syslog=opt_syslog,use_stderr=opt_stderr;
char *string;
const char *const double_error="Error printing error message";

        if (verror_quiet)
                return;
        if (!use_syslog && !use_stderr)
                use_stderr=1;
        if (-1==vasprintf(&string,fmt,ap)) {
                if (fmt==double_error)
                        exit(EXIT_FAILURE);
                fatal(double_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();
                }
}

static void error(const char *fmt,...) G_GNUC_PRINTF(1,2);
static void error(const char *fmt,...)
{
va_list ap;

        va_start(ap,fmt);
        verror(fmt,ap);
        va_end(ap);
}

static void fatal(const char *fmt,...)
{
va_list ap;

        va_start(ap,fmt);
        verror(fmt,ap);
        va_end(ap);
        exit(EXIT_FAILURE);
}

static char *asprintf_checked(const char *fmt,...) G_GNUC_PRINTF(1,2);
static char *asprintf_checked(const char *fmt,...)
{
char *string;
va_list ap;
int rc;

        va_start(ap,fmt);
        rc=vasprintf(&string,fmt,ap);
        va_end(ap);
        if (rc==-1)
                fatal("Error formatting string using formatstring: %s",fmt);
        return string;
}

static void *xmalloc(size_t size)
{
void *r;

        if ((r=malloc(size)))
                return r;
        fatal("Error allocing %lu bytes",(unsigned long)size);
}

static void usage(void)
{
        fprintf(stderr,"\
Syntax: %s {-1|--start} [{-T|--start-command-timeout} 
<start-command-timeout>]\n\
        \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
        \t\[-I|--ignore-spawned-command-output]\n\
        \t{-p|--port} <server-port> <start-server-command>\n\
     or %s {-0|--stop}  [{-i|--idle-server-timeout} <idle-server-timeout>]\n\
        \t[{-l|--lock} <filename>] [-S|--syslog] [-e|--stderr]\n\
        \t\[-I|--ignore-spawned-command-output]\n\
        \t<stop-server-command>\n\
\n\
Error messages are printed to stderr by default,\n\
-S|--syslog omits stderr output, both -S|--syslog and -e|--stderr output\n\
the errors by both methods.\n\
-I|--ignore-spawned-command-output will no longer warn of any stdout/stderr\n\
output of <*-server-command>s but it will no longer stuck if they held their\n\
output descriptors open.\n\
\n",program_name,program_name);
        exit(EXIT_FAILURE);
}

static const struct option longopts[]={
        {"start"                        ,0,0,'1'},
        {"stop"                         ,0,0,'0'},
        {"start-command-timeout"        ,1,0,'T'},
        {"idle-server-timeout"          ,1,0,'i'},
        {"syslog"                       ,0,0,'S'},
        {"stderr"                       ,0,0,'e'},
        {"lock"                         ,1,0,'l'},
        {"port"                         ,1,0,'p'},
        {"ignore-spawned-command-output",0,0,'I'},
        {"help"                         ,0,0,'h'},
        {NULL                           ,0,0,0  },
        };

static int lock_fd=-1;

static int sighandler_flock_timeout_hit;
static void sighandler_flock_timeout(int signo)
{
        sighandler_flock_timeout_hit=1;
}

static int lock_create(int lock_mode)
{
int retries=3;
sighandler_t sighandler_alrm_orig;
int flock_rc;

        if (!opt_lock)
                return 1;
        /* Never drop the lock if the lock is already being held. */
        if (lock_fd!=-1)
                retries=-1;
retry:
        if (lock_fd==-1) {
                if (-1==(lock_fd=open(opt_lock,O_CREAT|O_RDWR,0600)))
                        fatal("Error creating lock file \"%s\": %m",opt_lock);
                }
        sighandler_alrm_orig=signal(SIGALRM,sighandler_flock_timeout);
        alarm(opt_start_command_timeout+FLOCK_TIMEOUT_OVER_START_TIMEOUT);
        flock_rc=flock(lock_fd,lock_mode);
        alarm(0);
        signal(SIGALRM,sighandler_alrm_orig);
        if (sighandler_flock_timeout_hit)
                fatal("Timeout locking lock file \"%s\": %m",opt_lock);
        if (flock_rc) {
                if (lock_mode&LOCK_NB && errno==EWOULDBLOCK)
                        return 0;
                fatal("Error locking lock file \"%s\": %m",opt_lock);
                }
        if (access(opt_lock,R_OK|W_OK)) {
                if (retries--<=0)
                        fatal("Racing for the lock file \"%s\", giving 
up",opt_lock);
                if (close(lock_fd))
                        fatal("Error closing lock file \"%s\": %m",opt_lock);
                lock_fd=-1;
                goto retry;
                }
        return 1;
}

static void lock_touch(void)
{
        if (!opt_lock || lock_fd==-1)
                return;
        if (utime(opt_lock,NULL))
                fatal("Error updating lock file \"%s\" timestamp: %m",opt_lock);
}

static void lock_close(void)
{
        if (lock_fd==-1 || !opt_lock)
                return;
        /* It should not be needed but some stale locks were seen on:
         *      White Box Linux kernel-smp-2.6.9-5.0.5.EL
         */
        if (flock(lock_fd,LOCK_UN|LOCK_NB))
                fatal("Error unlocking lock file \"%s\": %m",opt_lock);
        if (close(lock_fd))
                fatal("Error closing lock file \"%s\": %m",opt_lock);
        lock_fd=-1;
}

static void lock_close_atexit(void)
{
        /* Prevent some crashes of malloc(3) etc. */
        verror_quiet=1;
        lock_close();
}

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 (;;) {
                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[SESSION_BUFFER_SIZE];

                                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) {
                                        lock_close();
                                        exit(EXIT_SUCCESS);
                                        }
                                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 int popen_pclose_checked(const char *command)
{
FILE *f;
char buf[SYSTEM_CHECKED_BUFFER_SIZE];
size_t got;

        if (!(f=popen(command,"r")))
                fatal("Error opening spawned command \"%s\": %m",command);
        setlinebuf(f);
        while ((got=fread(buf,1,sizeof(buf)-1,f))) {
char *s,*s_next;

                assert(got<sizeof(buf));
                buf[got]=0;
                for (s=buf;*s;s=s_next) {
                        if ((s_next=strchr(s,'\n'))) {
                                *s_next++=0;
                                error("Error line of spawned \"%s\": 
%s",command,s);
                                }
                        else {
                                s_next=s+strlen(s);
                                error("Error line of spawned \"%s\" too long, 
string cut: %s",command,s);
                                }
                        }
                }
        if (ferror(f))
                fatal("Error reading output of spawned \"%s\"",command);
        if (!feof(f))
                fatal("Error reaching end-of-file of messages of spawned 
\"%s\"",command);
        return pclose(f);
}

static void system_checked(const char *command)
{
int rc;

        if (!opt_ignore_spawned_command_output)
                rc=popen_pclose_checked(opt_stderr
                                ? command       /* differentiate ourself-dumped 
stdout from stderr */
                                : asprintf_checked("(%s) 2>&1",command)
                                );
        else
                rc=system(opt_stderr
                                ? asprintf_checked("(%s) >&2",command)
                                : asprintf_checked("(%s) &>/dev/null",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);
}

static void start(void) G_GNUC_NORETURN;
static void start(void)
{
int retry;

        if (!opt_port)
                fatal("-p|--port is a required argument for -1|--start");
        if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT)
                fatal("-i|--idle-server-timeout is a forbidden argument for 
-1|--start");

        lock_create(LOCK_SH);
        lock_touch();
        session_try();
        lock_close();

        lock_create(LOCK_EX);
        system_checked(opt_command);
        lock_create(LOCK_SH);

        for 
(retry=0;retry*CONNECT_RETRY_MSEC/1000<opt_start_command_timeout;retry++) {
                lock_touch();
                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_command_timeout,opt_port,opt_command);
}

/* Returns: Is fresh? */
static int lock_create_and_time_check(int lock_mode)
{
struct stat statbuf;

        if (!opt_lock)
                return 0;

        if (!lock_create(lock_mode|LOCK_NB))
                exit(EXIT_SUCCESS);
        if (lock_fd==-1 || fstat(lock_fd,&statbuf))
                fatal("Error fstat(2)ting lock file \"%s\": %m",opt_lock);
        return statbuf.st_mtime>=time(NULL)-opt_idle_server_timeout;
}

static void lock_delete_and_close(void)
{
        if (opt_lock && lock_fd!=-1)
                if (unlink(opt_lock))
                        fatal("Error deleting no longer used lock file \"%s\": 
%m",opt_lock);
        lock_close();
}

static void stop(void) G_GNUC_NORETURN;
static void stop(void)
{
int is_fresh;

        /* Lock still being held! */
        if (opt_port)
                fatal("-p|--port is a forbidden argument for -0|--stop");
        if (opt_start_command_timeout!=DEFAULT_START_COMMAND_TIMEOUT)
                fatal("-T|--start-command-timeout is a forbidden argument for 
-0|--stop");
        if (opt_idle_server_timeout!=DEFAULT_IDLE_SERVER_TIMEOUT && !opt_lock)
                fatal("-l|--lock is a required argument for 
-i|--idle-server-timeout of -1|--start");

        is_fresh=lock_create_and_time_check(LOCK_SH);
        lock_close();
        if (is_fresh)
                exit(EXIT_SUCCESS);

        lock_create_and_time_check(LOCK_EX);
        system_checked(opt_command);
        lock_delete_and_close();

        exit(EXIT_SUCCESS);
}

int main(int argc,char **argv) G_GNUC_NORETURN;
int main(int argc,char **argv)
{
char optc;
size_t opt_command_len;
int i;
char *s;

        if ((program_name=strrchr(argv[0],'/')))
                program_name++;
        else
                program_name=argv[0];

        atexit(lock_close_atexit);

        optarg=NULL; optind=0;  /* FIXME: Possible portability problem. */
        while 
((optc=getopt_long(argc,argv,"01T:i:Sel:p:Ih",longopts,NULL))!=EOF) switch 
(optc) {
long l;
char *endptr;

                case '1':       /* -1|--start */
                        opt_start=1;
                        break;

                case '0':       /* -0|--stop */
                        opt_stop=1;
                        break;

                case 'T':       /* -T|--start-command-timeout */
                        l=strtol(optarg,&endptr,0);
                        if (l<=0 || 
l>=LONG_MAX-FLOCK_TIMEOUT_OVER_START_TIMEOUT || (endptr && *endptr))
                                fatal("Invalid -T|--start-command-timeout 
value: %s",optarg);
                        opt_start_command_timeout=l;
                        break;

                case 'i':       /* -i|--idle-server-timeout */
                        l=strtol(optarg,&endptr,0);
                        if (l<=0 || l>=LONG_MAX || (endptr && *endptr))
                                fatal("Invalid -i|--idle-server-timeout value: 
%s",optarg);
                        opt_idle_server_timeout=l;
                        break;

                case 'S':       /* -S|--syslog */
                        opt_syslog=1;
                        break;

                case 'e':       /* -e|--stderr */
                        opt_stderr=1;
                        break;

                case 'l':       /* -l|--lock */
                        opt_lock=optarg;
                        break;

                case 'I':       /* -I|--ignore-spawned-command-output */
                        opt_ignore_spawned_command_output=1;
                        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_start && !opt_stop)
                fatal("At least one of -1|--start or -0|--stop is required");
        if ( opt_start &&  opt_stop)
                fatal("Both modes -1|--start and -0|--stop can never be 
specified simultaneously");

        if (optind>=argc)
                fatal("<start-server-command/stop-server-command> is a required 
argument");
        opt_command_len=0;
        for (i=optind;i<argc;i++)
                opt_command_len+=strlen(argv[i])+1;
        opt_command=xmalloc(opt_command_len);
        s=opt_command;
        for (i=optind;i<argc;i++) {
size_t argv_i_len=strlen(argv[i]);

                if (s>opt_command)
                        *s++=' ';
                memcpy(s,argv[i],argv_i_len);
                s+=argv_i_len;
                }
        *s++=0;
        assert(s==opt_command+opt_command_len);

        if (!opt_syslog && !opt_stderr)
                opt_stderr=1;

        if (opt_start)
                start();
        if (opt_stop)
                stop();
        assert(0);
}

Reply via email to