/* 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 "loc.h"
#include "object.h"
#include "cache.h"
#include "log.h"
#include "util.h"
#include "dbpack.h"
#include "memory.h"
#include "config.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)
#define READ_WRITE (S_IRUSR | S_IWUSR)
#define READ_WRITE_EXECUTE (S_IRUSR | S_IWUSR | S_IXUSR)
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 int cur_search;
int init_db(void)
{
struct stat statbuf;
FILE *fp;
char buf[80], *key;
off_t offset;
int new = 1, size;
/* 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) {
new = 0;
fgets(buf, 80, fp);
cur_search = atoi(buf);
}
}
}
fclose(fp);
}
database_file = fopen("binary/objects", (new) ? "w+" : "r+");
if (!database_file)
fail_to_start("Cannot open object database file.");
/* Open hash table. */
loc_open("binary/index", new);
/* 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);
key = loc_first();
while (key) {
if (!loc_retrieve(key, &offset, &size))
fail_to_start("Database index is inconsistent.");
/* Mark blocks as busy in the bitmap. */
db_mark(LOGICAL_BLOCK(offset), size);
key = loc_next();
}
/* If database is new, mark it as clean; otherwise, it was clean
* already. */
if (new)
db_is_clean();
else
db_clean = 1;
return new;
}
/* 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, char *name)
{
off_t offset;
int size;
/* Get the object location for the name. */
if (!loc_retrieve(name, &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, char *name)
{
off_t old_offset, new_offset;
int old_size, new_size = size_object(obj);
db_is_dirty();
if (loc_retrieve(name, &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 (!loc_store(name, new_offset, new_size))
return 0;
if (fseek(database_file, new_offset, SEEK_SET)) {
write_log("ERROR: Seek failed for %s.", name);
return 0;
}
pack_object(obj, database_file);
fflush(database_file);
return 1;
}
int db_check(char *name)
{
off_t offset;
int size;
return loc_retrieve(name, &offset, &size);
}
int db_del(char *name)
{
off_t offset;
int size;
/* Get offset and size of key. */
if (!loc_retrieve(name, &offset, &size))
return 0;
/* Remove key from location db. */
if (!loc_remove(name))
return 0;
/* 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 %s", name);
return 0;
}
fputs("delobj", database_file);
fflush(database_file);
return 1;
}
void db_close(void)
{
loc_close();
fclose(database_file);
free(bitmap);
db_is_clean();
}
void db_flush(void)
{
loc_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, "%d\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;
}
}