/* ====================================================================
 * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */


/*
 * Resource allocation code... the code here is responsible for making
 * sure that nothing leaks.
 *
 * rst --- 4/95 --- 6/95
 */

#include "httpd.h"

#include <stdarg.h>

/*****************************************************************
 *
 * Managing free storage blocks...
 */

union align
{
  /* Types which are likely to have the longest RELEVANT alignment
   * restrictions...
   */
  
  char *cp;
  void (*f)();
  long l;
  FILE *fp;
  double d;
};

#define CLICK_SZ (sizeof(union align))

union block_hdr
{
  union align a;
  
  /* Actual header... */
  
  struct {
    char *endp;
    union block_hdr *next;
    char *first_avail;
  } h;
};

union block_hdr *block_freelist = NULL;



/* Get a completely new block from the system pool. Note that we rely on
malloc() to provide aligned memory. */

union block_hdr *malloc_block (int size)
{
  union block_hdr *blok =
    (union block_hdr *)malloc(size + sizeof(union block_hdr));

  if (blok == NULL) {
      fprintf (stderr, "Ouch!  malloc failed in malloc_block()\n");
      exit (1);
  }
  blok->h.next = NULL;
  blok->h.first_avail = (char *)(blok + 1);
  blok->h.endp = size + blok->h.first_avail;
  
  return blok;
}



void chk_on_blk_list (union block_hdr *blok, union block_hdr *free_blk)
{
  /* Debugging code.  Left in for the moment. */
    
  while (free_blk) {
    if (free_blk == blok) {
      fprintf (stderr, "Ouch!  Freeing free block\n");
      exit (1);
    }
    free_blk = free_blk->h.next;
  }
}

/* Free a chain of blocks --- must be called with alarms blocked. */

void free_blocks (union block_hdr *blok)
{
  /* First, put new blocks at the head of the free list ---
   * we'll eventually bash the 'next' pointer of the last block
   * in the chain to point to the free blocks we already had.
   */
  
  union block_hdr *old_free_list = block_freelist;

  if (blok == NULL) return;	/* Sanity check --- freeing empty pool? */
  
  block_freelist = blok;
  
  /*
   * Next, adjust first_avail pointers of each block --- have to do it
   * sooner or later, and it simplifies the search in new_block to do it
   * now.
   */

  while (blok->h.next != NULL) {
    chk_on_blk_list (blok, old_free_list);
    blok->h.first_avail = (char *)(blok + 1);
    blok = blok->h.next;
  }

  chk_on_blk_list (blok, old_free_list);
  blok->h.first_avail = (char *)(blok + 1);

  /* Finally, reset next pointer to get the old free blocks back */

  blok->h.next = old_free_list;
}




/* Get a new block, from our own free list if possible, from the system
 * if necessary.  Must be called with alarms blocked.
 */

union block_hdr *new_block (int min_size)
{
  union block_hdr **lastptr = &block_freelist;
  union block_hdr *blok = block_freelist;
  
  /* First, see if we have anything of the required size
   * on the free list...
   */

  while (blok != NULL) {
    if (min_size + BLOCK_MINFREE <= blok->h.endp - blok->h.first_avail) {
      *lastptr = blok->h.next;
      blok->h.next = NULL;
      return blok;
    }
    else {
      lastptr = &blok->h.next;
      blok = blok->h.next;
    }
  }

  /* Nope. */

  min_size += BLOCK_MINFREE;
  return malloc_block((min_size > BLOCK_MINALLOC) ? min_size : BLOCK_MINALLOC);
}



/* Accounting */

long bytes_in_block_list (union block_hdr *blok)
{
  long size = 0;

  while (blok) {
    size += blok->h.endp - (char *)(blok + 1);
    blok = blok->h.next;
  }

  return size;
}


/*****************************************************************
 *
 * Pool internals and management...
 * NB that subprocesses are not handled by the generic cleanup code,
 * basically because we don't want cleanups for multiple subprocesses
 * to result in multiple three-second pauses.
 */

struct process_chain;
struct cleanup;

static void run_cleanups (struct cleanup *);
static void free_proc_chain (struct process_chain *);

