/*
 * $Header: /private/postgres/src/support/RCS/postmaster.c,v 1.57 1992/08/12 02:47:20 mao 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:
 *
 * Security:  
 *	There is none. Not a little bit.
 *
 * NOTES:
 *
 */

#include <signal.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#if !sprite
#include <sys/un.h>
#endif /* !sprite */

#include "support/master.h"
#include "utils/log.h"
#include "storage/ipci.h"
#include "tmp/pqcomm.h"
#include "catalog/pg_user.h"
#include "support/list.h"

/* default permissions for opening files. */
#define OPEN_PERMISSIONS	(0666)

List *ListGarbage;	/* holds used list nodes for future use */
short CommPort;
char ShmIdStr[10];
char ShmSizeStr[10];
extern char *GetPGHome();


#ifdef DBX_VERSION
#define FORK() (0)
#else
#define FORK() fork()
#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;

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


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

/* for elog() messages.  Port to send the message to. */
Port	*SendPort;

/*
 * Default startup message parameters.  
 */
StartupPacket DefaultStartup;

/*
 * IOList is used to setup file descriptors of backend processes.
 *	Specify here how the descriptor set of the child (backend)
 *	is different from the parent (Postmaster).  I'm not sure
 *	this stuff works all that well.  I don't use any of the 
 * 	sys logs.
 *
 * See descriptor reset for some notes.
 */
typedef struct IOList {
  int	fd;
  char  *name;
  int	mode;
} IOList;

#define DEFAULT	""

IOList IO_List[] = {
  { 0, DEFAULT, (O_RDONLY) },
  { 1, DEFAULT, (O_WRONLY | O_APPEND | O_CREAT) },
  { 2, DEFAULT, (O_WRONLY | O_APPEND| O_CREAT) },
  { 0, NULL, 0 },
};

extern int NBuffers;

extern char *optarg;
extern int opterr;
extern int optind;
extern int errno;

extern bool	IsPostmaster;

short	PostPortName =	DEF_PORT;
short	ActiveBackends = FALSE;
int	NextBackendId = 0x7fffffff;	/* XXX not sure what this does */

char *Usage = "postmaster [ -p port ] [ -b backend_pathname ] [ -d [level] ]\n";
/*
 * Default Values
 */
char Username[USER_NAMESIZE];
char Dbname[64];

/* 
 * This is set by getpghome().
 */
char *Home = "";

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

/* 
 * Set by the -d option
 */
int	Debug = 0;

/*
 * Set by the -o option
 */
char ExtraOptions[ARGV_SIZE] = {0};

/*
 * 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.
 */
int	Reinit = 1;
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 ));
int PortDestroy ARGS((Port *port ));
int pmdie ARGS((void ));
int reaper ARGS((void ));
int CleanupProc ARGS((int pid , int exitstatus ));
int BackendStartup ARGS((Port *port , StartupPacket *packet , IOList *ioList ));
int fixlead ARGS((char *str , char **dstP , int *sizeP ));
int DoExec ARGS((char *execFile , char *database , int portFd , StartupPacket *pack ));
int DescriptorReset ARGS((IOList *ioList ));
int ExitPostmaster ARGS((int status ));
int FindBackend ARGS((char *backend ));
int ValidBackend ARGS((char *path ));
int ReadObjDir ARGS((char *dir ));
char *zalloc ARGS((unsigned long size ));
#if 0
int *CacheAlloc ARGS((unsigned int size ));
#endif

