1
0
mirror of https://github.com/libretro/RetroArch synced 2024-07-05 09:48:42 +00:00
RetroArch/libretro-db/libretrodb.c

529 lines
11 KiB
C
Raw Normal View History

2015-01-23 04:59:47 +00:00
#include "libretrodb.h"
2015-01-19 21:47:09 +00:00
#include <sys/types.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include "rmsgpack_dom.h"
#include "rmsgpack.h"
#include "bintree.h"
2015-01-23 04:59:47 +00:00
#include "libretrodb_endian.h"
2015-01-19 21:47:09 +00:00
#include "query.h"
2015-01-24 03:04:56 +00:00
struct node_iter_ctx
{
libretrodb_t *db;
libretrodb_index_t *idx;
2015-01-19 21:47:09 +00:00
};
static struct rmsgpack_dom_value sentinal;
static int libretrodb_read_metadata(int fd, libretrodb_metadata_t *md)
2015-01-24 03:04:56 +00:00
{
return rmsgpack_dom_read_into(fd, "count", &md->count, NULL);
2015-01-19 21:47:09 +00:00
}
static int libretrodb_write_metadata(int fd, libretrodb_metadata_t *md)
2015-01-24 03:04:56 +00:00
{
rmsgpack_write_map_header(fd, 1);
rmsgpack_write_string(fd, "count", strlen("count"));
return rmsgpack_write_uint(fd, md->count);
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
static int validate_document(const struct rmsgpack_dom_value * doc)
{
unsigned i;
struct rmsgpack_dom_value key;
struct rmsgpack_dom_value value;
2015-01-24 03:04:56 +00:00
int rv = 0;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (doc->type != RDT_MAP)
return -EINVAL;
2015-01-19 21:47:09 +00:00
2015-09-17 04:39:17 +00:00
for (i = 0; i < doc->val.map.len; i++)
2015-01-24 03:04:56 +00:00
{
2015-09-17 04:39:17 +00:00
key = doc->val.map.items[i].key;
value = doc->val.map.items[i].value;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (key.type != RDT_STRING)
return -EINVAL;
2015-09-17 04:39:17 +00:00
if (key.val.string.len <= 0)
2015-01-24 03:04:56 +00:00
return -EINVAL;
2015-09-17 04:39:17 +00:00
if (key.val.string.buff[0] == '$')
2015-01-24 03:04:56 +00:00
return -EINVAL;
if (value.type != RDT_MAP)
continue;
if ((rv == validate_document(&value)) != 0)
return rv;
}
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
return rv;
2015-01-19 21:47:09 +00:00
}
int libretrodb_create(int fd, libretrodb_value_provider value_provider,
void * ctx)
2015-01-24 03:04:56 +00:00
{
int rv;
off_t root;
libretrodb_metadata_t md;
2015-09-17 07:25:06 +00:00
struct rmsgpack_dom_value item;
uint64_t item_count = 0;
2015-09-17 05:30:32 +00:00
libretrodb_header_t header = {{0}};
2015-01-23 04:14:26 +00:00
2015-01-24 03:04:56 +00:00
memcpy(header.magic_number, MAGIC_NUMBER, sizeof(MAGIC_NUMBER)-1);
root = lseek(fd, 0, SEEK_CUR);
2015-01-23 04:14:26 +00:00
2015-01-24 03:04:56 +00:00
/* We write the header in the end because we need to know the size of
2015-01-23 04:59:47 +00:00
* the db first */
lseek(fd, sizeof(libretrodb_header_t), SEEK_CUR);
2015-01-23 04:59:47 +00:00
2015-01-24 03:04:56 +00:00
while ((rv = value_provider(ctx, &item)) == 0)
{
if ((rv = validate_document(&item)) < 0)
goto clean;
2015-01-19 21:47:09 +00:00
if ((rv = rmsgpack_dom_write(fd, &item)) < 0)
goto clean;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
item_count++;
}
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (rv < 0)
goto clean;
2015-01-19 21:47:09 +00:00
if ((rv = rmsgpack_dom_write(fd, &sentinal)) < 0)
goto clean;
2015-01-19 21:47:09 +00:00
header.metadata_offset = httobe64(lseek(fd, 0, SEEK_CUR));
2015-01-24 03:04:56 +00:00
md.count = item_count;
libretrodb_write_metadata(fd, &md);
lseek(fd, root, SEEK_SET);
write(fd, &header, sizeof(header));
clean:
2015-01-24 03:04:56 +00:00
rmsgpack_dom_value_free(&item);
return rv;
2015-01-19 21:47:09 +00:00
}
static int libretrodb_read_index_header(int fd, libretrodb_index_t *idx)
2015-01-23 04:59:47 +00:00
{
2015-01-24 03:04:56 +00:00
uint64_t name_len = 50;
return rmsgpack_dom_read_into(fd,
2015-01-24 03:04:56 +00:00
"name", idx->name, &name_len,
"key_size", &idx->key_size,
"next", &idx->next, NULL);
2015-01-19 21:47:09 +00:00
}
static void libretrodb_write_index_header(int fd, libretrodb_index_t * idx)
2015-01-23 04:59:47 +00:00
{
rmsgpack_write_map_header(fd, 3);
rmsgpack_write_string(fd, "name", strlen("name"));
rmsgpack_write_string(fd, idx->name, strlen(idx->name));
rmsgpack_write_string(fd, "key_size", strlen("key_size"));
rmsgpack_write_uint(fd, idx->key_size);
rmsgpack_write_string(fd, "next", strlen("next"));
rmsgpack_write_uint(fd, idx->next);
2015-01-19 21:47:09 +00:00
}
void libretrodb_close(libretrodb_t *db)
2015-01-23 04:14:26 +00:00
{
close(db->fd);
db->fd = -1;
2015-01-19 21:47:09 +00:00
}
int libretrodb_open(const char *path, libretrodb_t *db)
2015-01-23 04:59:47 +00:00
{
2015-01-24 03:04:56 +00:00
libretrodb_header_t header;
libretrodb_metadata_t md;
int rv;
#ifdef _WIN32
int fd = open(path, O_RDWR | O_BINARY);
#else
int fd = open(path, O_RDWR);
#endif
2015-01-23 04:59:47 +00:00
if (fd == -1)
2015-01-24 03:04:56 +00:00
return -errno;
2015-01-19 21:47:09 +00:00
strcpy(db->path, path);
db->root = lseek(fd, 0, SEEK_CUR);
2015-01-19 21:47:09 +00:00
if ((rv = read(fd, &header, sizeof(header))) == -1)
2015-01-24 03:04:56 +00:00
{
rv = -errno;
goto error;
}
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (strncmp(header.magic_number, MAGIC_NUMBER, sizeof(MAGIC_NUMBER)) != 0)
{
rv = -EINVAL;
goto error;
}
header.metadata_offset = betoht64(header.metadata_offset);
lseek(fd, header.metadata_offset, SEEK_SET);
2015-01-24 03:04:56 +00:00
if (libretrodb_read_metadata(fd, &md) < 0)
2015-01-24 03:04:56 +00:00
{
rv = -EINVAL;
goto error;
}
db->count = md.count;
db->first_index_offset = lseek(fd, 0, SEEK_CUR);
db->fd = fd;
2015-01-24 03:04:56 +00:00
return 0;
2015-01-19 21:47:09 +00:00
error:
close(fd);
2015-01-24 03:04:56 +00:00
return rv;
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
static int libretrodb_find_index(libretrodb_t *db, const char *index_name,
libretrodb_index_t *idx)
{
off_t eof = lseek(db->fd, 0, SEEK_END);
off_t offset = lseek(db->fd, db->first_index_offset, SEEK_SET);
2015-01-23 04:59:47 +00:00
2015-01-24 03:04:56 +00:00
while (offset < eof)
2015-01-23 04:59:47 +00:00
{
libretrodb_read_index_header(db->fd, idx);
2015-02-01 14:47:02 +00:00
2015-01-23 04:59:47 +00:00
if (strncmp(index_name, idx->name, strlen(idx->name)) == 0)
return 0;
2015-02-01 14:47:02 +00:00
offset = lseek(db->fd, idx->next, SEEK_CUR);
2015-01-23 04:59:47 +00:00
}
2015-01-24 03:04:56 +00:00
return -1;
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
static int node_compare(const void * a, const void * b, void * ctx)
{
return memcmp(a, b, *(uint8_t *)ctx);
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
static int binsearch(const void * buff, const void * item,
uint64_t count, uint8_t field_size, uint64_t * offset)
{
int mid = count / 2;
int item_size = field_size + sizeof(uint64_t);
uint64_t * current = (uint64_t *)buff + (mid * item_size);
int rv = node_compare(current, item, &field_size);
if (rv == 0)
{
*offset = *(uint64_t *)(current + field_size);
return 0;
}
2015-02-01 14:47:02 +00:00
if (count == 0)
2015-01-24 03:04:56 +00:00
return -1;
2015-02-01 14:47:02 +00:00
if (rv > 0)
2015-01-24 03:04:56 +00:00
return binsearch(buff, item, mid, field_size, offset);
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
return binsearch(current + item_size, item,
count - mid, field_size, offset);
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
int libretrodb_find_entry(libretrodb_t *db, const char *index_name,
const void *key, struct rmsgpack_dom_value *out)
2015-01-24 03:04:56 +00:00
{
libretrodb_index_t idx;
int rv;
void * buff;
2015-01-24 03:04:56 +00:00
uint64_t offset;
ssize_t bufflen, nread = 0;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (libretrodb_find_index(db, index_name, &idx) < 0)
return -1;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
bufflen = idx.next;
buff = malloc(bufflen);
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (!buff)
return -ENOMEM;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
while (nread < bufflen)
{
void * buff_ = (uint64_t *)buff + nread;
rv = read(db->fd, buff_, bufflen - nread);
2015-01-24 03:04:56 +00:00
if (rv <= 0)
{
free(buff);
return -errno;
}
nread += rv;
}
2015-01-19 21:47:09 +00:00
rv = binsearch(buff, key, db->count, idx.key_size, &offset);
2015-01-24 03:04:56 +00:00
free(buff);
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (rv == 0)
lseek(db->fd, offset, SEEK_SET);
rv = rmsgpack_dom_read(db->fd, out);
if (rv < 0)
return rv;
2015-01-19 21:47:09 +00:00
return rv;
2015-01-19 21:47:09 +00:00
}
/**
2015-01-23 04:59:47 +00:00
* libretrodb_cursor_reset:
* @cursor : Handle to database cursor.
*
* Resets cursor.
*
* Returns: ???.
**/
2015-01-23 04:59:47 +00:00
int libretrodb_cursor_reset(libretrodb_cursor_t *cursor)
{
cursor->eof = 0;
return lseek(cursor->fd,
cursor->db->root + sizeof(libretrodb_header_t),
2015-01-24 03:04:56 +00:00
SEEK_SET);
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
int libretrodb_cursor_read_item(libretrodb_cursor_t *cursor,
struct rmsgpack_dom_value * out)
{
int rv;
if (cursor->eof)
return EOF;
2015-01-19 21:47:09 +00:00
retry:
rv = rmsgpack_dom_read(cursor->fd, out);
2015-01-24 03:04:56 +00:00
if (rv < 0)
return rv;
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (out->type == RDT_NULL)
{
cursor->eof = 1;
return EOF;
}
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
if (cursor->query)
{
if (!libretrodb_query_filter(cursor->query, out))
{
rmsgpack_dom_value_free(out);
2015-01-24 03:04:56 +00:00
goto retry;
}
2015-01-24 03:04:56 +00:00
}
2015-01-19 21:47:09 +00:00
2015-01-24 03:04:56 +00:00
return 0;
2015-01-19 21:47:09 +00:00
}
/**
2015-01-23 04:59:47 +00:00
* libretrodb_cursor_close:
* @cursor : Handle to database cursor.
*
* Closes cursor and frees up allocated memory.
**/
2015-01-23 04:59:47 +00:00
void libretrodb_cursor_close(libretrodb_cursor_t *cursor)
{
2015-01-27 03:02:10 +00:00
if (!cursor)
return;
close(cursor->fd);
cursor->is_valid = 0;
cursor->fd = -1;
cursor->eof = 1;
cursor->db = NULL;
2015-06-03 14:57:51 +00:00
if (cursor->query)
libretrodb_query_free(cursor->query);
2015-06-03 14:57:51 +00:00
cursor->query = NULL;
2015-01-19 21:47:09 +00:00
}
/**
2015-01-23 04:59:47 +00:00
* libretrodb_cursor_open:
* @db : Handle to database.
* @cursor : Handle to database cursor.
* @q : Query to execute.
*
* Opens cursor to database based on query @q.
*
* Returns: 0 if successful, otherwise negative.
**/
2015-01-27 03:02:10 +00:00
int libretrodb_cursor_open(libretrodb_t *db, libretrodb_cursor_t *cursor,
libretrodb_query_t *q)
{
cursor->fd = dup(db->fd);
if (cursor->fd == -1)
2015-01-27 03:02:10 +00:00
return -errno;
cursor->db = db;
2015-01-27 03:02:10 +00:00
cursor->is_valid = 1;
libretrodb_cursor_reset(cursor);
cursor->query = q;
2015-01-27 03:02:10 +00:00
if (q)
libretrodb_query_inc_ref(q);
2015-01-27 03:02:10 +00:00
return 0;
2015-01-19 21:47:09 +00:00
}
static int node_iter(void * value, void * ctx)
{
struct node_iter_ctx *nictx = (struct node_iter_ctx*)ctx;
2015-01-19 21:47:09 +00:00
if (write(nictx->db->fd, value,
nictx->idx->key_size + sizeof(uint64_t)) > 0)
return 0;
2015-01-19 21:47:09 +00:00
return -1;
2015-01-19 21:47:09 +00:00
}
2015-01-23 04:59:47 +00:00
static uint64_t libretrodb_tell(libretrodb_t *db)
{
return lseek(db->fd, 0, SEEK_CUR);
2015-01-19 21:47:09 +00:00
}
2015-01-24 03:04:56 +00:00
int libretrodb_create_index(libretrodb_t *db,
const char *name, const char *field_name)
{
int rv;
struct node_iter_ctx nictx;
struct rmsgpack_dom_value key;
libretrodb_index_t idx;
struct rmsgpack_dom_value item;
struct rmsgpack_dom_value * field;
uint64_t idx_header_offset;
2015-09-17 07:25:06 +00:00
libretrodb_cursor_t cur = {0};
void * buff = NULL;
uint64_t * buff_u64 = NULL;
uint8_t field_size = 0;
uint64_t item_loc = libretrodb_tell(db);
bintree_t *tree = bintree_new(node_compare, &field_size);
if (!tree)
{
rv = -1;
goto clean;
}
if (libretrodb_cursor_open(db, &cur, NULL) != 0)
{
rv = -1;
goto clean;
}
2015-01-19 21:47:09 +00:00
key.type = RDT_STRING;
2015-09-17 04:39:17 +00:00
key.val.string.len = strlen(field_name);
2015-02-01 14:47:02 +00:00
/* We know we aren't going to change it */
2015-09-17 04:39:17 +00:00
key.val.string.buff = (char *) field_name;
2015-02-01 14:47:02 +00:00
while (libretrodb_cursor_read_item(&cur, &item) == 0)
2015-01-24 03:04:56 +00:00
{
if (item.type != RDT_MAP)
2015-01-24 03:04:56 +00:00
{
rv = -EINVAL;
printf("Only map keys are supported\n");
goto clean;
}
2015-01-24 03:04:56 +00:00
field = rmsgpack_dom_value_map_value(&item, &key);
2015-01-24 03:04:56 +00:00
if (!field)
2015-01-24 03:04:56 +00:00
{
rv = -EINVAL;
printf("field not found in item\n");
goto clean;
}
2015-01-24 03:04:56 +00:00
if (field->type != RDT_BINARY)
2015-01-24 03:04:56 +00:00
{
rv = -EINVAL;
printf("field is not binary\n");
goto clean;
}
2015-01-19 21:47:09 +00:00
2015-09-17 04:39:17 +00:00
if (field->val.binary.len == 0)
2015-01-24 03:04:56 +00:00
{
rv = -EINVAL;
printf("field is empty\n");
goto clean;
}
if (field_size == 0)
2015-09-17 04:39:17 +00:00
field_size = field->val.binary.len;
else if (field->val.binary.len != field_size)
2015-01-24 03:04:56 +00:00
{
rv = -EINVAL;
printf("field is not of correct size\n");
goto clean;
}
2015-02-01 14:47:02 +00:00
buff = malloc(field_size + sizeof(uint64_t));
if (!buff)
2015-01-24 03:04:56 +00:00
{
rv = -ENOMEM;
goto clean;
}
2015-06-03 14:57:51 +00:00
2015-09-17 04:39:17 +00:00
memcpy(buff, field->val.binary.buff, field_size);
2015-06-03 14:57:51 +00:00
buff_u64 = (uint64_t *)buff + field_size;
2015-06-03 14:57:51 +00:00
memcpy(buff_u64, &item_loc, sizeof(uint64_t));
2015-09-05 17:51:55 +00:00
2015-09-17 07:25:06 +00:00
if (bintree_insert(tree, buff) != 0)
{
printf("Value is not unique: ");
rmsgpack_dom_value_print(field);
printf("\n");
rv = -EINVAL;
goto clean;
}
buff = NULL;
rmsgpack_dom_value_free(&item);
item_loc = libretrodb_tell(db);
}
(void)rv;
(void)idx_header_offset;
idx_header_offset = lseek(db->fd, 0, SEEK_END);
strncpy(idx.name, name, 50);
idx.name[49] = '\0';
idx.key_size = field_size;
idx.next = db->count * (field_size + sizeof(uint64_t));
libretrodb_write_index_header(db->fd, &idx);
nictx.db = db;
nictx.idx = &idx;
2015-09-17 07:25:06 +00:00
bintree_iterate(tree, node_iter, &nictx);
bintree_free(tree);
2015-09-17 05:09:31 +00:00
clean:
rmsgpack_dom_value_free(&item);
if (buff)
free(buff);
if (cur.is_valid)
libretrodb_cursor_close(&cur);
return 0;
2015-01-19 21:47:09 +00:00
}