/*
 * $Header: /home2/aoki/postgres/src/backend/postmaster/RCS/postmaster.c,v 1.65 1993/03/17 20:31:06 aoki Exp $
 *
 *	POSTMASTER
 *
 *	This program acts as a clearing house for requests to the
 *	POSTGRES system.  Frontend programs send a startup message
 *	to the Postmaster and the postmaster uses the info in the
 *	message to setup a backend process.
 *
 * Initialization:
 *	The Postmaster sets up a few shared memory data structures 
 * 	for the backends.  It should at the very least initialize the
 *	lock manager.
 *
 * Synchronization:
 *	The Postmaster shares memory with the backends and will have to lock
 *	the shared memory it accesses.  The Postmaster should never block
 *	on messages from clients.
 *	
 * Garbage Collection:
 *	The Postmaster cleans up after backends if they have an emergency
 *	exit and/or core dump.
 *
 * Communication:
 *
 * NOTES:
 *
 */

#include <signal.h>
#include <strings.h>
#include <sys/types.h>		/* for fd_set stuff */
#include <sys/time.h>
#ifdef PORTNAME_svr4
#include <netdb.h>
#else
#include <sys/param.h>		/* for MAXHOSTNAMELEN */
#endif
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <values.h>		/* for MAXINT */

#include "tmp/master.h"		/* for DEF_PORT */
#include "storage/ipci.h"
#include "tmp/pqcomm.h"
#include "tmp/miscadmin.h"
#include "catalog/pg_user.h"	/* for USER_NAMESIZE */
#include "tmp/simplelists.h"

#ifdef DBX_VERSION
#define FORK() (0)
#else
#define FORK() vfork()
#endif

/*
 * Info for garbage collection.  Whenever a process dies, the Postmaster
 * cleans up after it.  Currently, NO information is required for cleanup,
 * but I left this structure around in case that changed.
 */
typedef struct bkend {
    int	pid;		/* process id of backend */
} Backend;

typedef struct bnode {
    SLNode	bn_node;
    Backend	bn_backend;
} BackendNode;

/* list of active backends.  For garbage collection only now. */
static SLList	BackendList;

typedef struct pnode {
    SLNode	pn_node;
    Port	pn_port;
} PortNode;

/* list of ports associated with still open, but incomplete connections */
static SLList	PortList;

static short	PostPortName = -1;
static short	ActiveBackends = FALSE;
static int	NextBackendId = MAXINT;		/* XXX why? */
static char	*progname = (char *) NULL;

char		*DataDir = (char *) NULL;
    
/*
 * Default Values
 */
static char	Execfile[MAXPATHLEN] = "";
static char	Username[USER_NAMESIZE] = "NOT_PROVIDED";

static int	ServerSock = INVALID_SOCK;	/* stream socket server */

/* 
 * Defined in utils/init/globals.c, set by the -d option and passed on
 * to child processes
 */
extern short	DebugLvl;

/*
 * Set by the -o option
 */
static char	ExtraOptions[ARGV_SIZE] = "";

/*
 * These globals control the behavior of the postmaster in case some
 * backend dumps core.  Normally, it kills all peers of the dead backend
 * and reinitializes shared memory.  By specifying -s or -n, we can have
 * the postmaster stop (rather than kill) peers and not reinitialize
 * shared data structures.
 */
static int	Reinit = 1;
static int	SendStop = 0;

/* 
 * postmaster.c - function prototypes (none of the funcs are public use)
 */
int	ServerLoop ARGS((int serverFd));
int     ConnStartup ARGS((Port * port));
int     ConnCreate ARGS((int serverFd, int *newFdP));
void	reset_shared ARGS((short port));
int     pmdie ARGS(());
int     reaper ARGS(());
int	dumpstatus ARGS(());
void	CleanupProc ARGS((int pid, int exitstatus));
int     BackendStartup ARGS((StartupPacket * packet, Port * port));
int     DoExec ARGS((StartupPacket * packet, int portFd));
int     ExitPostmaster ARGS((int status));

/* from utils/init/miscinit.c */
extern	ValidateBackend ARGS((char *path));
extern	FindBackend ARGS((char *backend, char *argv0));

