dbm/
misc/
old-docs/
/* db.c */

#include "copyright.h"
#include "config.h"

#include <stdio.h>
#ifdef STRING_H
#include <string.h>
#else
#include <strings.h>
#endif				/* STRING_H */
#include <fcntl.h>
#include <sys/types.h>
#ifndef NO_UIO_H
#include <sys/uio.h>
#endif				/* NO_UIO_H */
#ifdef STDDEF_H
#include <stddef.h>
#else
#define size_t unsigned
#endif				/* STDDEF_H */

#include "teeny.h"
#include "db.h"

/*
 * Primary DB handling routines. Uses the caching routines and stuff to
 * access object data, etc.. You know.
 * 
 * read_descriptors() is the function to call to bring up an existing DB.
 * initialize_db() will bring up a blank DB.
 * 
 * Provides the following funtions to implement the DB:
 * 
 * get_int_elt(), get_str_elt(), get_lock_elt()
 * 
 * These return various data from an object. Not that the latter two return
 * literal pointers to the object data, so a) don't spam it, and b) it may be
 * swapped out, so use it soon, or hide it away somewhere safe.
 * 
 * set_int_elt(), set_str_elt(), set_lock_elt()
 * 
 * These store data away in objects. Note that set_str_elt() *does* allocate
 * fresh new memory for the string, but set_lock_elt() does *not* allocate
 * memory, since bool_parse() assembles locks into freshly allocated memory.
 * 
 * exists_object()
 * 
 * Probes the DB, returns 1 if the object does exist, 0 if not.
 * 
 * 
 * create_obj(), destroy_obj()
 * 
 * do the obvious things.
 * 
 * db_top()
 * 
 * returns 1 + the largest object number of anything in the DB. Note that there
 * are quite possibly garbage objects with lower object numbers. Use
 * exists_object().
 * 
 */

/*
 * #define LOADDEBUG
 */

/*
 * This is what we use to reference objects by number, a big array of
 * pointers to descriptors. Garbage object slots are indicated by a NULL
 * entry. Ooog. This is way inefficient. I don't like it.
 * 
 */

struct dsc    **main_index;

int             total_objects;	/* Number of real objects/garbage objects */
int             actual_objects;	/* Number of real objects */
int             garbage_count;	/* number of garbage objects */
int             slack;		/* Space after all the real objects/garbage
				 * in the */

/* main index, in # of objects */
int             growth_increment;	/* Amount to grow the main index, in
					 * # of objects */

struct obj_data *lookup_obj();

char           *realloc();

#ifdef COMPRESS

/*
 * NOTEs on compression: if you use COMPRESS, a pointer returned by
 * get_str_elt() will become very invalid upon another call to get_str_elt()
 * in too many cases... i will *try* to get it debugged before release.
 * --jason
 */

extern char    *compress();
extern char    *uncompress();

#endif

int             db_top()
{
  return (total_objects);
}

/*
 * Routines to dig elements out of objects. They return an error status of 0
 * if OK, -1 if not.
 * 
 */

int             get_str_elt(num, elt, ret)
  int             num, elt;
  char          **ret;

{
  struct dsc     *thedsc;
  struct obj_data *theobj;

  if (elt == NAME && num >= 0 && num < total_objects) {
    thedsc = main_index[num];
    if (thedsc == NULL) {
      return (-1);
    } else {
      *ret = DSC_NAME(thedsc);
      return (0);
    }
  }
  /* It's not in the descriptor, so go get the object data */

  if ((theobj = lookup_obj(num)) == NULL) {
    return (-1);
  }
  switch (elt) {
  case SUC:
    *ret = theobj->suc;
    break;
  case OSUC:
    *ret = theobj->osuc;
    break;
  case FAIL:
    *ret = theobj->fail;
    break;
  case OFAIL:
    *ret = theobj->ofail;
    break;
  case DROP:
    *ret = theobj->drop;
    break;
  case ODROP:
    *ret = theobj->odrop;
    break;
  case DESC:
    *ret = theobj->desc;
    break;
  case ENTER:
    *ret = theobj->enter;
    break;
  case OENTER:
    *ret = theobj->oenter;
    break;
  case EFAIL:
    *ret = theobj->efail;
    break;
  case OEFAIL:
    *ret = theobj->oefail;
    break;
  case OXENTER:
    *ret = theobj->oxent;
    break;
  case LEAVE:
    *ret = theobj->leave;
    break;
  case OLEAVE:
    *ret = theobj->oleave;
    break;
  case OXLEAVE:
    *ret = theobj->oxlea;
    break;
  case IDESC:
    *ret = theobj->idesc;
    break;
  case ODESC:
    *ret = theobj->odesc;
    break;
  case SITE:
    *ret = theobj->site;
    break;
  case PASSWORD:
    *ret = theobj->password;
    break;
  case KILL:
    *ret = theobj->kill;
    break;
  case OKILL:
    *ret = theobj->okill;
    break;
  case OTELEPORT:
    *ret = theobj->otel;
    break;
  case OXTELEPORT:
    *ret = theobj->oxtel;
    break;

  default:
    warning("get_str_elt", "invalid element code");
    return (-1);
    break;
  }
#ifdef COMPRESS
  /* this is spoooky */
  if (elt != PASSWORD) {
    *ret = uncompress(*ret);
  }
#endif
  return (0);
}