struct pool {
  union block_hdr *first;
  union block_hdr *last;
  struct cleanup *cleanups;
  struct process_chain *subprocesses;
  struct pool *sub_pools;
  struct pool *sub_next;
  struct pool *sub_prev;
  struct pool *parent;
  char *free_first_avail;
};

pool *permanent_pool;

/* Each pool structure is allocated in the start of its own first block,
 * so we need to know how many bytes that is (once properly aligned...).
 * This also means that when a pool's sub-pool is destroyed, the storage
 * associated with it is *completely* gone, so we have to make sure it
 * gets taken off the parent's sub-pool list...
 */

#define POOL_HDR_CLICKS (1 + ((sizeof(struct pool) - 1) / CLICK_SZ))
#define POOL_HDR_BYTES (POOL_HDR_CLICKS * CLICK_SZ)			 

struct pool *make_sub_pool (struct pool *p)
{
  union block_hdr *blok;
  pool *new_pool;

  block_alarms();
  
  blok = new_block (0);
  new_pool = (pool *)blok->h.first_avail;
  blok->h.first_avail += POOL_HDR_BYTES;

  memset ((char *)new_pool, '\0', sizeof (struct pool));
  new_pool->free_first_avail = blok->h.first_avail;
  new_pool->first = new_pool->last = blok;
    
  if (p) {
    new_pool->parent = p;
    new_pool->sub_next = p->sub_pools;
    if (new_pool->sub_next) new_pool->sub_next->sub_prev = new_pool;
    p->sub_pools = new_pool;
  }
  
  unblock_alarms();
  
  return new_pool;
}

void init_alloc() { permanent_pool = make_sub_pool (NULL); }

void clear_pool (struct pool *a)
{
  block_alarms();
  
  while (a->sub_pools)
    destroy_pool (a->sub_pools);
    
  a->sub_pools = NULL;
  
  run_cleanups (a->cleanups);        a->cleanups = NULL;
  free_proc_chain (a->subprocesses); a->subprocesses = NULL;
  free_blocks (a->first->h.next);    a->first->h.next = NULL;

  a->last = a->first;
  a->first->h.first_avail = a->free_first_avail;

  unblock_alarms();
}

void destroy_pool (pool *a)
{
  block_alarms();
  clear_pool (a);

  if (a->parent) {
    if (a->parent->sub_pools == a) a->parent->sub_pools = a->sub_next;
    if (a->sub_prev) a->sub_prev->sub_next = a->sub_next;
    if (a->sub_next) a->sub_next->sub_prev = a->sub_prev;
  }
  
  free_blocks (a->first);
  unblock_alarms();
}

long bytes_in_pool (pool *p) { return bytes_in_block_list (p->first); }
long bytes_in_free_blocks () { return bytes_in_block_list (block_freelist); }

/*****************************************************************
 *
 * Allocating stuff...
 */


void *palloc (struct pool *a, int reqsize)
{
  /* Round up requested size to an even number of alignment units (core clicks)
   */
  
  int nclicks = 1 + ((reqsize - 1) / CLICK_SZ);
  int size = nclicks * CLICK_SZ;

  /* First, see if we have space in the block most recently
   * allocated to this pool
   */
  
  union block_hdr *blok = a->last; 
  char *first_avail = blok->h.first_avail;
  char *new_first_avail;

  if(reqsize <= 0)
      return NULL;
  
  new_first_avail = first_avail + size;
  
  if (new_first_avail <= blok->h.endp) {
    blok->h.first_avail = new_first_avail;
    return (void *)first_avail;
  }

  /* Nope --- get a new one that's guaranteed to be big enough */
  
  block_alarms();
  blok = new_block (size);
  a->last->h.next = blok;
  a->last = blok;
  unblock_alarms();

  first_avail = blok->h.first_avail;
  blok->h.first_avail += size;

  return (void *)first_avail;
}

void *pcalloc(struct pool *a, int size)
{
  void *res = palloc (a, size);
  memset (res, '\0', size);
  return res;
}

