/**
 * @file db_manage.c
 * Management of a DB file
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 */

#define _GNU_SOURCE

#include <stdlib.h>
#ifdef ANDROID
#include <fcntl.h>
#else
#include <sys/fcntl.h>
#endif
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

#include "odb.h"
#include "op_string.h"
#include "op_libiberty.h"

 
static __inline odb_descr_t * odb_to_descr(odb_data_t * data)
{
	return (odb_descr_t *)(((char*)data->base_memory) + data->sizeof_header);
}

 
static __inline odb_node_t * odb_to_node_base(odb_data_t * data)
{
	return (odb_node_t *)(((char *)data->base_memory) + data->offset_node);
}

 
static __inline odb_index_t * odb_to_hash_base(odb_data_t * data)
{
	return (odb_index_t *)(((char *)data->base_memory) + 
				data->offset_node +
				(data->descr->size * sizeof(odb_node_t)));
}

 
/**
 * return the number of bytes used by hash table, node table and header.
 */
static unsigned int tables_size(odb_data_t const * data, odb_node_nr_t node_nr)
{
	size_t size;

	size = node_nr * (sizeof(odb_index_t) * BUCKET_FACTOR);
	size += node_nr * sizeof(odb_node_t);
	size += data->offset_node;

	return size;
}


int odb_grow_hashtable(odb_data_t * data)
{
	unsigned int old_file_size;
	unsigned int new_file_size;
	unsigned int pos;
	void * new_map;

	old_file_size = tables_size(data, data->descr->size);
	new_file_size = tables_size(data, data->descr->size * 2);

	if (ftruncate(data->fd, new_file_size))
		return 1;

#ifdef MISSING_MREMAP
	new_map = mmap(0, new_file_size, PROT_READ | PROT_WRITE,
		MAP_SHARED, data->fd, 0);
#else
	new_map = mremap(data->base_memory,
			 old_file_size, new_file_size, MREMAP_MAYMOVE);
#endif

	if (new_map == MAP_FAILED)
		return 1;

#ifdef MISSING_MREMAP
	munmap(data->base_memory, old_file_size);
#endif

	data->base_memory = new_map;
	data->descr = odb_to_descr(data);
	data->descr->size *= 2;
	data->node_base = odb_to_node_base(data);
	data->hash_base = odb_to_hash_base(data);
	data->hash_mask = (data->descr->size * BUCKET_FACTOR) - 1;

	/* rebuild the hash table, node zero is never used. This works
	 * because layout of file is node table then hash table,
	 * sizeof(node) > sizeof(bucket) and when we grow table we
	 * double size ==> old hash table and new hash table can't
	 * overlap so on the new hash table is entirely in the new
	 * memory area (the grown part) and we know the new hash
	 * hash table is zeroed. That's why we don't need to zero init
	 * the new table */
	/* OK: the above is not exact
	 * if BUCKET_FACTOR < sizeof(bd_node_t) / sizeof(bd_node_nr_t)
	 * all things are fine and we don't need to init the hash
	 * table because in this case the new hash table is completely
	 * inside the new growed part. Avoiding to touch this memory is
	 * useful.
	 */
#if 0
	for (pos = 0 ; pos < data->descr->size*BUCKET_FACTOR ; ++pos)
		data->hash_base[pos] = 0;
#endif

	for (pos = 1; pos < data->descr->current_size; ++pos) {
		odb_node_t * node = &data->node_base[pos];
		size_t index = odb_do_hash(data, node->key);
		node->next = data->hash_base[index];
		data->hash_base[index] = pos;
	}

	return 0;
}


void odb_init(odb_t * odb)
{
	odb->data = NULL;
}


/* the default number of page, calculated to fit in 4096 bytes */
#define DEFAULT_NODE_NR(offset_node)	128
#define FILES_HASH_SIZE                 512

static struct list_head files_hash[FILES_HASH_SIZE];


static void init_hash()
{
	size_t i;
	for (i = 0; i < FILES_HASH_SIZE; ++i)
		list_init(&files_hash[i]);
}