int             get_int_elt(num, elt, ret)
  int             num, elt;
  long           *ret;

{
  struct dsc     *thedsc;
  struct obj_data *theobj;

  /* Check for things that live right in the descriptor */

  if ((elt == FLAGS || elt == NEXT || elt == OWNER || elt == HOME)
      && num >= 0 && num < total_objects) {
    thedsc = main_index[num];
    if (thedsc == NULL) {
      return (-1);
    } else {
      switch (elt) {
      case FLAGS:
	*ret = DSC_FLAGS(thedsc);
	break;
      case NEXT:
	*ret = thedsc->list_next;
	break;
      case OWNER:
	*ret = DSC_OWNER(thedsc);
	break;
      case HOME:
	/* case DESTINATION: */
	/* case DROPTO: */
	*ret = thedsc->home_dropto;
	break;
      }
      return (0);
    }
  }
  /* It's not in the descriptor, so go get the object data */

  if ((theobj = lookup_obj(num)) == NULL) {
    return (-1);
  }
  switch (elt) {
  case QUOTA:
    *ret = (long) theobj->pennies;
    break;
  case LOC:
    *ret = theobj->loc;
    break;
  case CONTENTS:
    *ret = theobj->contents;
    break;
  case EXITS:
    *ret = theobj->exits;
    break;
  case ROOMS:
    *ret = theobj->rooms;
    break;
  case TIMESTAMP:
    *ret = theobj->timestamp;
    break;
  default:
    warning("get_int_elt", "invalid element code");
    return (-1);
    break;
  }
  return (0);
}

int             get_lock_elt(num, elt, ret)
  int             num, elt;
  int           **ret;

{
  struct obj_data *theobj;

  if ((elt != LOCK) && (elt != ELOCK) && (elt != DESTINATIONS)) {
    warning("get_lock_elt", "bad elt code");
    return (-1);
  }
  if ((theobj = lookup_obj(num)) == NULL) {
    return (-1);
  }
  switch (elt) {
  case LOCK:
    *ret = theobj->lock;
    break;
  case ELOCK:
    *ret = theobj->elock;
    break;
  case DESTINATIONS:
    *ret = theobj->destinations;
    break;
  }
  return (0);
}

/*
 * These set the values of elements. They return 0 is OK, -1 if not.
 */

int             set_str_elt(num, elt, value)
  int             num, elt;
  char           *value;

