/* udb.h - u(micro) data base, stores data and index information in mmap file. * By W.C.A. Wijngaards * Copyright 2010, NLnet Labs. * BSD, see LICENSE. */ #ifndef UDB_H #define UDB_H #include /** * The micro data base UDB. * * File data.udb is mmapped and used to lookup and edit. * it contains a header with space-allocation-info, and a reference to the * base information, an object that is the entry point for the file. * Then it contains a lot of data and index objects. * * The space allocator is 'buddy system', 1megareas, larger get own area. * So worst case is 2xdata filesize (+header). Growth semi-linear. * Chunks have size and type (for recovery). Call to reserve space. * Call to 'realloc-in-place', if space permits. * * Usually you want a record-type and its indexes (sorted) to be stored in * the file. This is a table (named by string). The record is opaque * data. * * To be able to use pointers in the mmapped file, there is conversion of * relative-pointers(to file base) to system-pointers. * * If an item is moved its internal pointers need to be recalculated. * Thus a recordtype (that has internal pointers) must provide a routine. * Structures that are 'on-disk', are denoted with _d. Except rel_ptr which * is also on-disk. * * About 64-bit trouble. The pointer-size which which the application is * compiled determines the file layout, because this makes it perform well * in a mmap. It could in theory be converted if you really wanted to. * Nonpointer data is best stored as a fixed bitsize (uint8, 16, 32, 64). */ typedef struct udb_base udb_base; typedef struct udb_alloc udb_alloc; /** these checks are very slow, disabled by default */ #if 0 /** perform extra checks (when --enable-checking is used) */ #ifndef NDEBUG #define UDB_CHECK 1 #endif #endif /** pointers are stored like this */ typedef uint64_t udb_void; /** convert relptr to usable pointer */ #define UDB_REL(base, relptr) ((base) + (relptr)) /** from system pointer to relative pointer */ #define UDB_SYSTOREL(base, ptr) ((udb_void)((void*)(ptr) - (base))) /** MAX 2**x exponent of alloced chunks, for 1Mbytes. The smallest * chunk is 16bytes (8preamble+8data), so 0-3 is unused. */ #define UDB_ALLOC_CHUNKS_MAX 20 /** size of areas that are subdivided */ #define UDB_ALLOC_CHUNK_SIZE ((uint64_t)1<= amount. */ int udb_exp_size(uint64_t amount); /** * Utility for alloc, what is the size that the current offset supports * as a maximum 2**x chunk. * Does not work for offset = 0 (result is infinite). * @param offset: the offset into the memory region. * @return maximum exponent where 2**x is fits the offset, thus * offset % (2**x) == 0 and x cannot be larger. */ int udb_exp_offset(uint64_t offset); /** * Convert pointer to the data part to a pointer to the base of the chunk. * @param data: data part. * @return pointer to the base of the chunk. */ udb_void chunk_from_dataptr_ext(udb_void data); /** * Create empty UDB allocate structure to write to disk to initialize file. * @param a: allocation structure to initialize. system pointer. */ void udb_alloc_init_new(udb_alloc_d* a); /** * Create new udb allocator, with specific data on disk * @param udb: the udb. * @param disk: disk data. * @return udb allocator or NULL on (malloc) failure. */ udb_alloc* udb_alloc_create(udb_base* udb, udb_alloc_d* disk); /** * Free the udb allocator from memory. * @param alloc: the udb space allocator. */ void udb_alloc_delete(udb_alloc* alloc); /** * Allocate space on the disk. * This may involve closing and reopening the mmap. * @param alloc: the udb space allocator. * @param sz: size you want to use. * @return relative pointer (or 0 on alloc failure). */ udb_void udb_alloc_space(udb_alloc* alloc, size_t sz); /** * Allocate space on disk, give already the data you want there. * This may involve closing and reopening the mmap. * @param alloc: the udb space allocator. * @param d: data you want there (system pointer). * @param sz: size you want to use. * @return relative pointer (or 0 on alloc failure). */ udb_void udb_alloc_init(udb_alloc* alloc, void* d, size_t sz); /** * free allocated space. It may shrink the file. * This may involve closing and reopening the mmap. * @param alloc: the udb space allocator. * @param r: relative pointer to data you want to free. * @param sz: the size of the data you stop using. * @return false if the free failed, it failed the close and mmap. */ int udb_alloc_free(udb_alloc* alloc, udb_void r, size_t sz); /** * realloc an existing allocated space. It may grow the file. * This may involve closing and reopening the mmap. * It could also use the existing space where it is now. * @param alloc: the udb space allocator. * @param r: relative pointer to data you want to realloc. * if 0 then this is alloc_space(), and osz is ignored. * @param osz: the old size of the data. * @param sz: the size of the data you want to get. * if this is 0 then a free() is done, but please do it directly, * as you then get a returnvalue (file errors). * @return relative pointer (0 on alloc failure, same if not moved). */ udb_void udb_alloc_realloc(udb_alloc* alloc, udb_void r, size_t osz, size_t sz); /** * Prepare for a lot of new entries. Grow space for that. * This can involve closing and reopening the mmap. * This space (if large) is going to be released on next free() or close(). * @param alloc: the udb space allocator. * @param sz: size of the entries. * @param num: number of entries. * @return false on failure to grow or re-mmap. */ int udb_alloc_grow(udb_alloc* alloc, size_t sz, size_t num); /** * Set the alloc type for a newly alloced piece of data * @param alloc: the udb space allocator. * @param r: relativeptr to the data. * @param tp: the type of that block. */ void udb_alloc_set_type(udb_alloc* alloc, udb_void r, udb_chunk_type tp); /** * See if a pointer could be valid (it points within valid space), * for the given type side. For debug checks. * @param udb: the udb * @param to: the ptr (offset). * @param destsize: the size_of of the destination of the pointer. * @return true if it points to a valid region. */ int udb_valid_offset(udb_base* udb, udb_void to, size_t destsize); /** * See if a pointer is valid (it points to a chunk). For debug checks. * @param udb: the udb. * @param to: the ptr (offset). * @return true if it points to the start of a chunks data region. */ int udb_valid_dataptr(udb_base* udb, udb_void to); /** * See if a pointer is on the relptrlist for dataptr. For debug checks. * @param udb: the udb. * @param rptr: the rel_ptr (offset). * @param to: dataptr of the chunk on which ptrlist the rptr is searched. * @return true if rptr is valid and on the ptrlist. */ int udb_valid_rptr(udb_base* udb, udb_void rptr, udb_void to); /*** UDB_REL_PTR ***/ /** * Init a new UDB rel ptr at NULL. * @param ptr: sysptr, becomes inited. */ void udb_rel_ptr_init(udb_rel_ptr* ptr); /** * Unlink a UDB rel ptr. * @param base: the udb base * @param ptr: sysptr, unlinked */ void udb_rel_ptr_unlink(void* base, udb_rel_ptr* ptr); /** * Link a UDB rel ptr to a new chunk * @param base: the udb base * @param ptr: sysptr, linked to new value. * @param to: the data to point to (relative ptr). */ void udb_rel_ptr_link(void* base, udb_rel_ptr* ptr, udb_void to); /** * Change rel ptr to a new value (point to another record) * @param base: the udb base * @param ptr: sysptr, points to new value. * @param to: the data to point to (relative ptr). */ void udb_rel_ptr_set(void* base, udb_rel_ptr* ptr, udb_void to); /** * A chunk has moved and now edit all the relptrs in list to fix them up * @param base: the udb base * @param list: start of the ptr list * @param to: where the chunk has moved to relptr to its userdata. */ void udb_rel_ptr_edit(void* base, udb_void list, udb_void to); /** * Get system pointer. Assumes there is a variable named 'base' * that points to the udb base. * @param ptr: the relative pointer (a sysptr to it). * @return void* to the data. */ #define UDB_SYSPTR(ptr) UDB_REL(base, (ptr)->data) /** get sys ptr for char* string */ #define UDB_CHAR(ptr) ((char*)UDB_REL(base, ptr)) /** get sys ptr for udb_rel_ptr */ #define UDB_REL_PTR(ptr) ((udb_rel_ptr*)UDB_REL(base, ptr)) /** get sys ptr for udb_glob_d */ #define UDB_GLOB(ptr) ((udb_glob_d*)UDB_REL(base, ptr)) /** get sys ptr for udb_chunk_d */ #define UDB_CHUNK(ptr) ((udb_chunk_d*)UDB_REL(base, ptr)) /** get sys ptr for udb_free_chunk_d */ #define UDB_FREE_CHUNK(ptr) ((udb_free_chunk_d*)UDB_REL(base, ptr)) /** get sys ptr for udb_xl_chunk_d */ #define UDB_XL_CHUNK(ptr) ((udb_xl_chunk_d*)UDB_REL(base, ptr)) /* udb_ptr */ /** * Initialize an udb ptr. Set to NULL. (and thus not linked can be deleted). * You MUST set it to 0 before you stop using the ptr. * @param ptr: the ptr to initialise (caller has allocated it). * @param udb: the udb base to link it to. */ void udb_ptr_init(udb_ptr* ptr, udb_base* udb); /** * Set udp ptr to a new value. If set to NULL you can delete it. * @param ptr: the ptr. * @param udb: the udb base to link up with that data segment's administration. * @param newval: new value to point to (udb_void relative file offset to data). */ void udb_ptr_set(udb_ptr* ptr, udb_base* udb, udb_void newval); /** dereference udb_ptr */ #define UDB_PTR(ptr) (UDB_REL(*((ptr)->base), (ptr)->data)) /** * Ease of use udb ptr, allocate space and return ptr to it * You MUST udb_ptr_set it to 0 before you stop using the ptr. * @param base: udb base to use. * @param ptr: ptr is overwritten, can be uninitialised. * @param type: type of the allocation. * You need a special type if the block contains udb_rel_ptr's. * You can use udb_type_data for plain data. * @param sz: amount to allocate. * @return 0 on alloc failure. */ int udb_ptr_alloc_space(udb_ptr* ptr, udb_base* udb, udb_chunk_type type, size_t sz); /** * Ease of use udb ptr, free space and set ptr to NULL (to it can be deleted). * The space is freed on disk. * @param ptr: the ptr. * @param udb: udb base. * @param sz: the size of the data you stop using. */ void udb_ptr_free_space(udb_ptr* ptr, udb_base* udb, size_t sz); /** * Get pointer to the data of the ptr. or use a macro to cast UDB_PTR to * the type of your structure(.._d) */ static inline uint8_t* udb_ptr_data(udb_ptr* ptr) { return (uint8_t*)UDB_PTR(ptr); } /** * See if udb ptr is null */ static inline int udb_ptr_is_null(udb_ptr* ptr) { return (ptr->data == 0); } /** * Get the type of a udb_ptr chunk. * @param ptr: udb pointer * @return type of chunk */ udb_chunk_type udb_ptr_get_type(udb_ptr* ptr); /** Ease of use, create new pointer to destination relptr * You MUST udb_ptr_set it to 0 before you stop using the ptr. */ static inline void udb_ptr_new(udb_ptr* ptr, udb_base* udb, udb_rel_ptr* d) { udb_ptr_init(ptr, udb); udb_ptr_set(ptr, udb, d->data); } /** Ease of use. Stop using this ptr */ static inline void udb_ptr_unlink(udb_ptr* ptr, udb_base* udb) { if(ptr->data) udb_base_unlink_ptr(udb, ptr); } /* Ease of use. Assign rptr from rptr */ static inline void udb_rptr_set_rptr(udb_rel_ptr* dest, udb_base* udb, udb_rel_ptr* p) { #ifdef UDB_CHECK if(dest->data) { assert(udb_valid_rptr(udb, UDB_SYSTOREL(udb->base, dest), dest->data)); } if(p->data) { assert(udb_valid_rptr(udb, UDB_SYSTOREL(udb->base, p), p->data)); } #endif udb_rel_ptr_set(udb->base, dest, p->data); } /* Ease of use. Assign rptr from ptr */ static inline void udb_rptr_set_ptr(udb_rel_ptr* dest, udb_base* udb, udb_ptr* p) { #ifdef UDB_CHECK if(dest->data) { assert(udb_valid_rptr(udb, UDB_SYSTOREL(udb->base, dest), dest->data)); } if(p->data) { assert(udb_valid_dataptr(udb, p->data)); } #endif udb_rel_ptr_set(udb->base, dest, p->data); } /* Ease of use. Assign ptr from rptr */ static inline void udb_ptr_set_rptr(udb_ptr* dest, udb_base* udb, udb_rel_ptr* p) { #ifdef UDB_CHECK if(p->data) { assert(udb_valid_rptr(udb, UDB_SYSTOREL(udb->base, p), p->data)); } #endif udb_ptr_set(dest, udb, p->data); } /* Ease of use. Assign ptr from ptr */ static inline void udb_ptr_set_ptr(udb_ptr* dest, udb_base* udb, udb_ptr* p) { udb_ptr_set(dest, udb, p->data); } /* Ease of use, zero rptr. You use this to zero an existing pointer. * A new rptr should be rel_ptr_init-ed before it is taken into use. */ static inline void udb_rptr_zero(udb_rel_ptr* dest, udb_base* udb) { #ifdef UDB_CHECK if(dest->data) { assert(udb_valid_rptr(udb, UDB_SYSTOREL(udb->base, dest), dest->data)); } #endif udb_rel_ptr_set(udb->base, dest, 0); } /* Ease of use, zero ptr */ static inline void udb_ptr_zero(udb_ptr* dest, udb_base* udb) { udb_ptr_set(dest, udb, 0); } /** ease of use, delete memory pointed at by relptr */ static inline void udb_rel_ptr_free_space(udb_rel_ptr* ptr, udb_base* udb, size_t sz) { udb_void d = ptr->data; #ifdef UDB_CHECK if(d) { assert(udb_valid_rptr(udb, UDB_SYSTOREL(udb->base, ptr), d)); } #endif udb_rel_ptr_set(udb->base, ptr, 0); udb_alloc_free(udb->alloc, d, sz); } #endif /* UDB_H */