diff --git a/Makefile.am b/Makefile.am index 61f94523..e1bdf58e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/cmdline/dup.c b/cmdline/dup.c index cb148f64..c5d209c3 100644 --- a/cmdline/dup.c +++ b/cmdline/dup.c @@ -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); diff --git a/cmdline/elem.c b/cmdline/elem.c index 8381b8d7..a56924b4 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -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; diff --git a/cmdline/elem.h b/cmdline/elem.h index e0bcfe3c..d8429eb2 100644 --- a/cmdline/elem.h +++ b/cmdline/elem.h @@ -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 . */ + tommy_tree_node file_node; /**< Tree sorter by . */ +}; + /** * 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 and by */ - 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. diff --git a/cmdline/parity.c b/cmdline/parity.c index b7617669..cab58ccc 100644 --- a/cmdline/parity.c +++ b/cmdline/parity.c @@ -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))) diff --git a/cmdline/scan.c b/cmdline/scan.c index 0ab47b0f..ee26c2e7 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -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 */ diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index a80e659f..3f083899 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -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))); - } /****************************************************************************/ diff --git a/cmdline/state.c b/cmdline/state.c index 2705506b..52f9a630 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -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; diff --git a/cmdline/sync.c b/cmdline/sync.c index 71c483b4..db444d3d 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -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; } diff --git a/tommyds/tommy.c b/tommyds/tommy.c index 68316587..d7dc520f 100644 --- a/tommyds/tommy.c +++ b/tommyds/tommy.c @@ -38,5 +38,6 @@ #include "tommyarrayblk.c" #include "tommyarrayblkof.c" #include "tommylist.c" +#include "tommytree.c" #include "tommyhashdyn.c" diff --git a/tommyds/tommytree.c b/tommyds/tommytree.c new file mode 100644 index 00000000..7ea31c21 --- /dev/null +++ b/tommyds/tommytree.c @@ -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 /* 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); +} + diff --git a/tommyds/tommytree.h b/tommyds/tommytree.h new file mode 100644 index 00000000..a4db28b7 --- /dev/null +++ b/tommyds/tommytree.h @@ -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 +