{
  struct dsc     *thedsc;
  struct obj_data *theobj;
  int             newsize;
  int             valsize;
  char          **thestr;
  extern int      cache_usage;

  if (num < 0 || num >= total_objects)
    return (-1);

  /* Check the descriptor resident field first */

  if (elt == NAME) {
    thedsc = main_index[num];
    if (thedsc == NULL) {
      return (-1);
    } else {
      thestr = &(DSC_NAME(thedsc));
    }
    if (value == NULL) {
      valsize = 0;
    } else {
      valsize = strlen(value);
    }
  } else {

    /* It's not in the descriptor, so go get the object data */

    if (num >= total_objects || (thedsc = main_index[num]) == NULL) {
      return (-1);
    }
    if ((theobj = lookup_obj(num)) == NULL) {
      return (-1);
    }
    switch (elt) {
    case SUC:
      thestr = &(theobj->suc);
      break;
    case OSUC:
      thestr = &(theobj->osuc);
      break;
    case FAIL:
      thestr = &(theobj->fail);
      break;
    case OFAIL:
      thestr = &(theobj->ofail);
      break;
    case DROP:
      thestr = &(theobj->drop);
      break;
    case ODROP:
      thestr = &(theobj->odrop);
      break;
    case DESC:
      thestr = &(theobj->desc);
      break;
    case ENTER:
      thestr = &(theobj->enter);
      break;
    case OENTER:
      thestr = &(theobj->oenter);
      break;
    case EFAIL:
      thestr = &(theobj->efail);
      break;
    case OEFAIL:
      thestr = &(theobj->oefail);
      break;
    case OXENTER:
      thestr = &(theobj->oxent);
      break;
    case LEAVE:
      thestr = &(theobj->leave);
      break;
    case OLEAVE:
      thestr = &(theobj->oleave);
      break;
    case OXLEAVE:
      thestr = &(theobj->oxlea);
      break;
    case IDESC:
      thestr = &(theobj->idesc);
      break;
    case ODESC:
      thestr = &(theobj->odesc);
      break;
    case SITE:
      thestr = &(theobj->site);
      break;
    case PASSWORD:
      thestr = &(theobj->password);
      break;
    case KILL:
      thestr = &(theobj->kill);
      break;
    case OKILL:
      thestr = &(theobj->okill);
      break;
    case OTELEPORT:
      thestr = &(theobj->otel);
      break;
    case OXTELEPORT:
      thestr = &(theobj->oxtel);
      break;
    default:
      warning("set_str_elt", "invalid element code");
      return (-1);
      break;
    }

    /* Recompute the on disk size. */

#ifdef COMPRESS
    if ((value != NULL) && (elt != NAME) && (elt != PASSWORD)) {
      value = compress(value);
    }
#endif
    if (value == NULL)
      valsize = 0;
    else
      valsize = strlen(value);

    if (*thestr == NULL) {
      newsize = DSC_SIZE(thedsc) + valsize;
    } else {
      newsize = (DSC_SIZE(thedsc) - strlen(*thestr))
	+ valsize;
    }

    cache_usage = (cache_usage - DSC_SIZE(thedsc)) + newsize;
    DSC_SIZE(thedsc) = newsize;
    DSC_FLAGS(thedsc) |= DIRTY;
  }

  /* Free old value. ty_free() copes with NULL */

  ty_free(*thestr);
  if (value == NULL) {
    *thestr = NULL;
  } else {
    *thestr = ty_malloc(valsize + 1, "set_str_elt");
    strcpy(*thestr, value);
  }

  return (0);
}

int             set_int_elt(num, elt, value)
  int             num, elt;
  long            value;

{
  struct dsc     *thedsc;
  struct obj_data *theobj;

  /* Check for things that live right in the descriptor */

  if ((elt == FLAGS || elt == NEXT || elt == OWNER || elt == HOME)
      && num >= 0 && num < total_objects) {
    thedsc = main_index[num];
    if (thedsc == NULL) {
      return (-1);
    } else {
      switch (elt) {
      case FLAGS:
	DSC_FLAGS(thedsc) = value;
	break;
      case NEXT:
	thedsc->list_next = value;
	break;
      case OWNER:
	DSC_OWNER(thedsc) = value;
	break;
      case HOME:
	/* case DESTINATION: */
	/* case DROPTO: */
	thedsc->home_dropto = value;
	break;
      }
      return (0);
    }
  }
  /* It's not in the descriptor, so go get the object data */

  if (num >= total_objects || (thedsc = main_index[num]) == NULL) {
    return (-1);
  }
  if ((theobj = lookup_obj(num)) == NULL) {
    return (-1);
  }
  switch (elt) {
  case QUOTA:
    theobj->pennies = (int) value;
    break;
  case LOC:
    theobj->loc = value;
    break;
  case CONTENTS:
    theobj->contents = value;
    break;
  case EXITS:
    theobj->exits = value;
    break;
  case ROOMS:
    theobj->rooms = value;
    break;
  case TIMESTAMP:
    theobj->timestamp = value;
    break;
  default:
    warning("set_int_elt", "invalid element code");
    return (-1);
    break;
  }
  DSC_FLAGS(thedsc) |= DIRTY;
  return (0);
}

/*
 * Unlike set_str_elt(), this DOES NOT allocate memory to put the lock in to.
 * bool_parse() does this for us, so we are free to use the memory it gave us
 * as permanenet storage here.
 * 
 */
int             set_lock_elt(num, elt, value)
  int             num, elt;
  int            *value;