extern	Quiet;

main(argc, argv)
    int		argc;
    char	*argv[];
{
    extern char	*getenv(), getopt(), *optarg;
    extern int	opterr, optind;
    extern int	NBuffers;	/* from buffer/bufmgr.c */
    extern bool	IsPostmaster;	/* from smgr/mm.c */
    char	opt;
    char	*hostName;
    int		status;
    char	hostbuf[MAXHOSTNAMELEN];

    progname = argv[0];

    signal(SIGCHLD, reaper);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTTOU, SIG_IGN);
    signal(SIGHUP, pmdie);
    signal(SIGINT, pmdie);
    signal(SIGTERM, pmdie);
    signal(SIGCONT, dumpstatus);

    if (!(hostName = getenv("PGHOST"))) {
	if (gethostname(hostbuf, MAXHOSTNAMELEN) < 0)
	    (void) strcpy(hostbuf, "localhost");
	hostName = hostbuf;
    }

    opterr = 0;
    while ((opt = getopt(argc, argv, "a:B:b:D:dno:p:s")) != EOF) {
	switch (opt) {
	case 'a': 
	    /* Set the authentication system. */
	    be_setauthsvc(optarg);
	    break;
	case 'B': 
	    /*
	     * The number of buffers to create.  Setting this
	     * option means we have to start each backend with
	     * a -B # to make sure they know how many buffers
	     * were allocated. 
	     */
	    NBuffers = atol(optarg);
	    (void) strcat(ExtraOptions, " -B ");
	    (void) strcat(ExtraOptions, optarg);
	    break;
	case 'b': 
	    /* Set the backend executable file to use. */
	    if (!ValidateBackend(optarg))
		strcpy(Execfile, optarg);
	    else {
		fprintf(stderr, "%s: invalid backend \"%s\"\n",
			progname, optarg);
		exit(2);
	    }
	    break;
	case 'D': 
	    /* Set PGDATA from the command line. */
	    DataDir = optarg;
	    break;
	case 'd': 
	    /*
	     * Turn on debugging for the postmaster and the backend
	     * servers descended from it.
	     */
	    if ((optind < argc) && *argv[optind] != '-') {
		DebugLvl = atoi(argv[optind]);
		optind++;
	    }
	    else
		DebugLvl = 1;
	    break;
	case 'n': 
	    /* Don't reinit shared mem after abnormal exit */
	    Reinit = 0;
	    break;
	case 'o': 
	    /*
	     * Other options to pass to the backend on the
	     * command line -- useful only for debugging.
	     */
	    (void) strcat(ExtraOptions, " ");
	    (void) strcat(ExtraOptions, optarg);
	    break;
	case 'p': 
	    /* Set PGPORT by hand. */
	    PostPortName = (short) atoi(optarg);
	    break;
	case 's': 
	    /*
	     * In the event that some backend dumps core,
	     * send SIGSTOP, rather than SIGUSR1, to all
	     * its peers.  This lets the wily post_hacker
	     * collect core dumps from everyone.
	     */
	    SendStop = 1;
	    break;
	default: 
	    /* usage() never returns */
	    usage(progname);
	    break;
	}
    }
    if (PostPortName == -1)
	PostPortName = pq_getport();

    IsPostmaster = true;

    if (!DataDir)
	DataDir = GetPGData();

    if (!Execfile[0] && FindBackend(Execfile, argv[0]) < 0) {
	fprintf(stderr, "%s: could not find backend to execute...\n",
		argv[0]);
	exit(1);
    }

    status = StreamServerPort(hostName, PostPortName, &ServerSock);
    if (status != STATUS_OK) {
	fprintf(stderr, "%s: cannot create stream port\n",
		progname);
	exit(1);
    }

    /* set up shared memory and semaphores */
    EnableMemoryContext(TRUE);
    reset_shared(PostPortName);

    /* 
     * Initialize the list of active backends.  This list is only
     * used for garbage collecting the backend processes.
     */
    SLNewList(&BackendList, 0);
    SLNewList(&PortList, 0);

    status = ServerLoop(ServerSock);

    ExitPostmaster(status != STATUS_OK);
}

