pgplus/bin/
pgplus/help_files/
pgplus/port_redirector/
pgplus/src/configure/makefiles/
/*
 * Playground+ - dynamic.c
 * Dynamic files
 * ---------------------------------------------------------------------------
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

/* just in case youre on a dippy system */

#ifndef S_IRUSR
#define S_IRUSR 00400
#endif
#ifndef S_IWUSR
#define S_IWUSR 00200
#endif


#include "include/config.h"
#include "include/player.h"
#include "include/proto.h"

/* keep the compiler happy */

extern char *end_string(char *);
extern char *store_int(char *, int);
extern char *get_int(int *, char *);
extern dfile *room_df;
extern saved_player **saved_hash[];

/* throw the keylist to disk */

void dynamic_key_sync(dfile * df)
{
  int fd, length;
  char *oldstack, *to;
  oldstack = stack;

  store_int((char *) df->keylist, df->first_free_block);
  store_int((char *) (df->keylist + 1), df->first_free_key);

/* doing a straight open could mean losing the key data
   dont risk it by moving the file first
   this is grossly slow, so provide a means of escaping it */

  sprintf(oldstack, "files/%s/keys", df->fname);
  if (!(sys_flags & SECURE_DYNAMIC))
  {
    stack = end_string(oldstack);
    to = stack;
    sprintf(to, "files/%s/keys.b", df->fname);
    rename(oldstack, to);
  }
  fd = open(oldstack, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);
  if (fd < 0)
    handle_error("Failed to open key file");
  length = (df->nkeys + 1) * 8;
  if (write(fd, df->keylist, length) != length)
    handle_error("Failed to write key data");
  close(fd);

  stack = oldstack;
}


/* set up a dfile structure */

dfile *dynamic_init(char *fil, int granularity)
{
  dfile *df;
  char *oldstack;
  int fd, length;
  oldstack = stack;

  if (sys_flags & VERBOSE)
  {
    sprintf(oldstack, "Loading dynamic file '%s'", fil);
    stack = end_string(oldstack);
    log("sync", oldstack);
    stack = oldstack;
  }
  df = (dfile *) MALLOC(sizeof(dfile));
  memset(df, 0, sizeof(dfile));
  strcpy(df->fname, fil);
  df->granularity = granularity;

  sprintf(oldstack, "files/%s/keys", df->fname);
  fd = open(oldstack, O_RDONLY | O_NDELAY);

  /* just in case this is the first time round */

  if (fd < 0)
  {
    stack = oldstack;
    df->first_free_block = 0;
    df->first_free_key = 0;
    df->nkeys = 0;
    df->keylist = 0;
  }
  else
  {
    length = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);
    if ((length % 8) != 0)
      handle_error("Corrupt key data");
    df->nkeys = (length / 8) - 1;
    df->keylist = (int *) MALLOC(length);
    if (read(fd, df->keylist, length) != length)
      handle_error("Failed to read keys");
    close(fd);
    (void) get_int(&(df->first_free_block), (char *) df->keylist);
    (void) get_int(&(df->first_free_key), (char *) (df->keylist + 1));
  }

  /* keep the data file open all the time */

  sprintf(oldstack, "files/%s/data", df->fname);
  df->data_fd = open(oldstack, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  if (df->data_fd < 0)
    handle_error("Failed to open dynamic data file");
  lseek(df->data_fd, 0, SEEK_SET);
  stack = oldstack;
  return df;
}

/* free up a dfile structure */

void dynamic_close(dfile * df)
{
  dynamic_key_sync(df);
  close(df->data_fd);
  FREE(df->keylist);
  FREE(df);
}



/* get the block and length info from the keylist */

int convert_key(dfile * df, int key, int *block, int *length)
{
  if ((key <= 0) || (key > (df->nkeys - 1)))
    return 0;
  key *= 2;
  (void) get_int(block, (char *) (df->keylist + key));
  if (*block <= 0)
    return 0;
  (void) get_int(length, (char *) (df->keylist + key + 1));
  if (*length <= 0)
    return 0;
  return 1;
}

/* grab a block from the data file */

int load_block(dfile * df, int *block, char *data)
{
  if (*block <= 0)
    return 0;
  dynamic_seek_block(df, *block);
  if (read(df->data_fd, data, 4) != 4)
    handle_error("Failed to read next block 1");
  get_int(block, data);
  if (read(df->data_fd, data, df->granularity - 4) != (df->granularity - 4))
    handle_error("Failed to read block");
  return 1;
}

/* load an entire entry from the data file */

