Change the internal mapping to a tree of chunk

This is intended to save memory as now we can use a fixed
block of 17 bytes to store the block information, compared
to the previous 28/36 bytes.
This commit is contained in:
Andrea Mazzoleni 2015-10-04 22:02:05 +02:00
parent fa775d24a8
commit 9bc5f9e5ce
12 changed files with 900 additions and 258 deletions

View file

@ -59,6 +59,7 @@ noinst_HEADERS = \
tommyds/tommyhash.h \
tommyds/tommyhashdyn.h \
tommyds/tommylist.h \
tommyds/tommytree.h \
tommyds/tommytypes.h \
tommyds/tommyarray.c \
tommyds/tommyarrayblk.c \
@ -66,6 +67,7 @@ noinst_HEADERS = \
tommyds/tommyhash.c \
tommyds/tommyhashdyn.c \
tommyds/tommylist.c \
tommyds/tommytree.c \
cmdline/portable.h \
cmdline/snapraid.h \
cmdline/util.h \

View file

@ -50,7 +50,7 @@ struct snapraid_hash* hash_alloc(struct snapraid_state* state, struct snapraid_d
/* set the back pointer */
for (i = 0; i < file->blockmax; ++i) {
struct snapraid_block* block = fs_file2block_get(disk, file, i);
struct snapraid_block* block = fs_file2block_get(file, i);
memcpy(buf + i * HASH_SIZE, block->hash, HASH_SIZE);

View file

@ -355,24 +355,6 @@ int filter_content(tommy_list* contentlist, const char* path)
return 0;
}
/**
* Sets the file containing the block.
*/
static void block_file_set(struct snapraid_block* block, struct snapraid_file* file)
{
uintptr_t ptr = (uintptr_t)file;
/* ensure that the pointer doesn't use the flag space */
if ((ptr & (uintptr_t)BLOCK_STATE_MASK) != 0) {
/* LCOV_EXCL_START */
log_fatal("Internal error for pointer not aligned\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
block->private_file_mixed = (block->private_file_mixed & (uintptr_t)BLOCK_STATE_MASK) | ptr;
}
struct snapraid_file* file_alloc(unsigned block_size, const char* sub, data_off_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical)
{
struct snapraid_file* file;
@ -390,13 +372,6 @@ struct snapraid_file* file_alloc(unsigned block_size, const char* sub, data_off_
file->blockvec = malloc_nofail(file->blockmax * sizeof(struct snapraid_block));
for (i = 0; i < file->blockmax; ++i) {
file->blockvec[i].private_parity_pos = POS_INVALID;
file->blockvec[i].private_file_mixed = 0;
/* set the file */
block_file_set(&file->blockvec[i], file);
/* set an invalid hash */
block_state_set(&file->blockvec[i], BLOCK_STATE_CHG);
hash_invalid_set(file->blockvec[i].hash);
}
@ -421,12 +396,8 @@ struct snapraid_file* file_dup(struct snapraid_file* copy)
file->blockvec = malloc_nofail(file->blockmax * sizeof(struct snapraid_block));
for (i = 0; i < file->blockmax; ++i) {
file->blockvec[i].private_file_mixed = copy->blockvec[i].private_file_mixed;
file->blockvec[i].private_parity_pos = copy->blockvec[i].private_parity_pos;
file->blockvec[i].state = copy->blockvec[i].state;
memcpy(file->blockvec[i].hash, copy->blockvec[i].hash, HASH_SIZE);
/* set the file to overwrite the copied one */
block_file_set(&file->blockvec[i], file);
}
return file;
@ -632,6 +603,55 @@ int file_pathstamp_compare(const void* void_a, const void* void_b)
return file_stamp_compare(void_a, void_b);
}
struct snapraid_chunk* chunk_alloc(block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos, block_off_t count)
{
struct snapraid_chunk* chunk;
chunk = malloc_nofail(sizeof(struct snapraid_chunk));
chunk->parity_pos = parity_pos;
chunk->file = file;
chunk->file_pos = file_pos;
chunk->count = count;
return chunk;
}
void chunk_free(struct snapraid_chunk* chunk)
{
free(chunk);
}
int chunk_parity_compare(const void* void_a, const void* void_b)
{
const struct snapraid_chunk* arg_a = void_a;
const struct snapraid_chunk* arg_b = void_b;
if (arg_a->parity_pos < arg_b->parity_pos)
return -1;
if (arg_a->parity_pos > arg_b->parity_pos)
return 1;
return 0;
}
int chunk_file_compare(const void* void_a, const void* void_b)
{
const struct snapraid_chunk* arg_a = void_a;
const struct snapraid_chunk* arg_b = void_b;
if (arg_a->file < arg_b->file)
return -1;
if (arg_a->file > arg_b->file)
return 1;
if (arg_a->file_pos < arg_b->file_pos)
return -1;
if (arg_a->file_pos > arg_b->file_pos)
return 1;
return 0;
}
struct snapraid_link* link_alloc(const char* sub, const char* linkto, unsigned link_flag)
{
struct snapraid_link* link;
@ -724,7 +744,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev
tommy_hashdyn_init(&disk->linkset);
tommy_list_init(&disk->dirlist);
tommy_hashdyn_init(&disk->dirset);
tommy_arrayblk_init(&disk->blockarr);
tommy_tree_init(&disk->fs_parity, chunk_parity_compare);
tommy_tree_init(&disk->fs_file, chunk_file_compare);
return disk;
}
@ -733,6 +754,7 @@ void disk_free(struct snapraid_disk* disk)
{
tommy_list_foreach(&disk->filelist, (tommy_foreach_func*)file_free);
tommy_list_foreach(&disk->deletedlist, (tommy_foreach_func*)file_free);
tommy_tree_foreach(&disk->fs_file, (tommy_foreach_func*)chunk_free);
tommy_hashdyn_done(&disk->inodeset);
tommy_hashdyn_done(&disk->pathset);
tommy_hashdyn_done(&disk->stampset);
@ -740,87 +762,32 @@ void disk_free(struct snapraid_disk* disk)
tommy_hashdyn_done(&disk->linkset);
tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free);
tommy_hashdyn_done(&disk->dirset);
tommy_arrayblk_done(&disk->blockarr);
free(disk);
}
block_off_t disk_block_size(struct snapraid_disk* disk)
struct chunk_disk_empty {
block_off_t blockmax;
};
/**
* Search for any block inside the specified blockmax.
*/
static int chunk_disk_empty_compare(const void* void_a, const void* void_b)
{
return tommy_arrayblk_size(&disk->blockarr);
}
const struct chunk_disk_empty* arg_a = void_a;
const struct snapraid_chunk* arg_b = void_b;
void fs_par2file_set(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos)
{
(void)disk;
assert(file_pos < file->blockmax);
file->blockvec[file_pos].private_parity_pos = parity_pos;
}
struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos)
{
struct snapraid_block* block = fs_par2block_get(disk, parity_pos);
struct snapraid_file* file = (struct snapraid_file*)(block->private_file_mixed & ~(uintptr_t)BLOCK_STATE_MASK);
if (block == BLOCK_EMPTY)
/* if the block is inside the specified blockmax, it's found */
if (arg_a->blockmax > arg_b->parity_pos)
return 0;
if (file_pos) {
if (block < file->blockvec || block >= file->blockvec + file->blockmax) {
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency in block %u ownership\n", block->private_parity_pos);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
*file_pos = block - file->blockvec;
}
return file;
}
block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
{
(void)disk;
assert(file_pos < file->blockmax);
return file->blockvec[file_pos].private_parity_pos;
}
struct snapraid_block* fs_file2block_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
{
(void)disk;
assert(file_pos < file->blockmax);
return &file->blockvec[file_pos];
}
void fs_par2block_clear(struct snapraid_disk* disk, block_off_t pos)
{
tommy_arrayblk_grow(&disk->blockarr, pos + 1);
tommy_arrayblk_set(&disk->blockarr, pos, BLOCK_EMPTY);
}
void fs_par2block_set(struct snapraid_disk* disk, block_off_t pos, struct snapraid_block* block)
{
tommy_arrayblk_grow(&disk->blockarr, pos + 1);
tommy_arrayblk_set(&disk->blockarr, pos, block);
}
struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos)
{
if (parity_pos < tommy_arrayblk_size(&disk->blockarr))
return tommy_arrayblk_get(&disk->blockarr, parity_pos);
else
return BLOCK_EMPTY;
/* otherwise search for a smaller one */
return -1;
}
int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
{
block_off_t i;
struct chunk_disk_empty arg = { blockmax };
/* if there is an element, it's not empty */
/* even if links and dirs have no block allocation */
@ -831,35 +798,208 @@ int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
if (!tommy_list_empty(&disk->dirlist))
return 0;
/* limit the search to the size of the disk */
if (blockmax > tommy_arrayblk_size(&disk->blockarr))
blockmax = tommy_arrayblk_size(&disk->blockarr);
/* checks all the blocks to search for deleted ones */
/* this search is slow, but it's done only if no file is present */
for (i = 0; i < blockmax; ++i) {
struct snapraid_block* block = tommy_arrayblk_get(&disk->blockarr, i);
unsigned block_state = block_state_get(block);
switch (block_state) {
case BLOCK_STATE_EMPTY :
/* empty block are expected for an empty disk */
break;
case BLOCK_STATE_DELETED :
/* if there is a deleted block, the disk is not empty */
return 0;
default :
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency for used block in disk '%s' without files\n", disk->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
/* search for any chunk inside blockmax */
if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare, &arg) != 0)
return 0;
/* finally, it's empty */
return 1;
}
struct chunk_disk_size {
block_off_t size;
};
/**
* Search for the chunk with the highest parity position.
*
* The maximum parity position is stored as size.
*/
static int chunk_disk_size_compare(const void* void_a, const void* void_b)
{
struct chunk_disk_size* arg_a = (void*)void_a;
const struct snapraid_chunk* arg_b = void_b;
/* get the maximum size */
if (arg_a->size < arg_b->parity_pos + arg_b->count)
arg_a->size = arg_b->parity_pos + arg_b->count;
/* search always for a bigger one */
return 1;
}
block_off_t disk_size(struct snapraid_disk* disk)
{
struct chunk_disk_size arg = { 0 };
tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare, &arg);
return arg.size;
}
struct chunk_parity_inside {
block_off_t parity_pos;
};
/**
* Search for the chunk containing the specified parity position.
*/
static int chunk_parity_inside_compare(const void* void_a, const void* void_b)
{
const struct chunk_parity_inside* arg_a = void_a;
const struct snapraid_chunk* arg_b = void_b;
if (arg_a->parity_pos < arg_b->parity_pos)
return -1;
if (arg_a->parity_pos >= arg_b->parity_pos + arg_b->count)
return 1;
return 0;
}
struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos)
{
struct chunk_parity_inside arg = { parity_pos };
struct snapraid_chunk* chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare, &arg);
if (!chunk)
return 0;
if (file_pos)
*file_pos = chunk->file_pos + (parity_pos - chunk->parity_pos);
return chunk->file;
}
struct chunk_file_inside {
struct snapraid_file* file;
block_off_t file_pos;
};
/**
* Search for the chunk containing the specified file position.
*/
static int chunk_file_inside_compare(const void* void_a, const void* void_b)
{
const struct chunk_file_inside* arg_a = void_a;
const struct snapraid_chunk* arg_b = void_b;
if (arg_a->file < arg_b->file)
return -1;
if (arg_a->file > arg_b->file)
return 1;
if (arg_a->file_pos < arg_b->file_pos)
return -1;
if (arg_a->file_pos >= arg_b->file_pos + arg_b->count)
return 1;
return 0;
}
block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
{
struct chunk_file_inside arg = { file, file_pos };
struct snapraid_chunk* chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare, &arg);
if (!chunk) {
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency for a file without parity in disk '%s'\n", disk->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
return chunk->parity_pos + (file_pos - chunk->file_pos);
}
void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos)
{
struct snapraid_chunk* chunk;
if (file_pos > 0) {
/* search an existing chunk for the previous file_pos */
struct chunk_file_inside arg = { file, file_pos - 1 };
chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare, &arg);
if (chunk != 0 && parity_pos == chunk->parity_pos + chunk->count) {
/* ensure that we are really extending a chunk */
if (file_pos != chunk->file_pos + chunk->count) {
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency extending a chunk in disk '%s'\n", disk->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* extend the existing chunk */
++chunk->count;
return;
}
}
/* a chunk doesn't exist, and we have to create a new one */
chunk = chunk_alloc(parity_pos, file, file_pos, 1);
/* insert te chunk in the trees */
tommy_tree_insert(&disk->fs_parity, &chunk->parity_node, chunk);
tommy_tree_insert(&disk->fs_file, &chunk->file_node, chunk);
}
void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
{
struct chunk_parity_inside arg = { parity_pos };
struct snapraid_chunk* chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare, &arg);
if (!chunk) {
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency for clearing a not existing block in disk '%s'\n", disk->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* if it's the only block of the chunk, delete it */
if (chunk->count == 1) {
/* remove from the trees */
tommy_tree_remove(&disk->fs_parity, chunk);
tommy_tree_remove(&disk->fs_file, chunk);
/* deallocate */
chunk_free(chunk);
return;
}
/* if it's at the start of the chunk, shrink the chunk */
if (parity_pos == chunk->parity_pos) {
++chunk->parity_pos;
++chunk->file_pos;
--chunk->count;
return;
}
/* if it's at the end of the chunk, shrink the chunk */
if (parity_pos == chunk->parity_pos + chunk->count - 1) {
--chunk->count;
return;
}
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency for clearing in the middle of a chunk in disk '%s'\n", disk->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos)
{
struct snapraid_file* file;
block_off_t file_pos;
file = fs_par2file_get(disk, parity_pos, &file_pos);
if (!file)
return BLOCK_EMPTY;
return fs_file2block_get(file, file_pos);
}
struct snapraid_map* map_alloc(const char* name, unsigned position, block_off_t total_blocks, block_off_t free_blocks, const char* uuid)
{
struct snapraid_map* map;

View file

@ -20,8 +20,9 @@
#include "util.h"
#include "support.h"
#include "tommyds/tommylist.h"
#include "tommyds/tommyhash.h"
#include "tommyds/tommylist.h"
#include "tommyds/tommytree.h"
#include "tommyds/tommyhashdyn.h"
#include "tommyds/tommyarray.h"
#include "tommyds/tommyarrayblk.h"
@ -152,19 +153,12 @@ struct snapraid_filter {
*/
#define BLOCK_STATE_DELETED 4
/**
* Mask used to store the previous states of the block.
* Used to mix them inside the file pointer.
*/
#define BLOCK_STATE_MASK 7
/**
* Block of a file.
*/
struct snapraid_block {
uintptr_t private_file_mixed; /**< Back pointer to the file owning this block, mixed with some flags. */
block_off_t private_parity_pos; /**< Position of the block in the parity. */
unsigned char hash[HASH_SIZE]; /**< Hash of the block. */
unsigned char state; /**< State of the block. */
};
/**
@ -287,6 +281,20 @@ struct snapraid_dir {
tommy_hashdyn_node nodeset;
};
/**
* Chunk.
*
* A chunk represents a fragment of a file mapped into the parity.
*/
struct snapraid_chunk {
struct snapraid_file* file; /* File containing this chunk. */
block_off_t parity_pos; /**< Parity position. */
block_off_t file_pos; /**< Position in the file. */
block_off_t count; /**< Number of sequential blocks in the file and parity. */
tommy_tree_node parity_node; /**< Tree sorted by <parity_pos>. */
tommy_tree_node file_node; /**< Tree sorter by <file,file_pos>. */
};
/**
* Disk.
*/
@ -315,12 +323,12 @@ struct snapraid_disk {
int had_empty_uuid; /**< If the disk had an empty UUID, meaning that it's a new disk. */
int mapping_idx; /**< Index in the mapping vector. Used only as buffer when writing the content file. */
/**<
* Block array of the disk.
*
* Each element points to a snapraid_block structure, or it's BLOCK_EMPTY.
/**
* Mapping of chunks in the parity.
* Sorted by <parity_pos> and by <file,file_pos>
*/
tommy_arrayblk blockarr;
tommy_tree fs_parity;
tommy_tree fs_file;
/**
* List of all the snapraid_file for the disk.
@ -531,21 +539,15 @@ static inline unsigned block_state_get(const struct snapraid_block* block)
if (block == BLOCK_EMPTY)
return BLOCK_STATE_EMPTY;
return block->private_file_mixed & BLOCK_STATE_MASK;
return block->state;
}
/**
* Set the state of the block.
*/
static inline void block_state_set(struct snapraid_block* block, unsigned state)
{
/* ensure that the state can be stored inside the file pointer */
if ((state & BLOCK_STATE_MASK) != state) {
/* LCOV_EXCL_START */
log_fatal("Internal error when setting the block state %u\n", state);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
block->private_file_mixed &= ~(uintptr_t)BLOCK_STATE_MASK;
block->private_file_mixed |= state & BLOCK_STATE_MASK;
block->state = state;
}
/**
@ -724,6 +726,26 @@ static inline tommy_uint32_t file_stamp_hash(data_off_t size, int64_t mtime_sec,
return tommy_inthash_u32((tommy_uint32_t)size ^ tommy_inthash_u32(mtime_sec ^ tommy_inthash_u32(mtime_nsec)));
}
/**
* Allocate a chunk.
*/
struct snapraid_chunk* chunk_alloc(block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos, block_off_t count);
/**
* Deallocate a chunk.
*/
void chunk_free(struct snapraid_chunk* chunk);
/**
* Compare chunk by parity position.
*/
int chunk_parity_compare(const void* void_a, const void* void_b);
/**
* Compare chunk by file and file position.
*/
int chunk_file_compare(const void* void_a, const void* void_b);
static inline int link_flag_has(const struct snapraid_link* link, unsigned mask)
{
return (link->flag & mask) == mask;
@ -829,12 +851,41 @@ void disk_free(struct snapraid_disk* disk);
/**
* Get the size of the disk in blocks.
*/
block_off_t disk_block_size(struct snapraid_disk* disk);
block_off_t disk_size(struct snapraid_disk* disk);
/**
* Set a filesystem mapping between file and parity positions.
* Check if a disk is totally empty and can be discarded from the content file.
* A disk is empty if it doesn't contain any file, symlink, hardlink or dir
* and without any DELETED block.
* The blockmax is used to limit the search of DELETED block up to blockmax.
*/
void fs_par2file_set(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos);
int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax);
/**
* Allocate a parity position for the specified file position.
*
* After this call you can use the par2file/par2block operations
* to query the relation.
*/
void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos);
/**
* Deallocate the parity position.
*
* After this call you can the par2file/par2block operations
* won't find anymore the parity association.
*/
void fs_deallocate(struct snapraid_disk* disk, block_off_t pos);
/**
* Get the block from the file position.
*/
static inline struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos)
{
assert(file_pos < file->blockmax);
return &file->blockvec[file_pos];
}
/**
* Get the file position from the parity position.
@ -847,36 +898,12 @@ struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t pa
*/
block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos);
/**
* Get the block from the file position.
*/
struct snapraid_block* fs_file2block_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos);
/**
* Clear a specific block.
*/
void fs_par2block_clear(struct snapraid_disk* disk, block_off_t pos);
/**
* Set the block from parity position.
* In case of holes, everything is initialized to 0.
*/
void fs_par2block_set(struct snapraid_disk* disk, block_off_t pos, struct snapraid_block* block);
/**
* Get the block from the parity position.
* Returns BLOCK_EMPTY==0 if the block is over the end of the disk or not used.
*/
struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos);
/**
* Check if a disk is totally empty and can be discarded from the content file.
* A disk is empty if it doesn't contain any file, symlink, hardlink or dir
* and without any DELETED block.
* The blockmax is used to limit the search of DELETED block up to blockmax.
*/
int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax);
/**
* Allocates a disk mapping.
* Uses uuid="" if not available.

View file

@ -37,7 +37,7 @@ block_off_t parity_allocated_size(struct snapraid_state* state)
struct snapraid_disk* disk = i->data;
/* start from the declared size */
block_off_t block = disk_block_size(disk);
block_off_t block = disk_size(disk);
/* decrease the block until an allocated one, but part of a file */
/* we don't stop at deleted blocks, because we want to have them cleared */
@ -64,7 +64,7 @@ block_off_t parity_used_size(struct snapraid_state* state)
struct snapraid_disk* disk = i->data;
/* start from the declared size */
block_off_t block = disk_block_size(disk);
block_off_t block = disk_size(disk);
/* decrease the block until an used one */
while (block > parity_block && !block_has_file_and_valid_parity(fs_par2block_get(disk, block - 1)))