usage(progname)
    char   *progname;
{
    fprintf(stderr, "usage: %s [options..]\n", progname);
    fprintf(stderr, "\t-B nbufs\tset number of shared buffers\n");
    fprintf(stderr, "\t-D datadir\tset data directory\n");
    fprintf(stderr, "\t-a authsys\tdo/do not permit use of an authentication system\n");
    fprintf(stderr, "\t-b backend\tuse a specific backend server executable\n");
    fprintf(stderr, "\t-d [debuglvl]\tset debugging level\n");
    fprintf(stderr, "\t-n\t\tdon't reinitialize shared memory after abnormal exit\n");
    fprintf(stderr, "\t-o option\tpass 'option' to each backend servers\n");
    fprintf(stderr, "\t-p port\tspecify port for postmaster to listen on\n");
    fprintf(stderr, "\t-s\t\tsend SIGSTOP to all backend servers if one dies\n");
    exit(1);
}

ServerLoop(serverFd)
    int     serverFd;
{
    fd_set	rmask, basemask;
    int		nSockets, nSelected, status, newFd;
    PortNode	*prev, *curr;

    nSockets = ServerSock + 1;
    FD_ZERO(&basemask);
    FD_SET(ServerSock, &basemask);

    for (;;) {
	newFd = -1;
	bcopy((char *) &basemask, (char *) &rmask, sizeof(fd_set));
	if ((nSelected = select(nSockets, &rmask,
				(fd_set *) NULL,
				(fd_set *) NULL,
				(struct timeval *) NULL)) < 0) {
	    if (errno == EINTR)
		continue;
	    fprintf(stderr, "%s: ServerLoop: select failed\n",
		    progname);
	    return(STATUS_ERROR);
	}
	if (DebugLvl) {
	    fprintf(stderr, "%s: ServerLoop: %d sockets pending\n",
		    progname, nSelected);
	}

	/* new connection pending on our well-known port's socket */
	if (FD_ISSET(ServerSock, &rmask)) {
	    /*
	     * connect and make an addition to PortList.  If
	     * the connection dies and we notice it, just forget
	     * about the whole thing.
	     */
	    if (ConnCreate(serverFd, &newFd) == STATUS_OK) {
		if (newFd >= nSockets)
		    nSockets = newFd + 1;
		FD_SET(newFd, &rmask);
		FD_SET(newFd, &basemask);
		if (DebugLvl)
		    fprintf(stderr, "%s: ServerLoop: connect on %d\n",
			    progname, newFd);
	    }
	    --nSelected;
	    FD_CLR(ServerSock, &rmask);
	}

	if (DebugLvl) {
	    fprintf(stderr, "%s: ServerLoop:\tnSelected=%d\n",
		    progname, nSelected);
	    curr = (PortNode *) SLGetHead(&PortList);
	    while (curr) {
		Port *port = &curr->pn_port;

		fprintf(stderr, "%s: ServerLoop:\t\tport %d%s pending\n",
			progname, port->sock,
			FD_ISSET(port->sock, &rmask)
			? "" :
			" not");
		curr = (PortNode *) SLGetSucc(&curr->pn_node);
	    }
	}

	curr = (PortNode *) SLGetHead(&PortList);
	while (curr) {
	    ConnId connId;	/* dummy argument */
	    Port *port = &curr->pn_port;
	    int lastbytes = port->nBytes;

	    if (FD_ISSET(port->sock, &rmask) && port->sock != newFd) {
		if (DebugLvl)
		    fprintf(stderr, "%s: ServerLoop:\t\thandling %d\n",
			    progname, port->sock);
		--nSelected;

		/*
		 * Read the incoming packet into its packet buffer.
		 * Read the connection id out of the packet so we
		 * know who the packet is from.
		 */
		status = PacketReceive(port, (Addr) &port->buf,
				       NON_BLOCKING, &connId);
		switch (status) {
		case STATUS_OK: 
		    ConnStartup(port);
		    ActiveBackends = TRUE;
		    /*FALLTHROUGH*/
		case STATUS_INVALID: 
		    if (DebugLvl)
			fprintf(stderr, "%s: ServerLoop:\t\tdone with %d\n",
				progname, port->sock);
		    break;
		case STATUS_NOT_DONE:
		    if (DebugLvl)
			fprintf(stderr, "%s: ServerLoop:\t\tpartial packet (%d bytes actually read) on %d\n",
				progname, port->nBytes, port->sock);
		    /*
		     * If we've received at least a PacketHdr's worth of data
		     * and we're still receiving data each time we read, we're
		     * ok.  If the client gives us less than a PacketHdr at
		     * the beginning, just kill the connection and forget
		     * about the whole thing.
		     */
		    if (lastbytes < port->nBytes) {
			if (DebugLvl)
			    fprintf(stderr, "%s: ServerLoop:\t\tpartial packet on %d ok\n",
				    progname, port->sock);
			curr = (PortNode *) SLGetSucc(&curr->pn_node);
			continue;
		    }
		    break;
		case STATUS_ERROR:	/* system call error - die */
		    fprintf(stderr, "%s: ServerLoop:\t\terror receiving packet\n",
			    progname);
		    return(STATUS_ERROR);
		}
		FD_CLR(port->sock, &basemask);
		StreamClose(port->sock);
		prev = (PortNode *) SLGetPred(&curr->pn_node);
		SLRemove(&curr->pn_node);
		free((char *) curr);
		if (!prev) {	/* removed head */
		    curr = (PortNode *) SLGetHead(&PortList);
		    continue;
		}
	    }
	    curr = (PortNode *) SLGetSucc(&curr->pn_node);
	}
	Assert(nSelected == 0);
    }
    /*NOTREACHED*/
}