main(argc, argv)
int	argc;
char	*argv[];
{
  char  	*getenv();
  char		getopt();
  char		opt;
  int		serverMask;
  char		*hostName;
  int		status;
  int		size;
  char 		sysLogPath[PATH_SIZE];
  char		*sysLogPtr = sysLogPath;
  char 		dblogPath[PATH_SIZE];
  char		*dblogPtr = dblogPath;

  /*
   * Initialize signal handlers.  I took this from Dillon's code.
   * Not sure who made the decision about what signals to handle.
   */
  signal(SIGCHLD, reaper);
  signal(SIGTTIN, SIG_IGN);
  signal(SIGTTOU, SIG_IGN);
  
  signal(SIGHUP, pmdie);
  signal(SIGINT, pmdie);
  signal(SIGTERM, pmdie);

  ListHead(&ListGarbage);	/* initialize ListGarbage */

  if (!(hostName = getenv("PGHOST"))) {
    hostName = "localhost";
  }

  opterr = 0;
  while ((opt = getopt(argc, argv, "B:b:dno:p:s")) != EOF) {
	switch (opt) {
		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);
			strcat(ExtraOptions, "-B ");
			strcat(ExtraOptions, optarg);
			break;
		case 'b':
			if (ValidBackend(optarg))
  				strcpy(DefaultStartup.execFile, optarg);
			else
				fprintf(stderr, "%s %s",
			  		"Couldn't find the requested backend",
			  		"-- Using a default search path...\n");
			break;
		case 'd':
			if ((optind < argc) && *argv[optind] != '-') {
				Debug = atoi(argv[optind]);
				optind++;
			}
			else
				Debug = 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.
			 */
			strcat(ExtraOptions, optarg);
			break;
		case 'p':
			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:
			perror("argument list");
			fprintf(stderr, "Usage: %s", Usage);
			exit(errno);
			break;
	}
  }

  IsPostmaster = true;

  Home = GetPGHome();

  /* default startup values */
  if (! DefaultStartup.execFile[0])
  	FindBackend(DefaultStartup.execFile);

  strncpy(Username, getenv("USER"), USER_NAMESIZE);
  strcpy(DefaultStartup.user, Username);
  strcpy(DefaultStartup.database,Username);
  strcpy(Dbname, Username);

  status = StreamServerPort(hostName,PostPortName, &ServerSock);
  if (status != STATUS_OK)  {
    elog(WARN,"PostMaster:cannot create stream port") ;
    exit(1);
  }

  /* set up shared memory and semaphores */
  EnableMemoryContext(TRUE);
  CreateSharedMemoryAndSemaphores(
	SystemPortAddressCreateIPCKey((SystemPortAddress)PostPortName));

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

  status = ServerLoop(ServerSock);
  ExitPostmaster(status != STATUS_OK);
}

ServerLoop(serverFd)
int	serverFd;
{
  int		mask;
  int		baseMask;
  int		serverMask;
  int		nSockets;
  int		nSelected;
  int		status;
  int		dummy;
  List		*curr;

  nSockets  = ServerSock+1;
  baseMask = 1 << ServerSock;
  serverMask = baseMask;

  for (;;) {
    dummy = 0;
    mask = baseMask;
    if ((nSelected = select(nSockets, &mask, &dummy, &dummy, NULL)) < 0) {
      if (errno == EINTR)
	continue;
      elog(WARN,"Postmaster: select failed");
      return(STATUS_ERROR);
    }

    if (serverMask & mask) {
      int	newFd;
      int	newMask;

      status = ConnCreate(serverFd, &newFd);

      if (newFd >= nSockets)
	nSockets = newFd+1;

      /* read the new connection on the first pass */
      newMask = 1 << newFd;
      nSelected--;
      /* add the new connection to the baseMask */
      baseMask |= newMask;
    }

    ListForEach( curr, PortList, PortList) {
      ConnId		connId;	/* dummy argument */
      Port		*port;	/* port for I/O */

      port = (Port *) ListElem(curr);

      if (port->mask & mask) {
	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;
	  baseMask &= ~ port->mask;
	  PortDestroy (port) ;
	  ListDelete (curr) ;
	  break;

	case STATUS_INVALID:
	  baseMask &= ~ port->mask;
	  PortDestroy (port) ;
	  ListDelete (curr) ;
	  break;

	case STATUS_NOT_DONE:
	  break;

	case STATUS_ERROR:
	  elog(WARN,"Postmaster: error receiving packet\n");
	  return(STATUS_ERROR);
	}
      }
    }
    Assert (! nSelected);
  }
  /* shouldn't ever get here */
}

