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