ConnStartup(port)
    Port	*port;			/* receiving port */
{
    int		status;
    MsgType		msgType;
    SeqNo		seqno = INITIAL_SEQNO;
    PacketLen	bufSize;
    Addr		buf;
    char		namebuf[USER_NAMESIZE + 1];
    StartupPacket	*sp;
    
    /* 
     * Get the packet header information.
     *  XXX This is the only use of PacketData() in the entire system,
     *	yet we're feeding it arguments (buf, bufSize, seqno) that
     *	we never use.  WTF??
     */
    sp = (StartupPacket *) &port->buf;
    PacketData((Addr) sp, &buf, &bufSize, &msgType, &seqno);
    
    (void) strncpy(namebuf, sp->user, USER_NAMESIZE);
    namebuf[USER_NAMESIZE] = '\0';
    if (!namebuf[0]) {
	fprintf(stderr, "%s: ConnStartup: no user name specified\n",
		progname);
	return(STATUS_ERROR);
    }
    
    if (be_recvauth(msgType, port, namebuf) != STATUS_OK) {
	fprintf(stderr, "%s: ConnStartup: authentication failed\n",
		progname);
	return(STATUS_ERROR);
    }
    
    if (BackendStartup(sp, port) != STATUS_OK) {
	fprintf(stderr, "%s: ConnStartup: couldn't start backend\n",
		progname);
	return(STATUS_ERROR);
    }
    
    return(STATUS_OK);
}


/*
 * ConnCreate -- create a local connection data structure
 */
ConnCreate(serverFd, newFdP)
    int	serverFd;
    int	*newFdP;
{
#if 0
    /* dead code having to do with the pseudo-Packet stuff */
    Connection	*conn;
    int		connId;
    long		currTime;
#endif
    int		status;
    Port		*port;
    PortNode	*pn;
    extern char	*calloc ();
    
    if (!(pn = (PortNode *) calloc(1, sizeof(PortNode)))) {
	fprintf(stderr, "%s: ConnCreate: malloc failed\n",
		progname);
	ExitPostmaster(1);
    }
    SLNewNode(&pn->pn_node);
    port = &pn->pn_port;
    
    if ((status = StreamConnection(serverFd, port)) != STATUS_OK) {
	StreamClose(port->sock);
	free((char *) pn);
    }
    else {
	SLAddHead(&PortList, &pn->pn_node);
	*newFdP = port->sock;
    }
    
    return (status);
}