ConnStartup(port)
Port	*port;		/* receiving port */	
{
    int 	status;		/* procedure return code */
    MsgType	msgType;	/* type of message recieved */
    SeqNo	seqno= INITIAL_SEQNO;/* dummy argument expected by packetData*/
    PacketLen	bufSize;	/* dummy argument */
    Addr	buf;		/* dummy argument */

    /*
     * Get the packet header information.
     */
    PacketData((Addr) &port->buf, &buf, &bufSize, &msgType,  &seqno);
    if (msgType != STARTUP_MSG) {
      elog(WARN,"Postmaster: unrecognized message type\n");
      return(STATUS_ERROR);
    }
    if (BackendStartup(port, (StartupPacket *)&port->buf, IO_List) != STATUS_OK)
    {
      elog(WARN,"Postmaster: couldn't startup backend for client\n");
      return(STATUS_ERROR);
    }
    return(STATUS_OK);
}


/*
 * ConnCreate -- create a local connection data structure
 */
ConnCreate(serverFd, newFdP)
int		serverFd;
int		*newFdP;
{
  Connection	*conn;
  int		connId;
  int		status;
  long		currTime;
  int		newFd;
  Port		*port = (Port *) zalloc(sizeof(Port));


  status = StreamConnection(serverFd,&newFd,&port->addr);

  port->sock = newFd;
  port->mask = 1 << newFd;

  ListPush(PortList, port);
  *newFdP = newFd;

  /* in case of error message */
  SendPort = port;

  /* 
   * If there was an error in the port creation, the connection
   * struct should be freed again.
   */
  if (status != STATUS_OK) {
    PortDestroy ( port ) ;
  }

  return(status);
}

PortDestroy(port)
Port	*port;
{
  StreamClose( port->sock );
  free ((char *)port);
}

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

/*
 * Reaper -- signal handler to cleanup after a backend (child) dies.
 *
 */
reaper()
{
#ifdef linux
  int		status;
#else
  union	wait	status;		/* backend exit status */
#endif
  struct	rusage	ruse;	/* resource usage structure */
  int		pid;		/* process id of dead backend */

  if (Debug)
    fprintf(stderr, "In reaper\n");
  while ((pid = wait3(&status, WNOHANG, &ruse)) > 0) {
#ifdef linux
    (void) CleanupProc(pid,WEXITSTATUS(status));
#else
    (void) CleanupProc(pid,status.w_status);
#endif
  }
}

/*
 * 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.
 */
CleanupProc(pid,exitstatus)
int	pid;
int	exitstatus;		/* child's exit status. */
{
  List	*curr;
  int sig;

  if (Debug)
  {
    fprintf(stderr, "In CleanUpProc - ");
    fprintf(stderr, "Backend with pid %d exited with status %d\n", pid, exitstatus);
    fflush(stderr);
  }
  /* -------------------------
   * If a backend dies in an ugly way (i.e. 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)
  {
      ListForEach( curr, BackendList, BackendList)
      {
        Backend *bp = (Backend *) ListElem(curr);

	if (bp->pid == pid)
	{
	  (void) free ((char *)bp);
	  ListDelete(curr);
	  break;
	}
      }
      return(STATUS_OK);
  }
  ListForEach( curr, BackendList, BackendList)
  {
    Backend *bp = (Backend *) ListElem(curr);
      
    /* -----------------
     * 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.
     * -----------------
     */
    if (SendStop)
	sig = SIGSTOP;
    else
	sig = SIGUSR1;
    if (bp->pid != pid)
    {
	if (Debug)
	  fprintf(stderr, "Sending %s to process %d\n",
		  (sig == SIGUSR1 ? "SIGUSR1" : "SIGSTOP"), bp->pid);
	kill(bp->pid, sig);
	ProcSemaphoreKill(bp->pid);
    }
    else
	ProcSemaphoreKill(bp->pid);

    (void) free ((char *)bp);
    ListDelete(curr);
  }
  /* -------------
   * 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)
  {
    if (Debug)
      fprintf(stderr, "Reinitializing shared memory and semaphores\n");
    quasi_exitpg();
    /* --------------
     * Recreate shared memory and semaphores.
     * --------------
     */
    CreateSharedMemoryAndSemaphores(
	  SystemPortAddressCreateIPCKey((SystemPortAddress)PostPortName));
    ActiveBackends = FALSE;
  }
    return(STATUS_OK);
}

