From e6bd1586d64723e360056264f052c4505a6f998c Mon Sep 17 00:00:00 2001 From: Vreixo Formoso Date: Thu, 20 Dec 2007 00:25:25 +0100 Subject: [PATCH] Replace glibc tsearch() with a custom red-black tree implementation. The library supplied tree estructure is not enought for our needs, due to its limited API. Thus, we have implemented a suitable red-black tree. --- Makefile.am | 1 + demo/ecma119_tree.c | 3 + src/ecma119.c | 10 +- src/ecma119.h | 4 +- src/filesrc.c | 35 ++---- src/filesrc.h | 4 +- src/util.h | 58 +++++++++ src/util_rbtree.c | 284 ++++++++++++++++++++++++++++++++++++++++++++ test/test_util.c | 54 +++++++++ 9 files changed, 422 insertions(+), 31 deletions(-) create mode 100644 src/util_rbtree.c diff --git a/Makefile.am b/Makefile.am index 3345770..402b6f1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,6 +29,7 @@ src_libisofs_la_SOURCES = \ src/stream.c \ src/util.h \ src/util.c \ + src/util_rbtree.c \ src/filesrc.h \ src/filesrc.c \ src/ecma119.h \ diff --git a/demo/ecma119_tree.c b/demo/ecma119_tree.c index dc500cd..a8491b7 100644 --- a/demo/ecma119_tree.c +++ b/demo/ecma119_tree.c @@ -8,6 +8,8 @@ #include "libisofs.h" #include "ecma119.h" #include "ecma119_tree.h" +#include "util.h" +#include "filesrc.h" #include #include #include @@ -91,6 +93,7 @@ int main(int argc, char **argv) } ecma119 = calloc(1, sizeof(Ecma119Image)); + iso_rbtree_new(iso_file_src_cmp, &(ecma119->files)); ecma119->iso_level = 1; ecma119->image = image; diff --git a/src/ecma119.c b/src/ecma119.c index 542e0dd..1bb25a4 100644 --- a/src/ecma119.c +++ b/src/ecma119.c @@ -29,7 +29,7 @@ void ecma119_image_free(Ecma119Image *t) ecma119_node_free(t->root); iso_image_unref(t->image); - iso_file_src_free(t); + iso_rbtree_destroy(t->files, iso_file_src_free); for (i = 0; i < t->nwriters; ++i) { IsoImageWriter *writer = t->writers[i]; @@ -293,6 +293,13 @@ int ecma119_image_new(IsoImage *src, Ecma119WriteOpts *opts, return ISO_MEM_ERROR; } + /* create the tree for file caching */ + ret = iso_rbtree_new(iso_file_src_cmp, &(target->files)); + if (ret < 0) { + free(target); + return ret; + } + target->image = src; iso_image_ref(src); @@ -303,6 +310,7 @@ int ecma119_image_new(IsoImage *src, Ecma119WriteOpts *opts, target->ms_block = 0; target->input_charset = strdup("UTF-8"); //TODO + /* * 2. Based on those options, create needed writers: iso, joliet... * Each writer inits its structures and stores needed info into diff --git a/src/ecma119.h b/src/ecma119.h index 5b76a69..b3bb8a7 100644 --- a/src/ecma119.h +++ b/src/ecma119.h @@ -10,6 +10,7 @@ #define LIBISO_ECMA119_H_ #include "libisofs.h" +#include "util.h" #include @@ -72,8 +73,7 @@ struct ecma119_image { IsoImageWriter **writers; /* tree of files sources */ - void *file_srcs; - int file_count; + IsoRBTree *files; /* file descriptors for read and writing image */ int wrfd; /* write to here */ diff --git a/src/filesrc.c b/src/filesrc.c index 4fc5d97..d90b597 100644 --- a/src/filesrc.c +++ b/src/filesrc.c @@ -9,15 +9,11 @@ #include "filesrc.h" #include "error.h" #include "node.h" +#include "util.h" #include -/* tdestroy is a GNU specific function */ -#define __USE_GNU -#include - -static -int comp_iso_file_src(const void *n1, const void *n2) +int iso_file_src_cmp(const void *n1, const void *n2) { const IsoFileSrc *f1, *f2; int res; @@ -25,7 +21,6 @@ int comp_iso_file_src(const void *n1, const void *n2) dev_t dev_id1, dev_id2; ino_t ino_id1, ino_id2; - f1 = (const IsoFileSrc *)n1; f2 = (const IsoFileSrc *)n2; @@ -76,7 +71,7 @@ int iso_file_src_create(Ecma119Image *img, IsoFile *file, IsoFileSrc **src) // care of it return ISO_ERROR; } else { - IsoFileSrc **inserted; + int ret; fsrc = malloc(sizeof(IsoFileSrc)); if (fsrc == NULL) { @@ -90,32 +85,18 @@ int iso_file_src_create(Ecma119Image *img, IsoFile *file, IsoFileSrc **src) fsrc->stream = file->stream; /* insert the filesrc in the tree */ - inserted = tsearch(fsrc, &(img->file_srcs), comp_iso_file_src); - if (inserted == NULL) { - free(fsrc); - return ISO_MEM_ERROR; - } else if (*inserted == fsrc) { - /* the file was inserted */ - img->file_count++; - } else { - /* the file was already on the tree */ + ret = iso_rbtree_insert(img->files, fsrc, (void**)src); + if (ret <= 0) { free(fsrc); + return ret; } - *src = *inserted; } return ISO_SUCCESS; } -void free_node(void *nodep) +void iso_file_src_free(void *node) { - /* nothing to do */ -} - -void iso_file_src_free(Ecma119Image *img) -{ - if (img->file_srcs != NULL) { - tdestroy(img->file_srcs, free_node); - } + free(node); } off_t iso_file_src_get_size(IsoFileSrc *file) diff --git a/src/filesrc.h b/src/filesrc.h index 61bce65..fa95318 100644 --- a/src/filesrc.h +++ b/src/filesrc.h @@ -21,6 +21,8 @@ struct Iso_File_Src { IsoStream *stream; }; +int iso_file_src_cmp(const void *n1, const void *n2); + /** * Create a new IsoFileSrc to get data from a specific IsoFile. * @@ -43,7 +45,7 @@ int iso_file_src_create(Ecma119Image *img, IsoFile *file, IsoFileSrc **src); /** * Free the IsoFileSrc especific data */ -void iso_file_src_free(Ecma119Image *img); +void iso_file_src_free(void *node); /** * Get the size of the file this IsoFileSrc represents diff --git a/src/util.h b/src/util.h index 453bb46..3cb4f16 100644 --- a/src/util.h +++ b/src/util.h @@ -89,4 +89,62 @@ void iso_datetime_7(uint8_t *buf, time_t t); /** Records the date/time into a 17 byte buffer (ECMA-119, 8.4.26.1) */ void iso_datetime_17(uint8_t *buf, time_t t); +typedef struct iso_rbtree IsoRBTree; + +/** + * Create a new binary tree. libisofs binary trees allow you to add any data + * passing it as a pointer. You must provide a function suitable for compare + * two elements. + * + * @param compare + * A function to compare two elements. It takes a pointer to both elements + * and return 0, -1 or 1 if the first element is equal, less or greater + * than the second one. + * @param tree + * Location where the tree structure will be stored. + */ +int iso_rbtree_new(int (*compare)(const void*, const void*), IsoRBTree **tree); + +/** + * Destroy a given tree. + * + * Note that only the structure itself is deleted. To delete the elements, you + * should provide a valid free_data function. It will be called for each + * element of the tree, so you can use it to free any related data. + */ +void iso_rbtree_destroy(IsoRBTree *tree, void (*free_data)(void *)); + +/** + * Inserts a given element in a Red-Black tree. + * + * @param tree + * the tree where to insert + * @param data + * element to be inserted on the tree. It can't be NULL + * @param item + * if not NULL, it will point to a location where the tree element ptr + * will be stored. If data was inserted, *item == data. If data was + * already on the tree, *item points to the previously inserted object + * that is equal to data. + * @return + * 1 success, 0 element already inserted, < 0 error + */ +int iso_rbtree_insert(IsoRBTree *tree, void *data, void **item); + +/** + * Get the number of elements in a given tree. + */ +size_t iso_rbtree_get_size(IsoRBTree *tree); + +/** + * Get an array view of the elements of the tree. + * + * @return + * A sorted array with the contents of the tree, or NULL if there is not + * enought memory to allocate the array. You should free(3) the array when + * no more needed. Note that the array is NULL-terminated, and thus it + * has size + 1 length. + */ +void **iso_rbtree_to_array(IsoRBTree *tree); + #endif /*LIBISO_UTIL_H_*/ diff --git a/src/util_rbtree.c b/src/util_rbtree.c new file mode 100644 index 0000000..72e4be4 --- /dev/null +++ b/src/util_rbtree.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "util.h" +#include "error.h" + +#include + +/* + * This implementation of Red-Black tree is based on the public domain + * implementation of Julienne Walker. + */ + +struct iso_rbnode { + void *data; + struct iso_rbnode *ch[2]; + unsigned int red:1; +}; + +struct iso_rbtree { + struct iso_rbnode *root; + size_t size; + int (*compare)(const void *a, const void *b); +}; + +/** + * Create a new binary tree. libisofs binary trees allow you to add any data + * passing it as a pointer. You must provide a function suitable for compare + * two elements. + * + * @param compare + * A function to compare two elements. It takes a pointer to both elements + * and return 0, -1 or 1 if the first element is equal, less or greater + * than the second one. + * @param tree + * Location where the tree structure will be stored. + */ +int +iso_rbtree_new(int (*compare)(const void*, const void*), IsoRBTree **tree) +{ + if (compare == NULL || tree == NULL) { + return ISO_NULL_POINTER; + } + *tree = calloc(1, sizeof(IsoRBTree)); + if (*tree == NULL) { + return ISO_MEM_ERROR; + } + (*tree)->compare = compare; + return ISO_SUCCESS; +} + +static +void rbtree_destroy_aux(struct iso_rbnode *root, void (*free_data)(void *)) +{ + if (root == NULL) { + return; + } + if (free_data != NULL) { + free_data(root->data); + } + rbtree_destroy_aux(root->ch[0], free_data); + rbtree_destroy_aux(root->ch[1], free_data); + free(root); +} + +/** + * Destroy a given tree. + * + * Note that only the structure itself is deleted. To delete the elements, you + * should provide a valid free_data function. It will be called for each + * element of the tree, so you can use it to free any related data. + */ +void +iso_rbtree_destroy(IsoRBTree *tree, void (*free_data)(void *)) +{ + if (tree == NULL) { + return; + } + rbtree_destroy_aux(tree->root, free_data); + free(tree); +} + +static inline +int is_red(struct iso_rbnode *root) +{ + return root != NULL && root->red; +} + +static +struct iso_rbnode *iso_rbtree_single(struct iso_rbnode *root, int dir) +{ + struct iso_rbnode *save = root->ch[!dir]; + + root->ch[!dir] = save->ch[dir]; + save->ch[dir] = root; + + root->red = 1; + save->red = 0; + return save; +} + +static +struct iso_rbnode *iso_rbtree_double(struct iso_rbnode *root, int dir) +{ + root->ch[!dir] = iso_rbtree_single(root->ch[!dir], !dir); + return iso_rbtree_single(root, dir); +} + +static +struct iso_rbnode *iso_rbnode_new(void *data) +{ + struct iso_rbnode *rn = malloc(sizeof(struct iso_rbnode)); + + if ( rn != NULL ) { + rn->data = data; + rn->red = 1; + rn->ch[0] = NULL; + rn->ch[1] = NULL; + } + + return rn; +} + +/** + * Inserts a given element in a Red-Black tree. + * + * @param tree + * the tree where to insert + * @param data + * element to be inserted on the tree. It can't be NULL + * @param item + * if not NULL, it will point to a location where the tree element ptr + * will be stored. If data was inserted, *item == data. If data was + * already on the tree, *item points to the previously inserted object + * that is equal to data. + * @return + * 1 success, 0 element already inserted, < 0 error + */ +int iso_rbtree_insert(IsoRBTree *tree, void *data, void **item) +{ + struct iso_rbnode *new; + int added = 0; /* has a new node been added? */ + + if (tree == NULL || data == NULL) { + return ISO_NULL_POINTER; + } + + if (tree->root == NULL) { + /* Empty tree case */ + tree->root = iso_rbnode_new(data); + if (tree->root == NULL) { + return ISO_MEM_ERROR; + } + new = data; + added = 1; + } else { + struct iso_rbnode head = {0}; /* False tree root */ + + struct iso_rbnode *g, *t; /* Grandparent & parent */ + struct iso_rbnode *p, *q; /* Iterator & parent */ + int dir = 0, last = 0; + int comp; + + /* Set up helpers */ + t = &head; + g = p = NULL; + q = t->ch[1] = tree->root; + + /* Search down the tree */ + while (1) { + if (q == NULL) { + /* Insert new node at the bottom */ + p->ch[dir] = q = iso_rbnode_new(data); + if (q == NULL) { + return ISO_MEM_ERROR; + } + added = 1; + } else if (is_red(q->ch[0]) && is_red(q->ch[1])) { + /* Color flip */ + q->red = 1; + q->ch[0]->red = 0; + q->ch[1]->red = 0; + } + + /* Fix red violation */ + if (is_red(q) && is_red(p)) { + int dir2 = (t->ch[1] == g); + + if (q == p->ch[last]) { + t->ch[dir2] = iso_rbtree_single(g, !last); + } else { + t->ch[dir2] = iso_rbtree_double(g, !last); + } + } + + comp = tree->compare(q->data, data); + + /* Stop if found */ + if (comp == 0) { + new = q->data; + break; + } + + last = dir; + dir = (comp < 0); + + /* Update helpers */ + if (g != NULL) + t = g; + g = p, p = q; + q = q->ch[dir]; + } + + /* Update root */ + tree->root = head.ch[1]; + } + + /* Make root black */ + tree->root->red = 0; + + if (item != NULL) { + *item = new; + } + if (added) { + /* a new element has been added */ + tree->size++; + return 1; + } else { + return 0; + } +} + +/** + * Get the number of elements in a given tree. + */ +size_t iso_rbtree_get_size(IsoRBTree *tree) +{ + return tree->size; +} + + +static +int rbtree_to_array_aux(struct iso_rbnode *root, void **array, size_t pos) +{ + if (root == NULL) { + return pos; + } + pos = rbtree_to_array_aux(root->ch[0], array, pos); + array[pos++] = root->data; + pos = rbtree_to_array_aux(root->ch[1], array, pos); + return pos; +} + +/** + * Get an array view of the elements of the tree. + * + * @return + * A sorted array with the contents of the tree, or NULL if there is not + * enought memory to allocate the array. You should free(3) the array when + * no more needed. Note that the array is NULL-terminated, and thus it + * has size + 1 length. + */ +void ** +iso_rbtree_to_array(IsoRBTree *tree) +{ + void **array; + + array = malloc((tree->size + 1) * sizeof(void*)); + if (array == NULL) { + return NULL; + } + + /* fill array */ + rbtree_to_array_aux(tree->root, array, 0); + array[tree->size] = NULL; + + return array; +} + diff --git a/test/test_util.c b/test/test_util.c index a22f55b..1e68c10 100644 --- a/test/test_util.c +++ b/test/test_util.c @@ -6,6 +6,8 @@ #include "test.h" #include "util.h" +#include + static void test_div_up() { CU_ASSERT_EQUAL( div_up(1, 2), 1 ); @@ -27,10 +29,62 @@ static void test_round_up() CU_ASSERT_EQUAL( round_up(14, 7), 14 ); } +static void test_iso_rbtree_insert() +{ + int res; + IsoRBTree *tree; + char *str1, *str2, *str3, *str4, *str5; + void *str; + + res = iso_rbtree_new(strcmp, &tree); + CU_ASSERT_EQUAL(res, 1); + + /* ok, insert one str */ + str1 = "first str"; + res = iso_rbtree_insert(tree, str1, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str1); + + str2 = "second str"; + res = iso_rbtree_insert(tree, str2, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str2); + + /* an already inserted string */ + str3 = "second str"; + res = iso_rbtree_insert(tree, str3, &str); + CU_ASSERT_EQUAL(res, 0); + CU_ASSERT_PTR_EQUAL(str, str2); + + /* an already inserted string */ + str3 = "first str"; + res = iso_rbtree_insert(tree, str3, &str); + CU_ASSERT_EQUAL(res, 0); + CU_ASSERT_PTR_EQUAL(str, str1); + + str4 = "a string to be inserted first"; + res = iso_rbtree_insert(tree, str4, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str4); + + str5 = "this to be inserted last"; + res = iso_rbtree_insert(tree, str5, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str5); + + /* + * TODO write a really good test to check all possible estrange + * behaviors of a red-black tree + */ + + iso_rbtree_destroy(tree, NULL); +} + void add_util_suite() { CU_pSuite pSuite = CU_add_suite("UtilSuite", NULL, NULL); CU_add_test(pSuite, "div_up()", test_div_up); CU_add_test(pSuite, "round_up()", test_round_up); + CU_add_test(pSuite, "iso_rbtree_insert()", test_iso_rbtree_insert); }