/*
 * reset_shared -- reset shared memory and semaphores
 */
void
reset_shared(port)
    short	port;
{
    IPCKey	key;
    
    key = SystemPortAddressCreateIPCKey((SystemPortAddress) port);
    CreateSharedMemoryAndSemaphores(key);
    ActiveBackends = FALSE;
}

/*
 * pmdie -- signal handler for cleaning up after a kill signal.
 */
pmdie()
{
    exitpg(0);
}

/*
 * Reaper -- signal handler to cleanup after a backend (child) dies.
 */
reaper()
{
    union wait	status;		/* backend exit status */
    int		pid;		/* process id of dead backend */
    
    if (DebugLvl)
	fprintf(stderr, "%s: reaping dead processes...\n",
		progname);
    while((pid = wait3(&status, WNOHANG, (struct rusage  *) NULL)) > 0)
	CleanupProc(pid, status.w_status);
#ifdef PORTNAME_svr4
    signal(SIGCHLD, reaper);	/* XXX is there a race here? */
#endif /* PORTNAME_svr4 */
}

/*
 * CleanupProc -- cleanup after terminated backend.
 *
 * Remove all local state associated with backend.
 *
 * Dillon's note: should log child's exit status in the system log.
 */
void
CleanupProc(pid, exitstatus)
    int	pid;
    int	exitstatus;		/* child's exit status. */
{
    BackendNode	*prev, *curr;
    Backend		*bp;
    int		sig;
    
    if (DebugLvl) {
	fprintf(stderr, "%s: CleanupProc: pid %d exited with status %d\n",
		progname, pid, exitstatus);
    }
    /*
     * -------------------------
     * If a backend dies in an ugly way (i.e. exit status not 0) then
     * we must signal all other backends to quickdie.  If exit status
     * is zero we assume everything is hunky dory and simply remove the
     * backend from the active backend list.
     * -------------------------
     */
    if (!exitstatus) {
	curr = (BackendNode *) SLGetHead(&BackendList);
	while (curr) {
	    bp = &curr->bn_backend;
	    if (bp->pid == pid) {
		SLRemove(&curr->bn_node);
		free((char *) curr);
		break;
	    }
	    curr = (BackendNode *) SLGetSucc(&curr->bn_node);
	}
	return;
    }
    
    curr = (BackendNode *) SLGetHead(&BackendList);
    while (curr) {
	bp = &curr->bn_backend;
	
	/*
	 * -----------------
	 * SIGUSR1 is the special signal that sez exit without exitpg
	 * and let the user know what's going on. ProcSemaphoreKill()
	 * cleans up the backends semaphore.  If SendStop is set (-s on
	 * the command line), then we send a SIGSTOP so that we can
	 * collect core dumps from all backends by hand.
	 * -----------------
	 */
	sig = (SendStop) ? SIGSTOP : SIGUSR1;
	if (bp->pid != pid) {
	    if (DebugLvl)
		fprintf(stderr, "%s: CleanupProc: sending %s to process %d\n",
			progname,
			(sig == SIGUSR1)
			? "SIGUSR1" : "SIGSTOP",
			bp->pid);
	    (void) kill(bp->pid, sig);
	}
	ProcSemaphoreKill(bp->pid);
	
	prev = (BackendNode *) SLGetPred(&curr->bn_node);
	SLRemove(&curr->bn_node);
	free((char *) curr);
	if (!prev) {		/* removed head */
	    curr = (BackendNode *) SLGetHead(&BackendList);
	    continue;
	}
	curr = (BackendNode *) SLGetSucc(&curr->bn_node);
    }
    /*
     * -------------
     * Quasi_exit means run all of the on_exitpg routines but don't
     * acutally call exit().  The on_exit list of routines to do is
     * also truncated.
     *
     * Nothing up my sleeve here, ActiveBackends means that since the
     * last time we recreated shared memory and sems another frontend
     * has requested and received a connection and I have forked off
     * another backend.  This prevents me from reinitializing shared
     * stuff more than once for the set of backends that caused the
     * failure and were killed off.
     * ----------------
     */
    if (ActiveBackends == TRUE && Reinit) {
	IPCKey	key;
	
	if (DebugLvl)
	    fprintf(stderr, "%s: CleanupProc: reinitializing shared memory and semaphores\n",
		    progname);
	quasi_exitpg();
	reset_shared(PostPortName);
    }
}

