/* db.c: Object storage routines. * The block allocation algorithm in this code is due to Marcus J. Ranum. */ #define _POSIX_SOURCE #include <stdio.h> #include <sys/param.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "db.h" #include "lookup.h" #include "object.h" #include "cache.h" #include "log.h" #include "util.h" #include "dbpack.h" #include "memory.h" #include "config.h" #include "ident.h" #define NEEDED(n, b) (((n) % (b)) ? (n) / (b) + 1 : (n) / (b)) #define ROUND_UP(a, m) (((a) - 1) + (m) - (((a) - 1) % (m))) #define BLOCK_SIZE 256 /* Default block size */ #define DB_BITBLOCK 512 /* Bitmap growth in blocks */ #define LOGICAL_BLOCK(off) ((off) / BLOCK_SIZE) #define BLOCK_OFFSET(block) ((block) * BLOCK_SIZE) #ifdef S_IRUSR #define READ_WRITE (S_IRUSR | S_IWUSR) #define READ_WRITE_EXECUTE (S_IRUSR | S_IWUSR | S_IXUSR) #else #define READ_WRITE 0600 #define READ_WRITE_EXECUTE 0700 #endif static void db_mark(off_t start, int size); static void db_unmark(off_t start, int size); static void grow_bitmap(int new_blocks); static int db_alloc(int size); static void db_is_clean(void); static void db_is_dirty(void); static int last_free = 0; /* Last known or suspected free block */ static FILE *database_file = NULL; static char *bitmap = NULL; static int bitmap_blocks = 0; static int db_clean; extern long cur_search, db_top; int init_db(void) { struct stat statbuf; FILE *fp; char buf[80]; off_t offset; int cnew = 1, size; long dbref; /* Make sure "binary" exists and is a directory. */ if (stat("binary", &statbuf) == -1) { /* Directory binary does not exist; create it. */ if (mkdir("binary", READ_WRITE_EXECUTE) == -1) fail_to_start("Cannot create binary directory."); } else if (!S_ISDIR(statbuf.st_mode)) { /* 'binary' is a file; nuke it and create directory. */ if (unlink("binary") == -1) fail_to_start("Cannot delete file 'binary'."); if (mkdir("binary", READ_WRITE_EXECUTE) == -1) fail_to_start("Cannot create binary directory."); } /* Check if binary/clean exists and contains the right version number. */ fp = fopen("binary/clean", "r"); if (fp) { if (fgets(buf, 80, fp) && atoi(buf) == VERSION_MAJOR) { if (fgets(buf, 80, fp) && atoi(buf) == VERSION_MINOR) { if (fgets(buf, 80, fp) && atoi(buf) == VERSION_BUGFIX) { cnew = 0; fgets(buf, 80, fp); cur_search = atoi(buf); } } } fclose(fp); } database_file = fopen("binary/objects", (cnew) ? "w+" : "r+"); if (!database_file) fail_to_start("Cannot open object database file."); /* Open hash table. */ lookup_open("binary/index", cnew); /* Determine size of chunk file for allocation bitmap. */ if (stat("binary/objects", &statbuf) < 0) fail_to_start("Cannot stat database file."); /* Allocate bitmap. */ bitmap_blocks = ROUND_UP(LOGICAL_BLOCK(statbuf.st_size) + DB_BITBLOCK, 8); bitmap = EMALLOC(char, bitmap_blocks / 8); memset(bitmap, 0, bitmap_blocks / 8); dbref = lookup_first_dbref(); while (dbref != NOT_AN_IDENT) { if (!lookup_retrieve_dbref(dbref, &offset, &size)) fail_to_start("Database index is inconsistent."); if (dbref >= db_top) db_top = dbref + 1; /* Mark blocks as busy in the bitmap. */ db_mark(LOGICAL_BLOCK(offset), size); dbref = lookup_next_dbref(); } /* If database is new, mark it as clean; otherwise, it was clean * already. */ if (cnew) db_is_clean(); else db_clean = 1; return cnew; } /* Grow the bitmap to given size. */ static void grow_bitmap(int new_blocks) { new_blocks = ROUND_UP(new_blocks, 8); bitmap = EREALLOC(bitmap, char, new_blocks / 8); memset(&bitmap[bitmap_blocks / 8], 0, (new_blocks / 8) - (bitmap_blocks / 8)); bitmap_blocks = new_blocks; } static void db_mark(off_t start, int size) { int i, blocks; blocks = NEEDED(size, BLOCK_SIZE); while (start + blocks > bitmap_blocks) grow_bitmap(bitmap_blocks + DB_BITBLOCK); for (i = start; i < start + blocks; i++) bitmap[i >> 3] |= (1 << (i & 7)); } static void db_unmark(off_t start, int size) { int i, blocks; blocks = NEEDED(size, BLOCK_SIZE); /* Remember a free block was here. */ last_free = start; for (i = start; i < start + blocks; i++) bitmap[i >> 3] &= ~(1 << (i & 7)); } static int db_alloc(int size) { int blocks_needed, b, count, starting_block, over_the_top; b = last_free; blocks_needed = NEEDED(size, BLOCK_SIZE); over_the_top = 0; while (1) { if (b >= bitmap_blocks) { /* Only wrap around once. */ if (!over_the_top) { b = 0; over_the_top = 1; } else { grow_bitmap(b + DB_BITBLOCK); } } starting_block = b; for (count = 0; count < blocks_needed; count++) { if (bitmap[b >> 3] & (1 << (b & 7))) break; b++; if (b >= bitmap_blocks) grow_bitmap(b + DB_BITBLOCK); } if (count == blocks_needed) { /* Mark these blocks taken and return the starting block. */ for (b = starting_block; b < starting_block + count; b++) bitmap[b >> 3] |= (1 << (b & 7)); last_free = b; return starting_block; } b++; } } int db_get(Object *object, long dbref) { off_t offset; int size; /* Get the object location for the dbref. */ if (!lookup_retrieve_dbref(dbref, &offset, &size)) return 0; /* seek to location */ if (fseek(database_file, offset, SEEK_SET)) return 0; unpack_object(object, database_file); return 1; } int db_put(Object *obj, long dbref) { off_t old_offset, new_offset; int old_size, new_size = size_object(obj); db_is_dirty(); if (lookup_retrieve_dbref(dbref, &old_offset, &old_size)) { if (NEEDED(new_size, BLOCK_SIZE) > NEEDED(old_size, BLOCK_SIZE)) { db_unmark(LOGICAL_BLOCK(old_offset), old_size); new_offset = BLOCK_OFFSET(db_alloc(new_size)); } else { new_offset = old_offset; } } else { new_offset = BLOCK_OFFSET(db_alloc(new_size)); } if (!lookup_store_dbref(dbref, new_offset, new_size)) return 0; if (fseek(database_file, new_offset, SEEK_SET)) { write_log("ERROR: Seek failed for %l.", dbref); return 0; } pack_object(obj, database_file); fflush(database_file); return 1; } int db_check(long dbref) { off_t offset; int size; return lookup_retrieve_dbref(dbref, &offset, &size); } int db_del(long dbref) { off_t offset; int size; /* Get offset and size of key. */ if (!lookup_retrieve_dbref(dbref, &offset, &size)) return 0; /* Remove key from location db. */ if (!lookup_remove_dbref(dbref)) return 0; db_is_dirty(); /* Mark free space in bitmap */ db_unmark(LOGICAL_BLOCK(offset), size); /* Mark object dead in file */ if (fseek(database_file, offset, SEEK_SET)) { write_log("ERROR: Failed to seek to object %l.", dbref); return 0; } fputs("delobj", database_file); fflush(database_file); return 1; } void db_close(void) { lookup_close(); fclose(database_file); free(bitmap); db_is_clean(); } void db_flush(void) { lookup_sync(); db_is_clean(); } static void db_is_clean(void) { FILE *fp; if (db_clean) return; /* Create 'clean' file. */ fp = open_scratch_file("binary/clean", "w"); if (!fp) panic("Cannot create file 'clean'."); fformat(fp, "%d\n%d\n%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_BUGFIX); fformat(fp, "%l\n", cur_search); close_scratch_file(fp); db_clean = 1; } static void db_is_dirty(void) { if (db_clean) { /* Remove 'clean' file. */ if (unlink("binary/clean") == -1) panic("Cannot remove file 'clean'."); db_clean = 0; } }