{
  struct obj_data *theobj;
  struct dsc     *thedsc;
  int             valsize;
  int             oldsize;
  extern int      cache_usage;

  if ((elt != LOCK) && (elt != ELOCK) && (elt != DESTINATIONS)) {
    warning("set_lock_elt", "bad element type code");
    return (-1);
  }
  if ((theobj = lookup_obj(num)) == NULL) {
    return (-1);
  }
  thedsc = theobj->descriptor;

  /*
   * Size computations are actually all low by 1 == basic on-disk lock
   * overhead.
   */

  if (value == NULL) {
    valsize = 0;
  } else {
    valsize = value[0] * sizeof(int);
  }

  switch (elt) {
  case LOCK:
    if (theobj->lock == NULL) {
      oldsize = 0;
    } else {
      oldsize = (theobj->lock)[0] * sizeof(int);
    }
    DSC_SIZE(thedsc) = (DSC_SIZE(thedsc) - oldsize) + valsize;

    ty_free((char *) theobj->lock);
    theobj->lock = value;
    break;
  case ELOCK:
    if (theobj->elock == NULL) {
      oldsize = 0;
    } else {
      oldsize = (theobj->elock)[0] * sizeof(int);
    }
    DSC_SIZE(thedsc) = (DSC_SIZE(thedsc) - oldsize) + valsize;

    ty_free((char *) theobj->elock);
    theobj->elock = value;
    break;
  case DESTINATIONS:
    if (theobj->destinations == NULL) {
      oldsize = 0;
    } else {
      oldsize = (theobj->destinations)[0] * sizeof(int);
    }
    DSC_SIZE(thedsc) = (DSC_SIZE(thedsc) - oldsize) + valsize;

    ty_free((char *) theobj->destinations);
    theobj->destinations = value;
    break;
  }
  DSC_FLAGS(thedsc) |= DIRTY;
  return (0);
}

/*
 * Destroy an existing object.
 */

void            destroy_obj(num)
  int             num;

{
  struct dsc     *thedsc;
  struct obj_data *theobj;

  if ((theobj = lookup_obj(num)) == NULL) {
    return;
  }
  cache_delete(theobj);		/* Get it out of cache */

  /* Free all the data on the object */

  ty_free((char *) theobj->lock);
  ty_free((char *) theobj->elock);
  ty_free((char *) theobj->destinations);

  ty_free(theobj->suc);
  ty_free(theobj->osuc);
  ty_free(theobj->fail);
  ty_free(theobj->ofail);
  ty_free(theobj->drop);
  ty_free(theobj->odrop);
  ty_free(theobj->kill);
  ty_free(theobj->okill);
  ty_free(theobj->desc);
  ty_free(theobj->enter);
  ty_free(theobj->oenter);
  ty_free(theobj->leave);
  ty_free(theobj->oleave);
  ty_free(theobj->idesc);
  ty_free(theobj->odesc);
  ty_free(theobj->site);
  ty_free(theobj->password);
  ty_free(theobj->otel);
  ty_free(theobj->oxtel);
  ty_free(theobj->oxent);
  ty_free(theobj->oxlea);
  ty_free(theobj->efail);
  ty_free(theobj->oefail);

  thedsc = theobj->descriptor;
  ty_free(DSC_NAME(thedsc));

  /* we don't KNOW if it has been stored yet, but gdbm is smart. i hope. */
  disk_delete(num);

  free_descriptor(thedsc);
  ty_free((char *) theobj);
  actual_objects--;
  garbage_count++;
  main_index[num] = (struct dsc *) NULL;
  return;
}

/*
 * Create a new object. Returns the object number.
 */

int             create_obj(type)
  int             type;