/*
 * BackendStartup -- start backend process
 *
 * returns: STATUS_ERROR if the fork/exec failed, STATUS_OK
 *	otherwise.
 *
 */
BackendStartup(packet, port)
    StartupPacket	*packet;	/* client's startup packet */
    Port		*port;
{
    int		status = 0;	/* return status */
    BackendNode	*bn;		/* for backend cleanup */
    int		pid;
    char		envEntry[4][2 * ARGV_SIZE];
    extern char	*calloc();
    
    /*
     * Set up the necessary environment variables for the backend
     * This should really be some sort of message....
     */
    sprintf(envEntry[0], "POSTPORT=%d", PostPortName);
    putenv(envEntry[0]);
    sprintf(envEntry[1], "POSTID=%d", NextBackendId);
    putenv(envEntry[1]);
    sprintf(envEntry[2], "PG_USER=%s", packet->user);
    putenv(envEntry[2]);
    if (!getenv("PGDATA")) {
	sprintf(envEntry[3], "PGDATA=%s", DataDir);
	putenv(envEntry[3]);
    }
    if (DebugLvl) {
	char		**p;
	extern char	**environ;
	
	fprintf(stderr, "%s: BackendStartup: environ dump:\n",
		progname);
	fprintf(stderr, "-----------------------------------------\n");
	for (p = environ; *p; ++p)
	    fprintf(stderr, "\t%s\n", *p);
	fprintf(stderr, "-----------------------------------------\n");
    }
    
    if ((pid = FORK()) == 0) {	/* child */
	if (DoExec(packet, port->sock))
	    fprintf(stderr, "%s child[%d]: BackendStartup: execv failed\n",
		    progname, pid);
	/* use _exit to keep from double-flushing stdio */
	_exit(1);
    }
    
    if (pid < 0) {
	fprintf(stderr, "%s: BackendStartup: fork failed\n",
		progname);
	return(STATUS_ERROR);
    }
    
    if (DebugLvl)
	fprintf(stderr, "%s: BackendStartup: forked pid[%d] on socket %d\n",
		progname, pid, port->sock);
    
    /* adjust backend counter */
    /* XXX Don't know why this is done, but for now backend needs it */
    NextBackendId -= 1;
    
    /*
     * Everything's been successful, it's safe to add this backend to our
     * list of backends.
     */
    if (!(bn = (BackendNode *) calloc(1, sizeof (BackendNode)))) {
	fprintf(stderr, "%s: BackendStartup: malloc failed\n",
		progname);
	ExitPostmaster(1);
    }
    SLNewNode(&bn->bn_node);
    bn->bn_backend.pid = pid;
    SLAddHead(&BackendList, &bn->bn_node);
    
    return(STATUS_OK);
}

/*
 * split_opts -- destructively load a string into an argv array
 *
 * Since no current POSTGRES arguments require any quoting characters,
 * we can use the simple-minded tactic of assuming each set of space-
 * delimited characters is a separate argv element.
 *
 * If you don't like that, well, we *used* to pass the whole option string
 * as ONE argument to execl(), which was even less intelligent...
 */
void
split_opts(argv, argcp, s)
    char	**argv;
    int	*argcp;
    char	*s;
{
    int	i = *argcp;
    
    while (s && *s) {
	while (isspace(*s))
	    ++s;
	if (*s)
	    argv[i++] = s;
	while (*s && !isspace(*s))
	    ++s;
	if (isspace(*s))
	    *s++ = '\0';
    }
    *argcp = i;
}