int dynamic_load(dfile * df, int key, char *data)
{
  int block, length, l;

  if (!convert_key(df, key, &block, &length))
    return -1;
  l = length;
  while (l > 0)
  {
    if (!load_block(df, &block, data))
      handle_error("Failed to load block of data");
    l -= (df->granularity - 4);
    data += (df->granularity - 4);
  }
  return length;
}

/* returns a free key, if necessary it will go and create
   some new key space (in chunks of 100 keys at a time) */

int dynamic_find_free_key(dfile * df)
{
  int *newlist, oldnkeys, i, key;
  if (!df->first_free_key)
  {
    oldnkeys = df->nkeys;
    df->nkeys += 100;
    newlist = (int *) MALLOC((df->nkeys + 1) * 8);
    if (df->keylist)
    {
      memcpy(newlist + 2, df->keylist + 2, (oldnkeys + 1) * 8);
      FREE(df->keylist);
    }
    df->keylist = newlist;
    df->first_free_key = oldnkeys + 1;
    newlist += df->first_free_key * 2;
    for (i = -df->first_free_key - 1; i >= -df->nkeys; i--)
    {
      newlist = (int *) store_int((char *) newlist, i);
      newlist = (int *) store_int((char *) newlist, 0);
    }
    newlist = (int *) store_int((char *) newlist, 0);
    newlist = (int *) store_int((char *) newlist, 0);
  }
  key = df->first_free_key;
  (void) get_int(&(df->first_free_key), (char *) (df->keylist + key * 2));
  df->first_free_key = -df->first_free_key;
  return key;
}

/* read in the index from the start of a block */

int dynamic_get_next_block(dfile * df, int block)
{
  int next_block, ret;
  if (block <= 0)
    return 0;
  dynamic_seek_block(df, block);
  ret = read(df->data_fd, stack, 4);
  if (!ret)
    return 0;
  if (ret != 4)
    handle_error("Failed to read next block 2");
  get_int(&next_block, stack);
  return next_block;
}

/* go through all the blocks in a file and free them up */

void dynamic_free(dfile * df, int key)
{
  int block, length, next_block;

  if (!convert_key(df, key, &block, &length))
    return;
  (void) store_int((char *) (df->keylist + key * 2), -df->first_free_key);
  (void) store_int((char *) (df->keylist + key * 2 + 1), 0);
  df->first_free_key = key;

/* simply keep adding blocks onto the top of the free list */

  while (length > 0)
  {
    if (block <= 0)
      return;
    next_block = dynamic_get_next_block(df, block);
    dynamic_seek_block(df, block);
    store_int(stack, -df->first_free_block);
    if (write(df->data_fd, stack, 4) != 4)
      handle_error("Failed to write next block");
    df->first_free_block = block;
    block = next_block;
    length -= (df->granularity - 4);
  }
  if (!(sys_flags & SECURE_DYNAMIC))
    dynamic_key_sync(df);
}

/* go hunting for a free block, if necessary, create enough
   space on the end of the file */

int dynamic_find_free_block(dfile * df)
{
  int length, new_block;
/*  printf("Called find_free_block ffb=%d\n",df->first_free_block); */
  if (df->first_free_block)
  {
    new_block = df->first_free_block;
    df->first_free_block = -dynamic_get_next_block(df, new_block);
  }
  else
  {
    length = lseek(df->data_fd, 0, SEEK_END);
    memset(stack, 0, df->granularity);
    if (!length)
    {
      if (write(df->data_fd, stack, df->granularity) != df->granularity)
	handle_error("Failed to make data block");
      length = df->granularity;
    }
    if (write(df->data_fd, stack, df->granularity) != df->granularity)
      handle_error("Failed to make data block");
    new_block = (length / df->granularity);
  }
  return new_block;
}

/* save an entry to the data file */

int dynamic_save(dfile * df, char *data, int l, int key)
{
  int block, length, next_block, blength, free_block;

/* key==0 means this is a new entry */

  if (!key)
  {
    key = dynamic_find_free_key(df);
    block = dynamic_find_free_block(df);
  }
  else if (!convert_key(df, key, &block, &length))
    return 0;
  (void) store_int((char *) (df->keylist + key * 2), block);
  (void) store_int((char *) (df->keylist + key * 2 + 1), l);
  blength = df->granularity - 4;
  while (l > 0)
  {
    next_block = dynamic_get_next_block(df, block);

    if (l > blength)
    {
      if (next_block <= 0)
	next_block = dynamic_find_free_block(df);
    }
    else
    {

/* can we free up any blocks at the end ? */

      free_block = next_block;
      while (free_block > 0)
      {
	next_block = dynamic_get_next_block(df, free_block);
	dynamic_seek_block(df, free_block);
	store_int(stack, -df->first_free_block);
	if (write(df->data_fd, stack, 4) != 4)
	  handle_error("Failed to write next block");
	df->first_free_block = free_block;
	free_block = next_block;
      }
      next_block = 0;
    }
    store_int(stack, next_block);
    dynamic_seek_block(df, block);
    if (write(df->data_fd, stack, 4) != 4)
      handle_error("Failed to write next block");
    if (write(df->data_fd, data, blength) != blength)
      handle_error("Failed to write block data");
    block = next_block;
    l -= blength;
    data += blength;
  }

  /* make sure the keylist is up to date */

  if (!(sys_flags & SECURE_DYNAMIC))
    dynamic_key_sync(df);
  return key;
}