/*
 * BackendStartup -- startup backend process
 *
 * returns: STATUS_ERROR if the fork/exec failed, STATUS_OK
 *	otherwise.
 *
 */
BackendStartup(port, packet, ioList)
Port		*port;
StartupPacket	*packet;   /* client's startup packet */
IOList		*ioList;   /* io devices to setup */
{
  int		status;	   /* return status */
  Backend 	*bp;	   /* info to be used for backend cleanup */
  char		dbData[PATH_SIZE];
  char		efData[ARGV_SIZE];
  char		*execFile;
  char		*database;  /* database after fixlead */
  int		size;	   /* of fixlead string */
  int		pid;

  database = dbData;
  execFile = efData;

  bp = (Backend *) zalloc(sizeof(Backend));
  if (! bp) {
    elog(FATAL,"Postmaster: cannot zalloc backend structure\n");
    ExitPostmaster(1);
  }
  ListPush(BackendList,bp);

  /*
   * If any of the arguments is missing, use the defaults.
   */

  /* Set up the backend as specified by the startup packet */

  if (* (packet->tty)) {	/* Stdout/Stderr will become the tty file */
    ioList[1].name = packet->tty;
    ioList[2].name = packet->tty;
  }

  if (! * (packet->user)) 
    strcpy(packet->user,DefaultStartup.user);

  size = PATH_SIZE;
  if (! * (packet->database)) 
    strcpy(database,DefaultStartup.database);
  else 
    (void) fixlead(packet->database,&database,&size);

  database = dbData;
    
  size = ARGV_SIZE;
  if (! * (packet->execFile))
    strcpy(execFile, DefaultStartup.execFile);
  else
    (void) fixlead(packet->execFile, &execFile, &size);

  execFile = efData;

  /*
   * Security: check the arguments.  There should be an authorized
   *	list of execFiles at the very least.
   */

  if ((pid = FORK()) == 0) {
    /* child: 
     * 	use the ioList structure to setup your file descriptors and
     * 	then exec the file requested.
	 */
	/*
	 * These have to be made big enough!
     */
    char	envEntry1[64];
    char	envEntry2[64];
    char	envEntry3[64];

    /* This goes to backend as command line arg!!!
     * dup2(port->sock,BACKEND_SOCK); 
     */
    status = DescriptorReset(ioList);
    if (status) {
      _exit(1);
    }

    /* Set up the necessary environment variables for the backend
     * This should really be some sort of message....
     */
#ifdef sprite
    { 
      char c[50];
      sprintf(c, "%d", PostPortName);
      setenv("POSTPORT", c);
      sprintf(c, "%d", NextBackendId);
      setenv("POSTID", c);
      sprintf(c, "%s", packet->user);
      setenv("PG_USER", c);
    }
#else

    sprintf(envEntry1, "POSTPORT=%d", PostPortName);
    putenv(envEntry1);
    sprintf(envEntry2, "POSTID=%d", NextBackendId);
    putenv(envEntry2);
    sprintf(envEntry3, "PG_USER=%s", packet->user);
    putenv(envEntry3);
    if (Debug) {
	printf("ENVIRONMENT: %s, %s, %s\n", envEntry1,envEntry2,envEntry3);
    }

#endif /* sprite */

    if (! DoExec(execFile, database, port->sock,packet)) {
      return;
    }

    /* error -- exec has failed if we get here. */
    elog(FATAL,"Postmaster: DoExec failed for '%s'\n",DefaultStartup.execFile);
    _exit(1);
  }

  if (Debug)
	fprintf(stderr,
		"started '%s' for '%s' on '%s' (%d) pid (%d)\n",
	 	execFile,packet->user, database,port->sock, pid);

  if (pid < 0) {
    fprintf(stderr, "PostMaster: fork failed");
    return(STATUS_ERROR);
  } 
  else
    bp->pid = pid;

  /* adjust backend counter */
  /* XXX Don't know why this is done, but for now backend needs it */
  NextBackendId -= 1;

  return(STATUS_OK);
}