/*
 * DoExec -- set up the argument list and perform an execv system call
 *
 * Tries fairly hard not to dork with anything that isn't automatically
 * allocated so we don't do anything weird to the postmaster when it gets
 * its thread back.  (This is vfork() we're talking about.  If we're using
 * fork() because we don't have vfork(), then we don't really care.)
 *
 * returns: 
 *	Shouldn't return at all.
 *	If execv() fails, return status.
 */
DoExec(packet, portFd)
    StartupPacket	*packet;
    int		portFd;
{
    char	execbuf[MAXPATHLEN];
    char	portbuf[ARGV_SIZE];
    char	debugbuf[ARGV_SIZE];
    char	ttybuf[ARGV_SIZE + 1];
    char	argbuf[(2 * ARGV_SIZE) + 1];
    /*
     * each argument takes at least three chars, so we can't
     * have more than ARGV_SIZE arguments in (2 * ARGV_SIZE)
     * chars (i.e., packet->options plus ExtraOptions)...
     */
    char	*av[ARGV_SIZE];
    char	dbbuf[ARGV_SIZE + 1];
    int	ac = 0;
    
#ifdef ANY_BACKEND
    if (packet->execFile[0])
	(void) strncpy(execbuf, packet->execFile, ARGV_SIZE);
    else
#endif
	(void) strncpy(execbuf, Execfile, MAXPATHLEN);
    execbuf[MAXPATHLEN - 1] = '\0';
    av[ac++] = execbuf;
    
    /* Tell the backend it is being called from the postmaster */
    av[ac++] = "-p";
    
    /* Pass the requested debugging level along to the backend */
    if (DebugLvl) {
	(void) sprintf(debugbuf, "-d%d", DebugLvl);
	av[ac++] = debugbuf;
    }
    else
	av[ac++] = "-Q";
    
    /* Pass the requested debugging output file */
    if (packet->tty[0]) {
	(void) strncpy(ttybuf, packet->tty, ARGV_SIZE);
	av[ac++] = "-o";
	av[ac++] = ttybuf;
    }
    
    /* Tell the backend the descriptor of the fe/be socket */
    (void) sprintf(portbuf, "-P%d", portFd);
    av[ac++] = portbuf;
    
    (void) strncpy(argbuf, packet->options, ARGV_SIZE);
    argbuf[ARGV_SIZE] = '\0';
    (void) strncat(argbuf, ExtraOptions, ARGV_SIZE);
    argbuf[(2 * ARGV_SIZE) + 1] = '\0';
    split_opts(av, &ac, argbuf);
    
    if (packet->database[0])
	(void) strncpy(dbbuf, packet->database, ARGV_SIZE);
    else
	(void) strncpy(dbbuf, packet->user, USER_NAMESIZE);
    dbbuf[ARGV_SIZE] = '\0';
    av[ac++] = dbbuf;
    
    av[ac] = (char *) NULL;
    
    if (DebugLvl) {
	int	i;
	
	fprintf(stderr, "%s child[%d]: execv(",
		progname, getpid());
	for (i = 0; i < ac; ++i)
	    fprintf(stderr, "%s, ", av[i]);
	fprintf(stderr, ")\n");
    }
    
    return(execv(av[0], av));
}

/*
 * ExitPostmaster -- cleanup
 */
ExitPostmaster(status)
    int	status;
{
    /* should cleanup shared memory and kill all backends */
    
    /* 
     * Not sure of the semantics here.  When the Postmaster dies,
     * should the backends all be killed? probably not.
     */
    if (ServerSock != INVALID_SOCK)
	close(ServerSock);
    exitpg(status);
}

dumpstatus()
{
    PortNode	*curr = (PortNode *) SLGetHead(&PortList);

    while (curr) {
	Port *port = &curr->pn_port;
	
	fprintf(stderr, "%s: dumpstatus:\n", progname);
	fprintf(stderr, "\tsock %d: nBytes=%d, laddr=0x%x, raddr=0x%x\n",
		port->sock, port->nBytes, port->laddr, port->raddr);
	curr = (PortNode *) SLGetSucc(&curr->pn_node);
    }
#ifdef PORTNAME_svr4
    signal(SIGCONT, dumpstatus);	/* XXX is there a race here? */
#endif /* PORTNAME_svr4 */
}
