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

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

#include <stdio.h>
#ifdef STRING_H
#include <string.h>
#else
#include <strings.h>
#endif				/* STRING_H */
#include <gdbm.h>

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

/*
 * Interfaces TeenyMUSK to Gnu DBM. loosly based upon the old TeenyMUD disk.c
 * 
 */

char           *snarf_str_field();
int            *snarf_lock();
char           *stuff_lock();
char           *stuff_str_field();

GDBM_FILE       chunkfd;

static char     obj_id[128];
static int     *obj_work = NULL;
static long     work_siz = 0;

/*
 * Write the object specified by the descriptor out to disk. Will not bother
 * to freeze an object that isn't dirty.
 * 
 */
void            disk_freeze(d)
  struct dsc     *d;

{
  int            *p;
  char           *q;
  struct obj_data *dat;
  int             total_size, obj;
  long            ret;
  datum           key, cont;

  if (!ResidentP(d)) {
    warning("disk_freeze", "attempt to freeze non-resident object");
    return;
  }
  dat = DSC_DATA(d);
  obj = DSC_NUMBER(d);

  /* Is the data for this thing dirty? If not, patch up its */
  /* descriptor to point at the disk, and free the memory   */

  if (!DirtyP(d)) {
    DSC_FLAGS(d) &= ~IN_MEMORY;
    free_obj(dat);
    return;
  }
  /* Step one. Write the thing into our workspace. */

  if (work_siz == 0) {		/* initialize our buffer */
    obj_work = (int *) ty_malloc(10240 / sizeof(int), "disk_freeze");
    work_siz = 10240 / sizeof(int);
  }
  if (DSC_SIZE(d) > work_siz) {	/* grow it */
    int            *tmp;

    tmp = (int *) realloc(obj_work, (DSC_SIZE(d) + 32));
    if (tmp == NULL) {
      log_error("disk_freeze: realloc of work buffer failed (%ld bytes), exiting\n", DSC_SIZE(d) + 32);
      exit(-1);
    }
    obj_work = tmp;
    work_siz = DSC_SIZE(d) + 32;
  }
  p = obj_work;
  *p++ = dat->pennies;
  *p++ = dat->loc;
  *p++ = dat->contents;
  *p++ = dat->exits;
  *p++ = dat->rooms;
  *p++ = dat->timestamp;

  /* Stuff variable length integer arrays. */
  /* NOTE: Order is important. These arrays *will* be correctly   */
  /* aligned, since they fall immediately after a mess of other */
  /* integers. Effectively, we start with a huge array of ints. */

  p = (int *) stuff_lock(p, dat->lock);
  p = (int *) stuff_lock(p, dat->elock);
  q = stuff_lock(p, dat->destinations);

  /* Stuff variable length strings. Now alignment goes to hell    */
  /* Strings are zero terminated, so we don't care!!!  */

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

  /* Step two. How big is this bugger? Write it out to disk */

  total_size = q - (char *) obj_work;
  if (total_size != DSC_SIZE(d)) {
    warning("disk_freeze", "computed disk size != stated");
  }
  (void) sprintf(obj_id, "#%d", obj);
  key.dptr = obj_id;
  key.dsize = strlen(obj_id) + 1;
  cont.dptr = (char *) obj_work;
  cont.dsize = total_size;
  ret = gdbm_store(chunkfd, key, cont, GDBM_REPLACE);

  if (ret != 0) {
    warning("disk_freeze", "failed write");
  }
  /* Update descriptor */

  DSC_FLAGS(d) &= ~IN_MEMORY;

  /* Free up the memory used. */

  free_obj(dat);
}

void            free_obj(obj)
  struct obj_data *obj;

{
  ty_free((char *) (obj->lock));
  ty_free((char *) (obj->elock));
  ty_free((char *) (obj->destinations));

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

  ty_free((char *) obj);
}

/*
 * Copies one of our internal arrays around. They start with a count of
 * number of ints, and are followed by that many ints. Yow.
 * 
 */

char           *
                stuff_lock(dst, src)
  int            *dst, *src;

{
  int             count;

  if (src == NULL) {
    *dst++ = -1;		/* Indicate empty lock */
    return ((char *) dst);
  }
  count = *dst++ = *src++;	/* The very first one is how many follow */

  while (count > 0) {
    *dst++ = *src++;
    count--;
  }

  return ((char *) dst);
}

/*
 * Really just copies a string around, and returns a pointer to the first
 * byte after the string.
 * 
 */

char           *
                stuff_str_field(dst, src)
  char           *dst, *src;

{
  int             len;

  if (src == NULL) {
    /* Shove in a zero length string */
    *dst++ = '\0';
    return (dst);
  }
  len = strlen(src);
  strcpy(dst, src);
  return (dst + len + 1);	/* plus one for the \0 */
}

/*
 * Read the object specified by number in from disk.
 */
struct obj_data *
                disk_thaw(d)
  struct dsc     *d;

{
  struct obj_data *new_obj;
  int             len, obj;
  char           *q;
  int            *p;
  datum           key, cont;
  extern void     free();