char *pstrdup(struct pool *a, const char *s)
{
  char *res;
  if (s == NULL) return NULL;
  res = palloc (a, strlen(s) + 1);
  strcpy (res, s);
  return res;
}

char *pstrndup(struct pool *a, const char *s, int n)
{
  char *res;
  if (s == NULL) return NULL;
  res = palloc (a, n + 1);
  strncpy (res, s, n);
  res[n] = '\0';
  return res;
}

char *pstrcat(pool *a, ...)
{
  char *cp, *argp, *res;
  
  /* Pass one --- find length of required string */
  
  int len = 0;
  va_list adummy;
  
  va_start (adummy, a);

  while ((cp = va_arg (adummy, char *)) != NULL) 
    len += strlen(cp);

  va_end (adummy);

  /* Allocate the required string */

  res = (char *)palloc(a, len + 1);
  cp = res;

  /* Pass two --- copy the argument strings into the result space */

  va_start (adummy, a);
  
  while ((argp = va_arg (adummy, char *)) != NULL) {
    strcpy (cp, argp);
    cp += strlen(argp);
  }

  va_end (adummy);

  /* Return the result string */

  return res;
}


/*****************************************************************
 *
 * The 'array' functions...
 */

array_header *make_array (pool *p, int nelts, int elt_size)
{
  array_header *res = (array_header *)palloc(p, sizeof(array_header));

  if (nelts < 1) nelts = 1;	/* Assure sanity if someone asks for
				 * array of zero elts.
				 */
  
  res->elts = pcalloc (p, nelts * elt_size);
  
  res->pool = p;
  res->elt_size = elt_size;
  res->nelts = 0;		/* No active elements yet... */
  res->nalloc = nelts;		/* ...but this many allocated */

  return res;
}

void *push_array (array_header *arr)
{
  if (arr->nelts == arr->nalloc) {
    int new_size = (arr->nalloc <= 0) ? 1 : arr->nalloc * 2;
    char *new_data;
    
    new_data = pcalloc (arr->pool, arr->elt_size * new_size);

    memcpy (new_data, arr->elts, arr->nalloc * arr->elt_size);
    arr->elts = new_data;
    arr->nalloc = new_size;
  }

  ++arr->nelts;
  return arr->elts + (arr->elt_size * (arr->nelts - 1));
}

void array_cat (array_header *dst, const array_header *src)
{
  int elt_size = dst->elt_size;
  
  if (dst->nelts + src->nelts > dst->nalloc) {
    int new_size = (dst->nalloc <= 0) ? 1 : dst->nalloc * 2;
    char *new_data;

    while (dst->nelts + src->nelts > new_size)
      new_size *= 2;

    new_data = pcalloc (dst->pool, elt_size * new_size);
    memcpy (new_data, dst->elts, dst->nalloc * elt_size);
    
    dst->elts = new_data;
    dst->nalloc = new_size;
  }

  memcpy (dst->elts + dst->nelts * elt_size, src->elts, elt_size * src->nelts);
  dst->nelts += src->nelts;
}

array_header *copy_array (pool *p, const array_header *arr)
{
  array_header *res = make_array (p, arr->nalloc, arr->elt_size);

  memcpy (res->elts, arr->elts, arr->elt_size * arr->nelts);
  res->nelts = arr->nelts;
  return res;
}

/* This cute function copies the array header *only*, but arranges
 * for the data section to be copied on the first push or arraycat.
 * It's useful when the elements of the array being copied are
 * read only, but new stuff *might* get added on the end; we have the
 * overhead of the full copy only where it is really needed.
 */

array_header *copy_array_hdr (pool *p, const array_header *arr)
{
  array_header *res = (array_header *)palloc(p, sizeof(array_header));

  res->elts = arr->elts;
  
  res->pool = p;
  res->elt_size = arr->elt_size;
  res->nelts = arr->nelts;
  res->nalloc = arr->nelts;	/* Force overflow on push */

  return res;
}

/* The above is used here to avoid consing multiple new array bodies... */

array_header *append_arrays (pool *p,
			     const array_header *first,
			     const array_header *second)
{
  array_header *res = copy_array_hdr (p, first);

  array_cat (res, second);
  return res;
}


