talker/
talker/bin/
talker/files/whois/
talker/update/
talker/update/bin/
/*
    dynamic.c   dynamic files (in theory)
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <unistd.h>
#include <fcntl.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 "config.h"
#include "player.h"
#include "dynamic.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 *file,int granularity)
{
  dfile *df;
  char *oldstack;
  int fd,length,i,*fill;
  oldstack=stack;

  if (sys_flags&VERBOSE) {
    sprintf(oldstack,"Loading dynamic file '%s'",file);
    stack=end_string(oldstack);
    log("sync",oldstack);
    stack=oldstack;
  }
  
  df=(dfile *)MALLOC(sizeof(dfile));
  memset(df,0,sizeof(dfile));
  strcpy(df->fname,file);
  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) {
    sprintf(oldstack,"Failed to load '%s' keys",df->fname);
    stack=end_string(oldstack);
    log("error",oldstack);
    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;
/*  printf("Dynamic load key=%d\n",key);  */
  if (!convert_key(df,key,&block,&length)) return -1;
  l=length;
/*    printf("DL block %d\n",block);  */
  while(l>0) {
    if (!load_block(df,&block,data))
      handle_error("Failed to load block of data");
/*    printf("DL block %d\n",block);  */
    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;
  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)
{
  char *oldstack;
  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;
  (void) store_int((char *)(df->keylist+key*2),block);
  (void) store_int((char *)(df->keylist+key*2+1),l);  
  blength=df->granularity-4;
/*  printf("Dynamic Save key=%d\n",key); */
  while(l>0) {
    next_block=dynamic_get_next_block(df,block);
/*    printf("DS block %d / next block %d\n",block,next_block);  */

/* do we need more space ? */

    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;
  int rooms_kept;
  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;
   }
}