/* some functions used during testing */

void dynamic_test_func_keys(player * p, char *str)
{
  dfile *df;
  char *oldstack;
  int key, *scan;
  oldstack = stack;
  df = room_df;

  sprintf(oldstack, "nkeys = %d\n", df->nkeys);
  stack = end_string(oldstack);
  tell_player(p, oldstack);
  stack = oldstack;

  scan = df->keylist;
  for (key = 0; key <= df->nkeys; key++)
  {
    sprintf(stack, "Key %d\tBlock pointer %d\tlength %d\n",
	    key, *scan, *(scan + 1));
    scan += 2;
    while (*stack)
      stack++;
  }
  *stack++ = 0;
  pager(p, oldstack);
  stack = oldstack;
}



void dynamic_test_func_blocks(player * p, char *str)
{
  dfile *df;
  char *oldstack;
  int block, total, next_block, length;
  oldstack = stack;
  df = room_df;
  length = lseek(df->data_fd, 0, SEEK_END);
  total = length / df->granularity;
  sprintf(oldstack, "Length = %d ( %d blocks )\n", length, total);
  stack = end_string(oldstack);
  tell_player(p, oldstack);
  stack = oldstack;
  for (block = 0; total; total--, block++)
  {
    next_block = dynamic_get_next_block(df, block);
    sprintf(stack, "Block %d\tNext block %d\n", block, next_block);
    while (*stack)
      stack++;
  }
  *stack++ = 0;
  pager(p, oldstack);
  stack = oldstack;
}

/* produce some (interesting ?) stats on the file */

void dynamic_dfstats(player * p, char *str)
{
  dfile *df;
  char *oldstack;
  int key_length = 0, block_length, *keyscan, count, tmp;
  int nblocks, fragments = 0, block, next_block, lost;
  oldstack = stack;
  df = room_df;

  keyscan = df->keylist + 2;
  for (count = 1; count <= df->nkeys; count++)
  {
    (void) get_int(&tmp, (char *) (keyscan + 1));
    key_length += tmp;
    keyscan += 2;
  }
  block_length = lseek(df->data_fd, 0, SEEK_END);
  nblocks = block_length / df->granularity;
  for (block = 1; block < nblocks; block++)
  {
    next_block = dynamic_get_next_block(df, block);
    if (next_block > 0 && next_block != (block + 1))
      fragments++;
  }
  lost = (block_length - 4 * nblocks) - key_length;
  sprintf(oldstack, " Total keys = %d\tTotal blocks = %d\n"
	  " Key length = %d\tBlock length = %d\n"
	  " Overhead = %d\n"
	  " Lost space = %d (%d%%)\n"
	  " Data/junk percentage = %d%%\n"
	  " Fragmentation = %d (%d%%)\n",
	  df->nkeys, nblocks, key_length, block_length,
	  4 * nblocks, lost, (lost * 100) / block_length,
	  (key_length * 100) / block_length,
	  fragments, (fragments * 100) / nblocks);
  stack = end_string(oldstack);
  tell_player(p, oldstack);
  stack = oldstack;
}

/* used in the defrag routine */

void transfer_key(dfile * old, dfile * new, int key)
{
  int data_length, new_key, start_block, old_length, new_length;
  char *oldstack;
  oldstack = stack;
  data_length = dynamic_load(old, key, stack);
  if (data_length <= 0)
    return;
  stack += data_length;
  new_key = dynamic_save(new, oldstack, data_length, 0);
  if (new_key <= 0)
    handle_error("Failed to write new file on defrag");
  (void) get_int(&old_length, (char *) (old->keylist + key * 2 + 1));
  (void) get_int(&new_length, (char *) (new->keylist + new_key * 2 + 1));
  if (old_length != new_length)
  {
    printf("key = %d\nnew_key = %d\nold_length = %d\nnew_length = %d\ndata_length = %d\n",
	   key, new_key, old_length, new_length, data_length);
    handle_error("lengths dont match on defrag");
  }
  (void) get_int(&start_block, (char *) (new->keylist + new_key * 2));
  (void) store_int((char *) (old->keylist + key * 2), start_block);
  stack = oldstack;
}