/*****************************************************************
 *
 * The "table" functions.
 */

table *make_table (pool *p, int nelts) {
    return make_array (p, nelts, sizeof (table_entry));
}

table *copy_table (pool *p, const table *t) {
    return copy_array (p, t);
}

void clear_table (table *t)
{
    t->nelts = 0;
}

array_header *table_elts (table *t) { return t; }

char *table_get (const table *t, const char *key)
{
    table_entry *elts = (table_entry *)t->elts;
    int i;

    if (key == NULL) return NULL;
    
    for (i = 0; i < t->nelts; ++i)
        if (!strcasecmp (elts[i].key, key))
	    return elts[i].val;

    return NULL;
}

void table_set (table *t, const char *key, const char *val)
{
    register int i, j, k;
    table_entry *elts = (table_entry *)t->elts;
    int done = 0;

    for (i = 0; i < t->nelts; ++i)
	if (!strcasecmp (elts[i].key, key)) {
	    if (!done) {
	        elts[i].val = pstrdup(t->pool, val);
	        done = 1;
	    }
	    else {     /* delete an extraneous element */
                for (j = i, k = i + 1; k < t->nelts; ++j, ++k) {
                    elts[j].key = elts[k].key;
                    elts[j].val = elts[k].val;
                }
                --t->nelts;
	    }
	}

    if (!done) {
        elts = (table_entry *)push_array(t);
        elts->key = pstrdup (t->pool, key);
        elts->val = pstrdup (t->pool, val);
    }
}

void table_unset( table *t, const char *key ) 
{
    register int i, j, k;   
    table_entry *elts = (table_entry *)t->elts;
 
    for (i = 0; i < t->nelts; ++i)
        if (!strcasecmp (elts[i].key, key)) {
 
            /* found an element to skip over
             * there are any number of ways to remove an element from
             * a contiguous block of memory.  I've chosen one that
             * doesn't do a memcpy/bcopy/array_delete, *shrug*...
             */
            for (j = i, k = i + 1; k < t->nelts; ++j, ++k) {
                elts[j].key = elts[k].key;
                elts[j].val = elts[k].val;
            }
            --t->nelts;
        }
}     

void table_merge (table *t, const char *key, const char *val)
{
    table_entry *elts = (table_entry *)t->elts;
    int i;

    for (i = 0; i < t->nelts; ++i)
        if (!strcasecmp (elts[i].key, key)) {
	    elts[i].val = pstrcat (t->pool, elts[i].val, ", ", val, NULL);
	    return;
	}

    elts = (table_entry *)push_array(t);
    elts->key = pstrdup (t->pool, key);
    elts->val = pstrdup (t->pool, val);
}

void table_add (table *t, const char *key, const char *val)
{
    table_entry *elts = (table_entry *)t->elts;

    elts = (table_entry *)push_array(t);
    elts->key = pstrdup (t->pool, key);
    elts->val = pstrdup (t->pool, val);
}

table* overlay_tables (pool *p, const table *overlay, const table *base)
{
    return append_arrays (p, overlay, base);
}

/* And now for something completely abstract ...
 *
 * For each key value given as a vararg:
 *   run the function pointed to as
 *     int comp(void *r, char *key, char *value);
 *   on each valid key-value pair in the table t that matches the vararg key,
 *   or once for every valid key-value pair if the vararg list is empty,
 *   until the function returns false (0) or we finish the table.
 *
 * Note that we restart the traversal for each vararg, which means that
 * duplicate varargs will result in multiple executions of the function
 * for each matching key.  Note also that if the vararg list is empty,
 * only one traversal will be made and will cut short if comp returns 0.
 *
 * Note that the table_get and table_merge functions assume that each key in
 * the table is unique (i.e., no multiple entries with the same key).  This
 * function does not make that assumption, since it (unfortunately) isn't
 * true for some of Apache's tables.
 *
 * Note that rec is simply passed-on to the comp function, so that the
 * caller can pass additional info for the task.
 */