{
  struct obj_data *theobj;
  struct dsc     *thedsc;
  int             num;

  thedsc = get_descriptor();
  theobj = (struct obj_data *) ty_malloc(sizeof(struct obj_data)
					 ,"create_obj");

  DSC_DATA(thedsc) = theobj;
  DSC_SIZE(thedsc) = INITIAL_SIZE;
  DSC_NAME(thedsc) = NULL;
  DSC_FLAGS(thedsc) = type | IN_MEMORY | DIRTY;
  DSC_OWNER(thedsc) = -1;
  thedsc->list_next = -1;
  thedsc->home_dropto = -1;

  /* Make it a grey box */

  theobj->suc = NULL;
  theobj->osuc = NULL;
  theobj->fail = NULL;
  theobj->ofail = NULL;
  theobj->drop = NULL;
  theobj->odrop = NULL;
  theobj->kill = NULL;
  theobj->okill = NULL;
  theobj->desc = NULL;
  theobj->enter = NULL;
  theobj->oenter = NULL;
  theobj->leave = NULL;
  theobj->oleave = NULL;
  theobj->idesc = NULL;
  theobj->odesc = NULL;
  theobj->site = NULL;
  theobj->password = NULL;
  theobj->otel = NULL;
  theobj->oxtel = NULL;
  theobj->oxent = NULL;
  theobj->oxlea = NULL;
  theobj->efail = NULL;
  theobj->oefail = NULL;
  theobj->lock = NULL;
  theobj->elock = NULL;
  theobj->destinations = NULL;
  theobj->contents = -1;
  theobj->exits = -1;
  theobj->rooms = -1;
  theobj->pennies = 0;
  theobj->loc = 0;
  theobj->descriptor = thedsc;

  /* Stuff it in cache */

  cache_insert(theobj);

  /* Now get this thing an object number. Yow! */

  if (garbage_count == 0) {	/* Gotta get a new number */

    if (slack == 0) {
      grow_index();
    }
    slack--;
    main_index[total_objects] = thedsc;
    num = total_objects;
    total_objects++;

  } else {			/* Find a garbage slot */
    for (num = total_objects - 1; num > 0; num--) {
      if (main_index[num] == NULL)
	break;
    }
    if (num == 0 && main_index[num] != NULL) {
      warning("create_obj", "garbage count out if synch");
      return (-1);
    }
    main_index[num] = thedsc;
    garbage_count--;
  }
  DSC_NUMBER(thedsc) = num;
  actual_objects++;
  return (num);			/* Done! */
}

/*
 * Look up an object by number. Snarf it off disk and shove it in cache if
 * necessary. The DB is *complicated*, so don't even THINK about trying to
 * reference object data any way other than through this function, OK?
 * 
 */

struct obj_data *
                lookup_obj(num)
  int             num;

{
  struct dsc     *thedsc;
  struct obj_data *theobj;
  extern int      cache_hits, cache_errors, cache_misses;

  /* If there is no such object (number too large, or garbage) */

  if (num >= total_objects || num < 0
      || (thedsc = main_index[num]) == NULL) {
    cache_errors++;
    return (NULL);
  }
  if (!ResidentP(thedsc)) {
    cache_misses++;
    /* Grab it off disk */

    DSC_NUMBER(thedsc) = num;
    if ((theobj = disk_thaw(thedsc)) == NULL) {
      warning("lookup_obj", "thaw failed");
      cache_errors++;
      return (NULL);
    }
    /* Stuff it into the cache, too */

    cache_insert(theobj);
  } else {
    cache_hits++;
    theobj = DSC_DATA(thedsc);
    touch(theobj);
  }

  /* Now it's been gotten, and is in cache. Yay. */

  return (theobj);
}

/*
 * Returns 1 if and only if the specified object number refers to a valid
 * object.
 * 
 */

int             exists_object(num)
  int             num;
{
  return (num >= 0 && num < total_objects && main_index[num] != NULL);
}

/*
 * This sets up a blank DB from scratch. Allocates an initial set of free
 * descriptors and an initial index (so if you know how big the DB is to
 * start with, you can be fairly efficient, and allocate all the descriptors
 * you'll need to start with).
 * 
 */

void            initialize_db(idx_siz)
  int             idx_siz;

{
  alloc_dsc(idx_siz);
  main_index = (struct dsc **) ty_malloc(sizeof(int) * idx_siz
					 ,"initialize_db");

  for (idx_siz--; idx_siz > 0; idx_siz--) {
    main_index[idx_siz] = NULL;
  }
  growth_increment = GROWTH_INCREMENT;
}

/*
 * Writes out the descriptor file. Returns -1 if failure, 0 otherwise.
 */

int             write_descriptors(name)
  char           *name;