/* 
 * fixlead -- Expand special characters from path names. 
 * 
 * returns: TRUE if expansion occurred FALSE otherwise.
 * SIDE EFFECTS: copies str into dst
 *
 * NOTES: this does not use the sizeP parameter.  It should
 *	check for string overflow.
 */
fixlead(str, dstP, sizeP)
char	**dstP, *str;
int	*sizeP;
{
  char *dst = *dstP;
  int	size = *sizeP;
  int	status = TRUE;

  switch (*str) {
    /* carriage return == NULL */
  case '\n':
    return(FALSE);

  case '~':

    /* home directory of postgres installation */
    strcpy(dst, Home);
    break;

  case '&':
    strcpy(dst, Home);
    strcat(dst, "/data/base/");
    strcat(dst, Dbname);
    break;

    /* directory of database */
  case '%':
    strcpy(dst, Home);
    strcat(dst, "/data");
    break;
    /* directory of global data (should be '&/..') */
  default:
    *dst = '\0';
    status = FALSE;
  }
  if (status)
    str++;

  strncat(dst,str,size);
  size = strlen(dst);
  *dstP += size;
  *sizeP -= size;
  return(status);
}


/*
 * DoExec -- setup the arguments and make an execv system call.
 *
 * returns: 
 *	if not debugging, we shouldn't return at all (exec()).
 *	if debugging, return 0.
 *	if exec fails, return 1.
 */
DoExec(execFile,database, portFd, pack)
char	*execFile,*database;
StartupPacket	*pack;
int 	portFd;
{
  int	status;
  char portBuf[32];
  char debugBuf[32];
  char startDir[PATH_SIZE];
  char args[2*ARGV_SIZE];
  
  /*
   * Call backend with any/all desired options.  These extra options
   * can either come from the front-end via the packet or from the
   * command line of the postmaster -o option.
   */
  args[0] = '\0';
  if (pack->options[0])
  	strcpy(args, pack->options);
  if (ExtraOptions[0])
	strcat(args, ExtraOptions);

  status = 1;

  strcpy(startDir, Home);
  strcat(startDir, "/data/base/");
  strcat(startDir, pack->database);
  if (chdir(startDir))
	perror(startDir);
  /* If debugging requested pass the request along to the backend */
  if (Debug)
	sprintf(debugBuf, "-d %d", Debug);	
  else
	sprintf(debugBuf, "-Q");

  sprintf(portBuf, "-P%d", portFd);
  if (Debug) {
	printf("The port file descriptor is %d\n", portFd);
	printf("execl: %s -p %s %s %s %s\n", 
		execFile, debugBuf, portBuf, database, args);
  }
  if (!(*args)) {
  status =
    execl(execFile, execFile, "-p", debugBuf, portBuf, database, NULL);
  }
  else {
    status =
      execl(execFile, execFile, "-p", debugBuf, portBuf, args, database, NULL);
  }
  return(status);
}


/*
 * DescriptorReset -- reset the servers file descriptors to
 *	match the configuration the new backend expects.
 *
 * This routine closes, stdin etc.  It'll set up a log file.
 */
DescriptorReset(ioList)
IOList	*ioList;
{
  /*
   * For each element of the io list: 
   *   If the name is NONE, close the corresponding file descriptor.
   *   If the name is DEFAULT, let the child inherit the parent's descriptors.
   *   Otherwise, open the named descriptor.
   */
  for (;ioList->name;ioList++) {
    if (! strcmp(ioList->name, "NONE")) {
      close(ioList->fd);
    } else if (strcmp(ioList->name,DEFAULT)) {
      int tmpfd;

      if (! (tmpfd = open(ioList->name, ioList->mode, OPEN_PERMISSIONS))) {
	perror(ioList->name);
	fprintf(stderr,"PostMaster: child cannot open tty '%s'\n",ioList->name);
	return(STATUS_ERROR);
      }
      close(ioList->fd);
      dup2(tmpfd,ioList->fd);
      close(tmpfd);
    }
  }
  return(STATUS_OK);
}