void table_do (int (*comp)(void *, const char *, const char *), void *rec,
               const table *t, ...)
{
    va_list vp;
    char *argp;
    table_entry *elts = (table_entry *)t->elts;
    int rv, i;
  
    va_start(vp, t);

    argp = va_arg(vp, char *);

    do {
        for (rv = 1, i = 0; rv && (i < t->nelts); ++i) {
            if (elts[i].key && (!argp || !strcasecmp(elts[i].key, argp))) {
                rv = (*comp)(rec, elts[i].key, elts[i].val);
            }
        }
    } while (argp && ((argp = va_arg(vp, char *)) != NULL));

    va_end(vp);
}

/*****************************************************************
 *
 * Managing generic cleanups.  
 */

struct cleanup {
  void *data;
  void (*plain_cleanup)(void *);
  void (*child_cleanup)(void *);
  struct cleanup *next;
};

void register_cleanup (pool *p, void *data, void (*plain_cleanup)(void *),
		       void (*child_cleanup)(void *))
{
  struct cleanup *c = (struct cleanup *)palloc(p, sizeof (struct cleanup));
  c->data = data;
  c->plain_cleanup = plain_cleanup;
  c->child_cleanup = child_cleanup;
  c->next = p->cleanups;
  p->cleanups = c;
}

void kill_cleanup (pool *p, void *data, void (*cleanup)(void *))
{
  struct cleanup *c = p->cleanups;
  struct cleanup **lastp = &p->cleanups;
    
  while (c) {
    if (c->data == data && c->plain_cleanup == cleanup) {
      *lastp = c->next;
      break;
    }

    lastp = &c->next;
    c = c->next;
  }
}

void run_cleanup (pool *p, void *data, void (*cleanup)(void *))
{
  block_alarms();		/* Run cleanup only once! */
  (*cleanup)(data);
  kill_cleanup (p, data, cleanup);
  unblock_alarms();
}

static void run_cleanups (struct cleanup *c)
{
  while (c) {
    (*c->plain_cleanup)(c->data);
    c = c->next;
  }
}

static void run_child_cleanups (struct cleanup *c)
{
  while (c) {
    (*c->child_cleanup)(c->data);
    c = c->next;
  }
}

static void cleanup_pool_for_exec (pool *p)
{
  run_child_cleanups (p->cleanups);
  p->cleanups = NULL;

  for (p = p->sub_pools; p; p = p->sub_next)
    cleanup_pool_for_exec (p);
}

void cleanup_for_exec()
{
  block_alarms();
  cleanup_pool_for_exec (permanent_pool);
  unblock_alarms();
}

/*****************************************************************
 *
 * Files and file descriptors; these are just an application of the
 * generic cleanup interface.
 */

static void fd_cleanup (void *fdv) { close ((int)fdv); }

void note_cleanups_for_fd (pool *p, int fd) {
  register_cleanup (p, (void *)fd, fd_cleanup, fd_cleanup);
}

void kill_cleanups_for_fd(pool *p,int fd)
    {
    kill_cleanup(p,(void *)fd,fd_cleanup);
    }

int popenf(pool *a, const char *name, int flg, int mode)
{
  int fd;
  int save_errno;

  block_alarms();
  fd = open(name, flg, mode);
  save_errno = errno;
  if (fd >= 0) {
    fd = ap_slack (fd, AP_SLACK_HIGH);
    note_cleanups_for_fd (a, fd);
  }
  unblock_alarms();
  errno = save_errno;
  return fd;
}

int pclosef(pool *a, int fd)
{
  int res;
  int save_errno;
  
  block_alarms();
  res = close(fd);
  save_errno = errno;
  kill_cleanup(a, (void *)fd, fd_cleanup);
  unblock_alarms();
  errno = save_errno;
  return res;
}

/* Note that we have separate plain_ and child_ cleanups for FILE *s,
 * since fclose() would flush I/O buffers, which is extremely undesirable;
 * we just close the descriptor.
 */

static void file_cleanup (void *fpv) { fclose ((FILE *)fpv); }
static void file_child_cleanup (void *fpv) { close (fileno ((FILE *)fpv)); }

