/* 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; } }