  if (ResidentP(d)) {
    warning("disk_thaw", "attempt to re-thaw thawed object");
    return (DSC_DATA(d));
  }
  obj = DSC_NUMBER(d);
  (void) sprintf(obj_id, "#%d", obj);
  key.dptr = obj_id;
  key.dsize = strlen(obj_id) + 1;
  cont = gdbm_fetch(chunkfd, key);

  if (cont.dptr == NULL) {
    warning("disk_thaw", "couldn't thaw chunk");
    return (NULL);
  }
  /* OK. It's in memory. Snarf the data out */

  len = cont.dsize;
  new_obj = (struct obj_data *) ty_malloc(sizeof(struct obj_data)
					  ,"disk_thaw");
  p = (int *) (cont.dptr);

  /* Fix up internal object data fields. */

  new_obj->size = cont.dsize;
  new_obj->descriptor = d;

  new_obj->pennies = *p++;
  new_obj->loc = *p++;
  new_obj->contents = *p++;
  new_obj->exits = *p++;
  new_obj->rooms = *p++;
  new_obj->timestamp = *p++;

  /* Variable length field integer field. */

  new_obj->lock = snarf_lock(&p);
  new_obj->elock = snarf_lock(&p);
  new_obj->destinations = snarf_lock(&p);

  q = (char *) p;
  len -= (q - (char *) (cont.dptr));	/* Subtract what we've chopped off */

  /* Variable length string fields. */

  new_obj->suc = snarf_str_field(&q, &len);
  new_obj->osuc = snarf_str_field(&q, &len);
  new_obj->fail = snarf_str_field(&q, &len);
  new_obj->ofail = snarf_str_field(&q, &len);
  new_obj->drop = snarf_str_field(&q, &len);
  new_obj->odrop = snarf_str_field(&q, &len);
  new_obj->desc = snarf_str_field(&q, &len);
  new_obj->enter = snarf_str_field(&q, &len);
  new_obj->oenter = snarf_str_field(&q, &len);
  new_obj->oxent = snarf_str_field(&q, &len);
  new_obj->leave = snarf_str_field(&q, &len);
  new_obj->oleave = snarf_str_field(&q, &len);
  new_obj->oxlea = snarf_str_field(&q, &len);
  new_obj->idesc = snarf_str_field(&q, &len);
  new_obj->odesc = snarf_str_field(&q, &len);
  new_obj->site = snarf_str_field(&q, &len);
  new_obj->password = snarf_str_field(&q, &len);
  new_obj->kill = snarf_str_field(&q, &len);
  new_obj->okill = snarf_str_field(&q, &len);
  new_obj->otel = snarf_str_field(&q, &len);
  new_obj->oxtel = snarf_str_field(&q, &len);
  new_obj->efail = snarf_str_field(&q, &len);
  new_obj->oefail = snarf_str_field(&q, &len);

  /* Update the descriptor */

  DSC_DATA(d) = new_obj;
  DSC_FLAGS(d) |= IN_MEMORY;
  DSC_FLAGS(d) &= ~DIRTY;

  free((char *) cont.dptr);
  return (new_obj);
}

/*
 * Copy an array of our integers into new fresh memory.
 * 
 * src had damn well better be aligned enough.
 * 
 */

int            *
                snarf_lock(src)
  int           **src;

{
  int             len;
  int            *p;

  len = *(*src);		/* First thing is how many there are to
				 * follow */
  if (len == -1) {		/* No lock, empty */
    (*src)++;
    return (NULL);
  }
  len++;			/* Add one for the length field, of course */

  p = (int *) ty_malloc((len * sizeof(int)), "snarf_lock");

  /* Ignore returned value, we care not. */

  (void) stuff_lock(p, *src);
  *src += len;
  return (p);
}

/*
 * Grabs a variable length string field out of a buffer. String should be
 * zero terminated.
 */

char           *
                snarf_str_field(src, remaining)
  char          **src;
  int            *remaining;

{
  int             len;
  char           *ret;

  if (*remaining <= 0) {
    warning("snarf_str_field", "disk data out of synch");
    return (NULL);
  }
  if (**src == '\0') {
    (*src)++;
    (*remaining)--;
    return (NULL);
  }
  len = strlen(*src) + 1;
  ret = ty_malloc(len, "snarf_str_field");
  *remaining -= len;
  strcpy(ret, *src);
  (*src) += len;
  return (ret);
}

/*
 * Open a chunkfile.
 */

void            db_fatal();

void            open_chunkfile(name)
  char           *name;

{
  datum           ver, key;
  static char     tmp[] = "version";

  if ((chunkfd = gdbm_open(name, 0, GDBM_WRITER, 0600, db_fatal)) == NULL) {
    fatal("open_chunkfile", "could not open chunk file");
  }
  key.dptr = tmp;
  key.dsize = strlen(tmp) + 1;
  ver = gdbm_fetch(chunkfd, key);
  if (ver.dptr == NULL || strcmp(ver.dptr, version)) {
    fatal("open_chunkfile", "database version does not match server version");
  }
  (void) free((char *) ver.dptr);
}