/*
 * ExitPostmaster -- cleanup
 */
ExitPostmaster(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);
}

/* Delete this once the simple list implementation is ready */

#define MAX_SIZE 450000
#define MALLOC_MAGICNO		'M'
#define PRINT_ALLOC(STR, STR1, NUM)
#define DO_ALLOC(size)
#define TOP_ADDR(str)
int TotalCacheAlloc;

int *
CacheAlloc(size)
unsigned int size;
{
  char *tmp;

  Assert(size);
  Assert(size < MAX_SIZE);
  /* have to add four bytes because zalloc is going to align the space */
  tmp = (char *)zalloc(size+4);
  Assert(tmp);
  TOP_ADDR(tmp);
  PRINT_ALLOC("CacheAlloc",tmp,size);
  DO_ALLOC(TotalCacheAlloc += size);

  *(tmp+3) = MALLOC_MAGICNO;
  tmp += 4;

  return((int *)tmp);
}

FindBackend(backend)
char *backend;
{
	char path[PATH_SIZE];
	char objDir[PATH_SIZE];
	char *envVar;
	struct stat buf;

	/* check $POSTGRESHOME/bin/postgres */

	envVar = getenv("POSTGRESHOME");
	if (envVar) {
		strncpy(path, envVar, PATH_SIZE-21);
		strcat(path, "/bin/postgres");
		if (ValidBackend(path)) {
			strcpy(backend, path);
			return;
		}
	}

	/* check $POSTTREE/bin/postgres */

	envVar = getenv("POSTTREE");
	if (envVar) {
		strncpy(path, envVar, PATH_SIZE-21);
		strcat(path, "/bin/postgres");
		if (ValidBackend(path)) {
			strcpy(backend, path);
			return;
		}
	}

	/* check $POSTTREE/<obj.dir>/support/postgres */

	ReadObjDir(objDir);

	if (objDir[0]) {
		strncpy(path, objDir, PATH_SIZE-21);
		strcat(path, "/support/postgres");
		if (ValidBackend(path)) {
			strcpy(backend, path);
			return;
		}
	}

	/* check /usr/postgres/bin/postgres */

	strncpy(path, "/usr/postgres/bin/postgres", PATH_SIZE);
	if (ValidBackend(path)) {
		strcpy(backend, path);
		return;
	}
	fprintf(stderr,"Could not find a backend to execute -- giving up...\n");
	fprintf(stderr,"Have you set POSTGRESHOME in your environment?\n");
	fprintf(stderr,"Have you installed everything successfully?\n");
	exit(1);
}

ValidBackend(path)
char *path;
{
	struct stat buf;

	if ( stat(path, &buf) < 0 )
		return (FALSE);
	else if ( ! (buf.st_mode & S_IEXEC) )
		return (FALSE);
	else
		return (TRUE);
}

ReadObjDir(dir)
char *dir;
{
	FILE *fp;
	char buf[256];
	char tmpFile[32];
	char *bufptr;
	char *envPtr;

	/* Look for $POSTTREE/newconf/config.mk */

	if ((envPtr = getenv("POSTTREE")) == NULL)
	{
		*dir = '\0';
		return;
	}

	/* make a temporary file */
	sprintf(tmpFile, "/tmp/pm.%d", getpid());

	sprintf(buf, 
		"fgrep OD= %s/newconf/config.mk | sed -e \'s/OD=\t//\' > %s",
		envPtr,
		tmpFile);

	system( buf );

	if ((fp = fopen(tmpFile, "r")) == NULL) {
		*dir = '\0';
		return;
	}

	fgets(buf, 255, fp);

	/* kill the annoying \n fgets always puts at the end of the line */

	bufptr = (char *)index(buf, '\n');
	if (bufptr)
		*bufptr = (char)NULL;
	strncpy(dir, buf, PATH_SIZE);

	fclose(fp);
	unlink(tmpFile);
	return;
}

char *
zalloc(size)

unsigned long size;

{
	char *t;

	t = (char *) malloc(size);
	bzero(t, size);
	return(t);
}