/* defragment the dynamic file */

void dynamic_defrag_rooms(player * p, char *str)
{
  dfile *old_df, *new_df;
  int length, old_nblocks, new_nblocks, key;
  char *oldstack;
  oldstack = stack;

  old_df = room_df;
  if (p != NULL)
    tell_player(p, " Defragging room file\n");

  sys_flags |= SECURE_DYNAMIC;

  length = lseek(old_df->data_fd, 0, SEEK_END);
  old_nblocks = length / old_df->granularity;

/* make sure there is no previous data around */

  unlink("files/defrag/data");
  unlink("files/defrag/keys");
  unlink("files/defrag/keys.b");

/* this will be the new defragged file */

  new_df = dynamic_init("defrag", old_df->granularity);

/* now step through each key in the old file and simply copy to the new */

  for (key = 1; key <= old_df->nkeys; key++)
    transfer_key(old_df, new_df, key);

/* find the length of the new file */

  length = lseek(new_df->data_fd, 0, SEEK_END);
  new_nblocks = length / new_df->granularity;

/* make sure the first free block corresponds properly */

  old_df->first_free_block = new_df->first_free_block;

/* close the two files */

  sys_flags &= ~SECURE_DYNAMIC;
  dynamic_close(old_df);
  dynamic_close(new_df);

/* copy the defragged data accross */

  unlink("files/rooms/data");
  rename("files/defrag/data", "files/rooms/data");

/* and reopen the new defragged file */

  room_df = dynamic_init("rooms", 256);

/* a couple of stats */

  sprintf(oldstack, " Defragging completed\n"
	  " Old blocks = %d\n"
	  " New blocks = %d\n", old_nblocks, new_nblocks);
  stack = end_string(oldstack);
  if (p != NULL)
    tell_player(p, oldstack);
  stack = oldstack;
}


/* Test the integrity of the rooms file and clean it up */

int dynamic_test_integrity(dfile * df, int key)
{
  int block, length, l;
  int next_block;
  if (!convert_key(df, key, &block, &length))
    return 0;
  l = length;
  while (l > 0)
  {
    if (block <= 0)
      return 0;
    dynamic_seek_block(df, block);
    if (read(df->data_fd, &next_block, 4) != 4)
      return 0;
    get_int((int *) &block, (char *) &next_block);
    l -= (df->granularity - 4);
  }
  return 1;
}


/* Entry point into the room validation routines */

void dynamic_validate_rooms(player * p, char *str)
{
  dfile *old_df, *new_df;
  char *oldstack;
  saved_player *scan, **hash;
  int i, j, rooms = 0, rejects = 0;
  room *r;

  oldstack = stack;

  old_df = room_df;

  if (p)
  {
    tell_player(p, " Validating room file\n");
  }
/* make sure there is no previous data around 
   use the defrag file cos it happens to be there already */

  unlink("files/defrag/data");
  unlink("files/defrag/keys");
  unlink("files/defrag/keys.b");

/* this will be the validated file */

  new_df = dynamic_init("defrag", old_df->granularity);

  sys_flags |= SECURE_DYNAMIC;

/* now step through each player and copy their rooms */

/* each letter */

  for (j = 0; j < 26; j++)
  {
    hash = saved_hash[j];

/* each hash */

    for (i = 0; i < HASH_SIZE; i++, hash++)
/* each player */

      for (scan = *hash; scan; scan = scan->next)
/* each room */

	for (r = scan->rooms; r; r = r->next)
	{

/* skip bad rooms */

	  if (dynamic_test_integrity(old_df, r->data_key))
	  {
	    room_df = old_df;
	    decompress_room(r);
	    r->data_key = 0;
	    r->flags |= ROOM_UPDATED;
	    room_df = new_df;
	    compress_room(r);
	  }
	  else
	    rejects += 1;
	  rooms += 1;
	}
  }

  sys_flags &= ~SECURE_DYNAMIC;
  dynamic_close(old_df);
  dynamic_close(new_df);

/* copy the validated data accross */

  unlink("files/rooms/data");
  unlink("files/rooms/keys");
  rename("files/defrag/data", "files/rooms/data");
  rename("files/defrag/keys", "files/rooms/keys");

/* and reopen the new room file */

  room_df = dynamic_init("rooms", 256);

/* a couple of stats */

  if (p)
  {
    sprintf(oldstack, " Validation complete\n Total Rooms checked: %d\n"
	    " Rooms rejected: %d\n", rooms, rejects);
    stack = end_string(oldstack);
    tell_player(p, oldstack);
    stack = oldstack;
  }
}