View file

@ -170,19 +170,27 @@ static void scan_file_allocate(struct snapraid_scan* scan, struct snapraid_file*
/* allocate the blocks of the file */
parity_pos = disk->first_free_block;
for (i = 0; i < file->blockmax; ++i) {
struct snapraid_block* block;
struct snapraid_block* over_block;
snapraid_info info;
struct snapraid_block* block = fs_file2block_get(disk, file, i);
/* increment the position until the first really free block */
while (block_has_file(fs_par2block_get(disk, parity_pos)))
++parity_pos;
/* set the position */
block->private_parity_pos = parity_pos;
/* get block we are going to overwrite, if any */
over_block = fs_par2block_get(disk, parity_pos);
/* deallocate it */
if (over_block != BLOCK_EMPTY)
fs_deallocate(disk, parity_pos);
/* get block specific info */
info = info_get(&state->infoarr, parity_pos);
/* get the new block we are going to write */
block = fs_file2block_get(file, i);
/* if the file block already has an updated hash without rehash */
if (block_has_updated_hash(block) && !info_get_rehash(info)) {
/* the only possible case is for REP blocks */
@ -193,26 +201,22 @@ static void scan_file_allocate(struct snapraid_scan* scan, struct snapraid_file*
/* and keep the hash as it's */
} else {
struct snapraid_block* over_block;
unsigned block_state;
unsigned over_state;
/* convert to a CHG block */
block_state_set(block, BLOCK_STATE_CHG);
/* block to overwrite */
over_block = fs_par2block_get(disk, parity_pos);
/* state of the block we are going to overwrite */
block_state = block_state_get(over_block);
over_state = block_state_get(over_block);
/* if the block is an empty one */
if (block_state == BLOCK_STATE_EMPTY) {
if (over_state == BLOCK_STATE_EMPTY) {
/* the block was empty and filled with zeros */
/* set the hash to the special ZERO value */
hash_zero_set(block->hash);
} else {
/* otherwise it's a DELETED one */
assert(block_state == BLOCK_STATE_DELETED);
assert(over_state == BLOCK_STATE_DELETED);
/* copy the past hash of the block */
memcpy(block->hash, over_block->hash, HASH_SIZE);
@ -235,7 +239,7 @@ static void scan_file_allocate(struct snapraid_scan* scan, struct snapraid_file*
}
/* store in the disk map, after invalidating all the other blocks */
fs_par2block_set(disk, parity_pos, block);
fs_allocate(disk, parity_pos, file, i);
/* set the new free position */
disk->first_free_block = parity_pos + 1;
@ -263,7 +267,7 @@ static void scan_file_deallocate(struct snapraid_scan* scan, struct snapraid_fil
/* free all the blocks of the file */
for (i = 0; i < file->blockmax; ++i) {
struct snapraid_block* block = fs_file2block_get(disk, file, i);
struct snapraid_block* block = fs_file2block_get(file, i);
block_off_t parity_pos = fs_file2par_get(disk, file, i);
unsigned block_state;
@ -357,7 +361,7 @@ static int file_is_full_invalid_parity_and_stable(struct snapraid_state* state,
/* check all blocks */
for (i = 0; i < file->blockmax; ++i) {
snapraid_info info;
struct snapraid_block* block = fs_file2block_get(disk, file, i);
struct snapraid_block* block = fs_file2block_get(file, i);
block_off_t parity_pos = fs_file2par_get(disk, file, i);
/* exclude blocks with parity */
@ -390,7 +394,7 @@ static int file_is_full_hashed_and_stable(struct snapraid_state* state, struct s
/* check all blocks */
for (i = 0; i < file->blockmax; ++i) {
snapraid_info info;
struct snapraid_block* block = fs_file2block_get(disk, file, i);
struct snapraid_block* block = fs_file2block_get(file, i);
block_off_t parity_pos = fs_file2par_get(disk, file, i);
/* exclude blocks without hash */

View file

@ -78,14 +78,14 @@ void memory(void)
{
log_tag("memory:used:%" PRIu64 "\n", (uint64_t)malloc_counter());
/* size of the block + the size of the blockarr pointer to it */
log_tag("memory:block:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_block) + sizeof(void*)));
/* size of the file + the size of the 3 hashtables pointers to it, * 2 for the hashtable grow factor */
log_tag("memory:file:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_file) + 3*2*sizeof(void*)));
/* size of the block */
log_tag("memory:block:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_block)));
log_tag("memory:chunk:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_chunk)));
log_tag("memory:file:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_file)));
log_tag("memory:link:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_link)));
log_tag("memory:dir:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_dir)));
msg_progress("Using %u MiB of memory.\n", (unsigned)(malloc_counter() / (1024 * 1024)));
}
/****************************************************************************/

View file

@ -1440,7 +1440,7 @@ static void position_clear_deleted(struct snapraid_state* state, block_off_t pos
/* if the block is deleted */
if (block_state_get(block) == BLOCK_STATE_DELETED) {
/* set it to empty */
fs_par2block_clear(disk, pos);
fs_deallocate(disk, pos);
}
}
}
@ -1721,15 +1721,7 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
/* fill the blocks in the run */
while (v_count) {
struct snapraid_block* block = &file->blockvec[v_idx];
if (block->private_parity_pos != POS_INVALID) {
/* LCOV_EXCL_START */
decoding_error(path, f);
log_fatal("Internal inconsistency in block position!\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
struct snapraid_block* block = fs_file2block_get(file, v_idx);
switch (c) {
case 'b' :
@ -1753,8 +1745,6 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
/* LCOV_EXCL_STOP */
}
block->private_parity_pos = v_pos;
/* read the hash only for 'blk/chg/rep', and not for 'new' */
if (c != 'n') {
ret = sread(f, block->hash, HASH_SIZE);
@ -1811,8 +1801,8 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
/* LCOV_EXCL_STOP */
}
/* insert the block in the block array */
fs_par2block_set(disk, v_pos, block);
/* set the parity association */
fs_allocate(disk, v_pos, file, v_idx);
/* go to the next block */
++v_idx;
@ -1925,7 +1915,6 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
} else if (c == 'h') {
/* hole */
uint32_t v_pos;
uint32_t i;
struct snapraid_disk* disk;
uint32_t mapping;
@ -1941,6 +1930,7 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
v_pos = 0;
while (v_pos < blockmax) {
uint32_t v_idx;
uint32_t v_count;
struct snapraid_file* deleted;
@ -1967,23 +1957,20 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
case 'o' :
/* if it's a run of deleted blocks */
/* allocate the deleted file */
/* allocate a fake deleted file */
deleted = file_alloc(state->block_size, "", v_count * state->block_size, 0, 0, 0, 0);
/* insert it in the list of deleted files */
tommy_list_insert_tail(&disk->deletedlist, &deleted->nodelist, deleted);
/* process all blocks */
i = 0;
v_idx = 0;
while (v_count) {
struct snapraid_block* block = &deleted->blockvec[i];
struct snapraid_block* block = fs_file2block_get(deleted, v_idx);
/* set the block as deleted */
block_state_set(block, BLOCK_STATE_DELETED);
/* set the position */
block->private_parity_pos = v_pos;
/* read the hash */
ret = sread(f, block->hash, HASH_SIZE);
if (ret < 0) {
@ -2012,13 +1999,13 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST
/* LCOV_EXCL_STOP */
} else {
/* insert the block in the block array */
fs_par2block_set(disk, v_pos, block);
fs_allocate(disk, v_pos, deleted, v_idx);
}
/* go to next block */
++v_pos;
++v_idx;
--v_count;
++i;
}
break;
case 'O' :
@ -2463,7 +2450,7 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
unsigned count_symlink;
unsigned count_dir;
tommy_node* i;
block_off_t b;
block_off_t idx;
block_off_t blockmax;
block_off_t begin;
time_t info_oldest;
@ -2484,10 +2471,10 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
/* and get some other info */
info_oldest = 0; /* oldest time in info */
info_has_rehash = 0; /* if there is a rehash info */
for (b = 0; b < blockmax; ++b) {
for (idx = 0; idx < blockmax; ++idx) {
/* if the position is used */
if (position_is_required(state, b)) {
snapraid_info info = info_get(&state->infoarr, b);
if (position_is_required(state, idx)) {
snapraid_info info = info_get(&state->infoarr, idx);
/* only if there is some info to store */
if (info) {
@ -2501,10 +2488,10 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
}
} else {
/* clear any previous info */
info_set(&state->infoarr, b, 0);
info_set(&state->infoarr, idx, 0);
/* and clear any deleted blocks */
position_clear_deleted(state, b);
position_clear_deleted(state, idx);
}
}
@ -2633,7 +2620,6 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
/* for each file */
for (j = disk->filelist; j != 0; j = j->next) {
struct snapraid_file* file = j->data;
struct snapraid_block* blockvec = file->blockvec;
uint64_t size;
uint64_t mtime_sec;
int32_t mtime_nsec;
@ -2665,22 +2651,23 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
/* for all the blocks of the file */
begin = 0;
while (begin < file->blockmax) {
unsigned block_state = block_state_get(blockvec + begin);
uint32_t v_pos = blockvec[begin].private_parity_pos;
unsigned v_state = block_state_get(fs_file2block_get(file, begin));
block_off_t v_pos = fs_file2par_get(disk, file, begin);
uint32_t v_count;
block_off_t end;
/* find the end of run of blocks */
end = begin + 1;
while (end < file->blockmax
&& block_state == block_state_get(blockvec + end)
&& blockvec[end - 1].private_parity_pos + 1 == blockvec[end].private_parity_pos
) {
while (end < file->blockmax) {
if (v_state != block_state_get(fs_file2block_get(file, end)))
break;
if (v_pos + (end - begin) != fs_file2par_get(disk, file, end))
break;
++end;
}
switch (block_state) {
switch (v_state) {
case BLOCK_STATE_BLK :
sputc('b', f);
break;
@ -2692,7 +2679,7 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
break;
default :
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency in state for block %u state %u\n", v_pos, block_state);
log_fatal("Internal inconsistency in state for block %u state %u\n", v_pos, v_state);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
@ -2703,16 +2690,8 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
sputb32(v_count, f);
/* write hashes */
for (b = begin; b < end; ++b) {
struct snapraid_block* block = blockvec + b;
/* consistency check */
if (block != fs_par2block_get(disk, block->private_parity_pos)) {
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency in mismatch for block %u state %u in file '%s'\n", block->private_parity_pos, block_state, file->sub);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
for (idx = begin; idx < end; ++idx) {
struct snapraid_block* block = fs_file2block_get(file, idx);
swrite(block->hash, HASH_SIZE, f);
}
@ -2808,14 +2787,6 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f)
while (begin < end) {
struct snapraid_block* block = fs_par2block_get(disk, begin);
/* consistency check */
if (block->private_parity_pos != begin) {
/* LCOV_EXCL_START */
log_fatal("Internal inconsistency in delete position %u/%u!\n", block->private_parity_pos, begin);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
swrite(block->hash, HASH_SIZE, f);
++begin;

View file

@ -1031,7 +1031,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
/* if it's a deleted block */
if (block_state_get(block) == BLOCK_STATE_DELETED) {
/* the parity is now updated without this block, so it's now empty */
fs_par2block_clear(handle[j].disk, i);
fs_deallocate(handle[j].disk, i);
continue;
}

View file

@ -38,5 +38,6 @@
#include "tommyarrayblk.c"
#include "tommyarrayblkof.c"
#include "tommylist.c"
#include "tommytree.c"
#include "tommyhashdyn.c"

283
tommyds/tommytree.c Normal file
View file

@ -0,0 +1,283 @@
/*
* Copyright (c) 2015, Andrea Mazzoleni. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "tommytree.h"
#include <assert.h> /* for assert */
/******************************************************************************/
/* tree */
void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
{
tree->root = 0;
tree->count = 0;
tree->cmp = cmp;
}
static int tommy_tree_delta(tommy_tree_node* root)
{
int left_height = root->prev ? root->prev->key : 0;
int right_height = root->next ? root->next->key : 0;
return left_height - right_height;
}
/* AVL tree operations */
static tommy_tree_node* tommy_tree_balance(tommy_tree_node*);
static tommy_tree_node* tommy_tree_rotate_left(tommy_tree_node* root)
{
tommy_tree_node* next = root->next;
root->next = next->prev;
next->prev = tommy_tree_balance(root);
return tommy_tree_balance(next);
}
static tommy_tree_node* tommy_tree_rotate_right(tommy_tree_node* root)
{
tommy_tree_node* prev = root->prev;
root->prev = prev->next;
prev->next = tommy_tree_balance(root);
return tommy_tree_balance(prev);
}
static tommy_tree_node* tommy_tree_move_right(tommy_tree_node* root, tommy_tree_node* node)
{
if (!root)
return node;
root->next = tommy_tree_move_right(root->next, node);
return tommy_tree_balance(root);
}
static tommy_tree_node* tommy_tree_balance(tommy_tree_node* root)
{
int delta = tommy_tree_delta(root);
if (delta < -1) {
if (tommy_tree_delta(root->next) > 0)
root->next = tommy_tree_rotate_right(root->next);
return tommy_tree_rotate_left(root);
}
if (delta > 1) {
if (tommy_tree_delta(root->prev) < 0)
root->prev = tommy_tree_rotate_left(root->prev);
return tommy_tree_rotate_right(root);
}
/* recompute key */
root->key = 0;
if (root->prev && root->prev->key > root->key)
root->key = root->prev->key;
if (root->next && root->next->key > root->key)
root->key = root->next->key;
/* count itself */
root->key += 1;
return root;
}
static tommy_tree_node* tommy_tree_insert_node(tommy_compare_func* cmp, tommy_tree_node* root, tommy_tree_node** let)
{
int c;
if (!root)
return *let;
c = cmp((*let)->data, root->data);
if (c < 0) {
root->prev = tommy_tree_insert_node(cmp, root->prev, let);
return tommy_tree_balance(root);
}
if (c > 0) {
root->next = tommy_tree_insert_node(cmp, root->next, let);
return tommy_tree_balance(root);
}
/* already present, set the return pointer */
*let = root;
return root;
}
void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data)
{
node->data = data;
node->prev = 0;
node->next = 0;
node->key = 0;
tree->root = tommy_tree_insert_node(tree->cmp, tree->root, &node);
++tree->count;
return node->data;
}
static tommy_tree_node* tommy_tree_remove_node(tommy_compare_func* cmp, tommy_tree_node* root, void* data, tommy_tree_node** let)
{
int c;
if (!root)
return 0;
c = cmp(data, root->data);
if (c < 0) {
root->prev = tommy_tree_remove_node(cmp, root->prev, data, let);
return tommy_tree_balance(root);
}
if (c > 0) {
root->next = tommy_tree_remove_node(cmp, root->next, data, let);
return tommy_tree_balance(root);
}
/* found */
*let = root;
return tommy_tree_move_right(root->prev, root->next);
}
void* tommy_tree_remove(tommy_tree* tree, void* data)
{
tommy_tree_node* node = 0;
tree->root = tommy_tree_remove_node(tree->cmp, tree->root, data, &node);
if (!node)
return 0;
return node->data;
}
static tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* root, void* data)
{
int c;
if (!root)
return 0;
c = cmp(data, root->data);
if (c < 0)
return tommy_tree_search_node(cmp, root->prev, data);
if (c > 0)
return tommy_tree_search_node(cmp, root->next, data);
return root;
}
void* tommy_tree_search(tommy_tree* tree, void* data)
{
tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data);
if (!node)
return 0;
return node->data;
}
void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_node(cmp, tree->root, cmp_arg);
if (!node)
return 0;
return node->data;
}
void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node)
{
return tommy_tree_remove(tree, node->data);
}
static void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func)
{
tommy_tree_node* next;
if (!root)
return;
tommy_tree_foreach_node(root->prev, func);
/* make a copy in case func is free() */
next = root->next;
func(root->data);
tommy_tree_foreach_node(next, func);
}
void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func)
{
tommy_tree_foreach_node(tree->root, func);
}
static void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg)
{
tommy_tree_node* next;
if (!root)
return;
tommy_tree_foreach_arg_node(root->prev, func, arg);
/* make a copy in case func is free() */
next = root->next;
func(arg, root->data);
tommy_tree_foreach_arg_node(next, func, arg);
}
void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
{
tommy_tree_foreach_arg_node(tree->root, func, arg);
}
tommy_size_t tommy_tree_memory_usage(tommy_tree* tree)
{
return tommy_tree_count(tree) * sizeof(tommy_tree_node);
}

214
tommyds/tommytree.h Normal file
View file

@ -0,0 +1,214 @@
/*
* Copyright (c) 2015, Andrea Mazzoleni. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/** \file
* AVL tree.
*
* This tree is a standard AVL tree implementation that stores elements in the order defined
* by the comparsion function.
*
* \code
* tommy_tree tree;
*
* tommy_tree_init(&tree, cmp);
* \endcode
*
* To insert elements in the tree you have to call tommy_tree_insert() for
* each element.
* In the insertion call you have to specify the address of the node, the
* address of the object.
* The address of the object is used to initialize the tommy_node::data field
* of the node.
*
* \code
* struct object {
* int value;
* // other fields
* tommy_tree_node node;
* };
*
* struct object* obj = malloc(sizeof(struct object)); // creates the object
*
* obj->value = ...; // initializes the object
*
* tommy_tree_insert(&tree, &obj->node, obj); // inserts the object
* \endcode
*
* To find and element in the tree you have to call tommy_tree_search() providing
* the key to search.
*
* \code
* struct object value_to_find = { 1 };
* struct object* obj = tommy_tree_search(&tree, &value_to_find);
* if (!obj) {
* // not found
* } else {
* // found
* }
* \endcode
*
* To remove an element from the tree you have to call tommy_tree_remove()
* providing the key to search and remove.
*
 * \code
* struct object* obj = tommy_tree_remove(&tree, value_to_remove);
* if (obj) {
* free(obj); // frees the object allocated memory
* }
* \endcode
*
* To destroy the tree you have to remove all the elements.
*
* If you need to iterate over all the elements in the tree, you can use
* tommy_hashtable_foreach() or tommy_hashtable_foreach_arg().
* If you need a more precise control with a real iteration, you have to insert
* all the elements also in a ::tommy_list, and use the list to iterate.
* See the \ref multiindex example for more detail.
*/
#ifndef __TOMMYTREE_H
#define __TOMMYTREE_H
#include "tommytypes.h"
/******************************************************************************/
/* tree */
/**
* Tree node.
* This is the node that you have to include inside your objects.
*/
typedef tommy_node tommy_tree_node;
typedef struct tommy_tree_struct {
tommy_tree_node* root; /**< Root node. */
tommy_count_t count; /**< Number of elements. */
tommy_compare_func* cmp; /**< Comparison function. */
} tommy_tree;
/**
* Initializes the tree.
*/
void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp);
/**
* Inserts an element in the tree.
* If the element is already present, it's not inserted again.
* You have to provide the pointer of the node embedded into the object,
* the pointer at the object.
* \param node Pointer at the node embedded into the object to insert.
* \param data Pointer at the object to insert.
* \return The element in the tree. Either the already existing one, or the one just inserted.
*/
void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data);
/**
* Searches and removes an element.
* If the element is not found, 0 is returned.
* \param data Element used for comparison.
* \return The removed element, or 0 if not found.
*/
void* tommy_tree_remove(tommy_tree* tree, void* data);
/**
* Searches an element in the tree.
* If the element is not found, 0 is returned.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
*/
void* tommy_tree_search(tommy_tree* tree, void* data);
/**
* Searches an element in the tree with a specific compare function.
*
* Like tommy_tree_search() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
*
* The ::data argument is going to always be the first argument of the comparison function,
* and it can be something different than the objects in the tree.
*/
void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg);
/**
* Removes an element from the tree.
* You must already have the address of the element to remove.
* \return The tommy_node::data field of the node removed.
*/
void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node);
/**
* Calls the specified function for each element in the tree.
*
* The elements are processed in order.
*
* You can use this function to deallocate all the elements inserted.
*
* \code
* tommy_tree tree;
*
* // initializes the tree
* tommy_tree_init(&tree, cmp);
*
* ...
*
* // creates an object
* struct object* obj = malloc(sizeof(struct object));
*
* ...
*
* // insert it in the hashtable
* tommy_tree_insert(&tree, &obj->node, obj);
*
* ...
*
* // deallocates all the objects iterating the tree
* tommy_tree_foreach(&tree, free);
* \endcode
*/
void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func);
/**
* Calls the specified function with an argument for each element in the tree.
*/
void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg);
/**
* Gets the number of elements.
*/
tommy_inline tommy_count_t tommy_tree_count(tommy_tree* tree)
{
return tree->count;
}
/**
* Gets the size of allocated memory.
* It includes the size of the ::tommy_tree_node of the stored elements.
*/
tommy_size_t tommy_tree_memory_usage(tommy_tree* tree);
#endif