void            init_chunkfile(name)
  char           *name;
{
  datum           ver, key;
  static char     tmp[] = "version";

  if ((chunkfd = gdbm_open(name, 0, GDBM_NEWDB, 0600, db_fatal)) == NULL) {
    fatal("init_chunkfile", "could not open chunk file");
  }
  key.dptr = tmp;
  key.dsize = strlen(tmp) + 1;
  ver.dptr = version;
  ver.dsize = strlen(version) + 1;
  if (gdbm_store(chunkfd, key, ver, GDBM_REPLACE) != 0)
    warning("init_chunkfile", "couldn't store version");
}

void            db_fatal(p)
  char           *p;
{
  log_error("FATAL: GDBM died with message: %s\n", p);
  exit(-1);
}

void            close_chunkfile()
{
  gdbm_close(chunkfd);
}

void            disk_delete(num)
  int             num;
{
  char            tmp[64];
  datum           key;

  (void) sprintf(tmp, "#%d", num);
  key.dptr = tmp;
  key.dsize = strlen(tmp) + 1;
  (void) gdbm_delete(chunkfd, key);
}

/*
 * Misc. flag checkers and such.
 */

extern struct dsc **main_index;
extern int      total_objects;

int             is_flag_set(obj, code)
  int             obj;
  int             code;
{

  if (obj < 0 || obj >= total_objects || main_index[obj] == NULL) {
    log_error("is_flag_set: bad obj passed as an argument, %d\n", obj);
    return 0;
  }
  return ((DSC_FLAGS(main_index[obj]) & code));
}

int             isplayer(obj)
  int             obj;
{

  if (obj < 0 || obj >= total_objects || main_index[obj] == NULL) {
    warning("isplayer", "bad obj passed as an argument");
    return 0;
  }
  return (PlayerP(main_index[obj]));
}

int             isroom(obj)
  int             obj;
{

  if (obj < 0 || obj >= total_objects || main_index[obj] == NULL) {
    warning("isroom", "bad obj passed as an argument");
    return 0;
  }
  return (RoomP(main_index[obj]));
}

int             isexit(obj)
  int             obj;
{

  if (obj < 0 || obj >= total_objects || main_index[obj] == NULL) {
    warning("isexit", "bad obj passed as an argument");
    return 0;
  }
  return (ExitP(main_index[obj]));
}

int             isthing(obj)
  int             obj;
{

  if (obj < 0 || obj >= total_objects || main_index[obj] == NULL) {
    warning("isthing", "bad obj passed as an argument");
    return 0;
  }
  return (ThingP(main_index[obj]));
}

int             isgarbage(obj)
  int             obj;
{

  return (obj >= 0 && obj < total_objects && main_index[obj] == NULL);
}

/* Utility routines with nowhere else to go. */

/*
 * Drops an element from a list. You'd better be sure the element is actually
 * on the list.
 */
void            list_drop(elt, place, code)
  int             elt;
  int             place;
  int             code;
{
  int             current, last, next;
  int             listcode;

  switch (code) {
  case CONTENTS_LIST:
    listcode = CONTENTS;
    break;
  case EXITS_LIST:
    listcode = EXITS;
    break;
  case ROOMS_LIST:
    listcode = ROOMS;
    break;
  default:
    warning("list_drop", "bad list code");
    return;
  }

  if (get_int_elt(place, listcode, &current) == -1) {
    warning("list_drop", "bad list reference.");
    return;
  }
  last = -1;
  while (current != elt && current != -1) {
    last = current;
    if (get_int_elt(current, NEXT, &current) == -1) {
      warning("list_drop", "bad ref inside list");
      return;
    }
  }

  /* Post Mortem. */

  /* grab the next thing on the list anyway. */

  if (get_int_elt(current, NEXT, &next) == -1) {
    warning("list_drop", "bad ref inside list");
    return;
  }
  if (last == -1) {		/* Was 1st thing on list */
    if (set_int_elt(place, listcode, next) == -1) {
      warning("list_drop", "bad list reference.");
      return;
    }
  } else {
    if (set_int_elt(last, NEXT, next) == -1) {
      warning("list_drop", "bad list reference.");
      return;
    }
  }
}

/*
 * Adds a thing to the top of the contents or exits list of the specified
 * place.
 */

void            list_add(thing, place, code)
  int             thing;
  int             place;
  int             code;
{
  int             list;
  int             listcode;

  switch (code) {
  case CONTENTS_LIST:
    listcode = CONTENTS;
    break;
  case EXITS_LIST:
    listcode = EXITS;
    break;
  case ROOMS_LIST:
    listcode = ROOMS;
    break;
  default:
    warning("list_add", "bad list code");
    return;
  }

  if (get_int_elt(place, listcode, &list) == -1) {
    warning("list_add", "can't get contents");
    return;
  }
  if (set_int_elt(thing, NEXT, list) == -1) {
    warning("list_add", "bad can't set NEXT");
    return;
  }
  if (set_int_elt(place, listcode, thing) == -1) {
    warning("list_add", "can't set contents");
    return;
  }
}