static odb_data_t *
find_samples_data(size_t hash, char const * filename)
{
	struct list_head * pos;

	/* FIXME: maybe an initial init routine ? */
	if (files_hash[0].next == NULL) {
		init_hash();
		return NULL;
	}

	list_for_each(pos, &files_hash[hash]) {
		odb_data_t * entry = list_entry(pos, odb_data_t, list);
		if (strcmp(entry->filename, filename) == 0)
			return entry;
	}

	return NULL;
}


int odb_open(odb_t * odb, char const * filename, enum odb_rw rw,
	     size_t sizeof_header)
{
	struct stat stat_buf;
	odb_node_nr_t nr_node;
	odb_data_t * data;
	size_t hash;
	int err = 0;

	int flags = (rw == ODB_RDWR) ? (O_CREAT | O_RDWR) : O_RDONLY;
	int mmflags = (rw == ODB_RDWR) ? (PROT_READ | PROT_WRITE) : PROT_READ;

	hash = op_hash_string(filename) % FILES_HASH_SIZE;
	data = find_samples_data(hash, filename);
	if (data) {
		odb->data = data;
		data->ref_count++;
		return 0;
	}

	data = xmalloc(sizeof(odb_data_t));
	memset(data, '\0', sizeof(odb_data_t));
	list_init(&data->list);
	data->offset_node = sizeof_header + sizeof(odb_descr_t);
	data->sizeof_header = sizeof_header;
	data->ref_count = 1;
	data->filename = xstrdup(filename);

	data->fd = open(filename, flags, 0644);
	if (data->fd < 0) {
		err = errno;
		goto out;
	}

	if (fstat(data->fd, &stat_buf)) {
		err = errno;
		goto fail;
	}

	if (stat_buf.st_size == 0) {
		size_t file_size;

		if (rw == ODB_RDONLY) {
			err = EIO;
			goto fail;
		}

		nr_node = DEFAULT_NODE_NR(data->offset_node);

		file_size = tables_size(data, nr_node);
		if (ftruncate(data->fd, file_size)) {
			err = errno;
			goto fail;
		}
	} else {
		/* Calculate nr node allowing a sanity check later */
		nr_node = (stat_buf.st_size - data->offset_node) /
			((sizeof(odb_index_t) * BUCKET_FACTOR) + sizeof(odb_node_t));
	}

	data->base_memory = mmap(0, tables_size(data, nr_node), mmflags,
				MAP_SHARED, data->fd, 0);

	if (data->base_memory == MAP_FAILED) {
		err = errno;
		goto fail;
	}

	data->descr = odb_to_descr(data);

	if (stat_buf.st_size == 0) {
		data->descr->size = nr_node;
		/* page zero is not used */
		data->descr->current_size = 1;
	} else {
		/* file already exist, sanity check nr node */
		if (nr_node != data->descr->size) {
			err = EINVAL;
			goto fail_unmap;
		}
	}

	data->hash_base = odb_to_hash_base(data);
	data->node_base = odb_to_node_base(data);
	data->hash_mask = (data->descr->size * BUCKET_FACTOR) - 1;

	list_add(&data->list, &files_hash[hash]);
	odb->data = data;
out:
	return err;
fail_unmap:
	munmap(data->base_memory, tables_size(data, nr_node));
fail:
	close(data->fd);
	free(data->filename);
	free(data);
	odb->data = NULL;
	goto out;
}


void odb_close(odb_t * odb)
{
	odb_data_t * data = odb->data;

	if (data) {
		data->ref_count--;
		if (data->ref_count == 0) {
			size_t size = tables_size(data, data->descr->size);
			list_del(&data->list);
			munmap(data->base_memory, size);
			if (data->fd >= 0)
				close(data->fd);
			free(data->filename);
			free(data);
			odb->data = NULL;
		}
	}
}


int odb_open_count(odb_t const * odb)
{
	if (!odb->data)
		return 0;
	return odb->data->ref_count;
}


void * odb_get_data(odb_t * odb)
{
	return odb->data->base_memory;
}


void odb_sync(odb_t const * odb)
{
	odb_data_t * data = odb->data;
	size_t size;

	if (!data)
		return;

	size = tables_size(data, data->descr->size);
	msync(data->base_memory, size, MS_ASYNC);
}