/**
* @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;
new_map = mremap(data->base_memory,
old_file_size, new_file_size, MREMAP_MAYMOVE);
if (new_map == MAP_FAILED)
return 1;
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);
}