| /** |
| * @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> |
| #include <sys/fcntl.h> |
| #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); |
| } |