void note_cleanups_for_file (pool *p, FILE *fp) {
  register_cleanup (p, (void *)fp, file_cleanup, file_child_cleanup);
}

FILE *pfopen(pool *a, const char *name, const char *mode)
{
  FILE *fd = NULL;
  int baseFlag, desc;

  block_alarms();

  if (*mode == 'a') {
    /* Work around faulty implementations of fopen */
    baseFlag = (*(mode+1) == '+') ? O_RDWR : O_WRONLY;
    desc = open(name, baseFlag | O_APPEND | O_CREAT,
		S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if (desc >= 0) {
      desc = ap_slack(desc, AP_SLACK_LOW);
      fd = fdopen(desc, mode);
    }
  } else {
    fd = fopen(name, mode);
  }

  if (fd != NULL) note_cleanups_for_file (a, fd);
  unblock_alarms();
  return fd;
}

FILE *pfdopen(pool *a,int fd, const char *mode)
{
  FILE *f;

  block_alarms();
  f=fdopen(fd,mode);
  if(f != NULL)
    note_cleanups_for_file(a,f);
  unblock_alarms();
  return f;
}


int pfclose(pool *a, FILE *fd)
{
  int res;
  
  block_alarms();
  res = fclose(fd);
  kill_cleanup(a, (void *)fd, file_cleanup);
  unblock_alarms();
  return res;
}

/*
 * Here's a pool-based interface to POSIX regex's regcomp().
 * Note that we return regex_t instead of being passed one.
 * The reason is that if you use an already-used regex_t structure,
 * the memory that you've already allocated gets forgotten, and
 * regfree() doesn't clear it. So we don't allow it.
 */

static void regex_cleanup (void *preg) { regfree ((regex_t *)preg); }

regex_t *pregcomp(pool *p, const char *pattern, int cflags) {
    regex_t *preg = palloc(p, sizeof(regex_t));

    if (regcomp(preg, pattern, cflags))
	return NULL;

    register_cleanup (p, (void *)preg, regex_cleanup, regex_cleanup);

    return preg;
}


void pregfree(pool *p, regex_t *reg)
{
    block_alarms();
    regfree (reg);
    kill_cleanup (p, (void *)reg, regex_cleanup);
    unblock_alarms();
}

/*****************************************************************
 *
 * More grotty system stuff... subprocesses.  Frump.  These don't use
 * the generic cleanup interface because I don't want multiple
 * subprocesses to result in multiple three-second pauses; the
 * subprocesses have to be "freed" all at once.  If someone comes
 * along with another resource they want to allocate which has the
 * same property, we might want to fold support for that into the
 * generic interface, but for now, it's a special case
 */

struct process_chain {
  pid_t pid;
  enum kill_conditions kill_how;
  struct process_chain *next;
};

void note_subprocess (pool *a, int pid, enum kill_conditions how)
{
  struct process_chain *new =
    (struct process_chain *)palloc(a, sizeof(struct process_chain));

  new->pid = pid;
  new->kill_how = how;
  new->next = a->subprocesses;
  a->subprocesses = new;
}

int spawn_child_err (pool *p, void (*func)(void *), void *data,
		     enum kill_conditions kill_how,
		     FILE **pipe_in, FILE **pipe_out, FILE **pipe_err)
{
  int pid;
  int in_fds[2];
  int out_fds[2];
  int err_fds[2];
  int save_errno;

  block_alarms();
  
  if (pipe_in && pipe (in_fds) < 0)
  {
      save_errno = errno;
      unblock_alarms();
      errno = save_errno;
      return 0;
  }
  
  if (pipe_out && pipe (out_fds) < 0) {
    save_errno = errno;
    if (pipe_in) {
      close (in_fds[0]); close (in_fds[1]);
    }
    unblock_alarms();
    errno = save_errno;
    return 0;
  }

  if (pipe_err && pipe (err_fds) < 0) {
    save_errno = errno;
    if (pipe_in) {
      close (in_fds[0]); close (in_fds[1]);
    }
    if (pipe_out) {
      close (out_fds[0]); close (out_fds[1]);
    }
    unblock_alarms();
    errno = save_errno;
    return 0;
  }

  if ((pid = fork()) < 0) {
    save_errno = errno;
    if (pipe_in) {
      close (in_fds[0]); close (in_fds[1]);
    }
    if (pipe_out) {
      close (out_fds[0]); close (out_fds[1]);
    }
    if (pipe_err) {
      close (err_fds[0]); close (err_fds[1]);
    }
    unblock_alarms();
    errno = save_errno;
    return 0;
  }

  if (!pid) {
    /* Child process */
    
    if (pipe_out) {
      close (out_fds[0]);
      dup2 (out_fds[1], STDOUT_FILENO);
      close (out_fds[1]);
    }

    if (pipe_in) {
      close (in_fds[1]);
      dup2 (in_fds[0], STDIN_FILENO);
      close (in_fds[0]);
    }

    if (pipe_err) {
      close (err_fds[0]);
      dup2 (err_fds[1], STDERR_FILENO);
      close (err_fds[1]);
    }

    /* HP-UX SIGCHLD fix goes here, if someone will remind me what it is... */
    signal (SIGCHLD, SIG_DFL);	/* Was that it? */
    
    func (data);
    exit (0);			/* Should never get here... */
  }

  /* Parent process */

  note_subprocess (p, pid, kill_how);
  
  if (pipe_out) {
    close (out_fds[1]);
#ifdef __EMX__
    /* Need binary mode set for OS/2. */
    *pipe_out = fdopen (out_fds[0], "rb");
#else
    *pipe_out = fdopen (out_fds[0], "r");
#endif  
  
    if (*pipe_out) note_cleanups_for_file (p, *pipe_out);
  }

  if (pipe_in) {
    close (in_fds[0]);
#ifdef __EMX__
    /* Need binary mode set for OS/2 */
    *pipe_in = fdopen (in_fds[1], "wb");
#else
    *pipe_in = fdopen (in_fds[1], "w");
#endif
    
    if (*pipe_in) note_cleanups_for_file (p, *pipe_in);
  }

  if (pipe_err) {
    close (err_fds[1]);
#ifdef __EMX__
    /* Need binary mode set for OS/2. */
    *pipe_err = fdopen (err_fds[0], "rb");
#else
    *pipe_err = fdopen (err_fds[0], "r");
#endif
  
    if (*pipe_err) note_cleanups_for_file (p, *pipe_err);
  }

  unblock_alarms();
  return pid;
}

static void free_proc_chain (struct process_chain *procs)
{
  /* Dispose of the subprocesses we've spawned off in the course of
   * whatever it was we're cleaning up now.  This may involve killing
   * some of them off...
   */

  struct process_chain *p;
  int need_timeout = 0;
  int status;

  if (procs == NULL) return;	/* No work.  Whew! */

  /* First, check to see if we need to do the SIGTERM, sleep, SIGKILL
   * dance with any of the processes we're cleaning up.  If we've got
   * any kill-on-sight subprocesses, ditch them now as well, so they
   * don't waste any more cycles doing whatever it is that they shouldn't
   * be doing anymore.
   */

#ifndef NEED_WAITPID
  /* Pick up all defunct processes */
  for (p = procs; p; p = p->next) {
    if (waitpid (p->pid, (int *) 0, WNOHANG) > 0) {
      p->kill_how = kill_never;
    }
  }
#endif

  for (p = procs; p; p = p->next) {
    if (p->kill_how == kill_after_timeout) {
      /* Subprocess may be dead already.  Only need the timeout if not. */
      if (kill (p->pid, SIGTERM) != -1)
	need_timeout = 1;
    } else if (p->kill_how == kill_always) {
      kill (p->pid, SIGKILL);
    }
  }

  /* Sleep only if we have to... */

  if (need_timeout) sleep (3);

  /* OK, the scripts we just timed out for have had a chance to clean up
   * --- now, just get rid of them, and also clean up the system accounting
   * goop...
   */

  for (p = procs; p; p = p->next){
    
    if (p->kill_how == kill_after_timeout) 
      kill (p->pid, SIGKILL);

    if (p->kill_how != kill_never)
      waitpid (p->pid, &status, 0);
  }
}