{
  int             fd;
  int             i, len;
  struct dsc     *thedsc;

  /* Space for obj#, chunk offset, flags, owner, etc., and a name */

  char            work[(sizeof(long) * 6) + 512];

  if ((fd = open(name, O_WRONLY | O_CREAT, 0600)) == -1) {
    warning("write_descriptors", "could not open output file");
    return (-1);
  }
  /* Write out the basic record at the beginning */
  /* Actual number of objects, and number of garbage descriptors */

  ((long *) work)[0] = actual_objects;
  ((long *) work)[1] = garbage_count;
  ((long *) work)[2] = total_objects;

  write(fd, work, sizeof(long) * 3);

  /* Write out all the real object descriptors */

  for (i = 0; i < total_objects; i++) {
    if ((thedsc = main_index[i]) != NULL) {
      /* Write a single descriptor */
      ((long *) work)[0] = i;	/* Obj # */
      ((long *) work)[1] = DSC_FLAGS(thedsc);
      ((long *) work)[2] = DSC_SIZE(thedsc);
      ((long *) work)[3] = thedsc->list_next;
      ((long *) work)[4] = DSC_OWNER(thedsc);
      ((long *) work)[5] = thedsc->home_dropto;

      if (DSC_NAME(thedsc) == NULL) {
	work[sizeof(long) * 6] = '\0';
	len = 1;
      } else {
	strcpy(&(work[sizeof(long) * 6])
	       ,DSC_NAME(thedsc));
	len = strlen(DSC_NAME(thedsc)) + 1;
      }
      write(fd, work, (sizeof(long) * 6) + len);
    }
  }

  /* close it all down */

  (void) close(fd);
  return (0);
}

/*
 * reads in a descriptor file by name. Use this when you are initializing
 * things, otherwise all hell will break loose.
 * 
 * This is basically what should be used to bring an existing DB back up. Play
 * with the numbers in the initialize_db() call if your MUD grows very slowly
 * or very quickly.
 * 
 */

int             read_descriptors(name)
  char           *name;

{
  int             fd;
  int             i, j;
  long            objnum;
  struct dsc     *thedsc;

  /* Space for obj#, chunk offset, flags, owner, etc., OR a name */

  char            work[512];

  if ((fd = open(name, O_RDONLY, 0)) == -1) {
    fatal("read_descriptors", "could not open descriptor file");
  }
  /* Read in the initial stuff */

  i = read(fd, work, sizeof(long) * 3);
  if (i != (sizeof(long) * 3)) {
    fatal("read_descriptors", "error reading descriptor file");
  }
  actual_objects = ((long *) work)[0];
  garbage_count = ((long *) work)[1];
  total_objects = ((long *) work)[2];

  /*
   * Set up the DB for enough descriptors for everything, and enough index
   * space too, plus 512 on both for luck.
   */

  initialize_db(total_objects + SLACK);
  slack = SLACK;

  /* Now charge along reading in descriptors like crazy. */

  for (i = actual_objects; i > 0; i--) {

    thedsc = get_descriptor();

    /* Start by reading in the seven longs at the front. */

    if (read(fd, work, sizeof(long) * 6) != (sizeof(long) * 6)) {
      fatal("read_descriptors"
	    ,"error reading descriptor file");
    }
    objnum = ((long *) work)[0];
    main_index[objnum] = thedsc;
    DSC_NUMBER(thedsc) = objnum;
    DSC_FLAGS(thedsc) = ((long *) work)[1];
    DSC_SIZE(thedsc) = ((long *) work)[2];
    thedsc->list_next = ((long *) work)[3];
    DSC_OWNER(thedsc) = ((long *) work)[4];
    thedsc->home_dropto = ((long *) work)[5];

    /* Now get the string, one char at a crack. Hee! */

    j = 0;
    do {
      if (read(fd, &(work[j]), 1) != 1) {
	fatal("read_descriptors"
	      ,"error reading descriptor file");
      }
    } while (work[j++] != '\0');

    DSC_NAME(thedsc) = ty_malloc(j, "read_descriptors");
    strcpy(DSC_NAME(thedsc), work);

#ifdef LOADDEBUG
    printf("Object %s lives at %dl, size == %dl\n", DSC_NAME(thedsc)
	   ,DSC_CHUNK(thedsc), DSC_SIZE(thedsc));
#endif

  }

  /* close it all down. */

  if (close(fd) == -1) {
    warning("read_descriptors", "error closing descriptor file");
  }
}

/*
 * This grows the index by the basic increment.
 */

void            grow_index()
{
  /* slack *should* be zero, but might not be */

  main_index = (struct dsc **) realloc((char *) main_index,
       (size_t) (sizeof(int) * (total_objects + growth_increment + slack)));

  if (main_index == NULL) {
    fatal("grow_index", "could not grow index!");
  }
}