From 2a32bf67f0a7c77b6adf6e7c23ec8abe7937a9ea Mon Sep 17 00:00:00 2001 From: Derrick Brashear Date: Fri, 8 Apr 2011 15:06:01 +0100 Subject: [PATCH] Add tsearch and friends, and a test program tsearch is missing from windows. use the netbsd version as it's license-compatible. --- cf/roken-frag.m4 | 6 ++ lib/roken/Makefile.am | 20 +++- lib/roken/NTMakefile | 2 + lib/roken/roken.h.in | 12 +++ lib/roken/search.hin | 42 ++++++++ lib/roken/tsearch-test.c | 125 ++++++++++++++++++++++++ lib/roken/tsearch.3 | 138 +++++++++++++++++++++++++++ lib/roken/tsearch.c | 180 +++++++++++++++++++++++++++++++++++ lib/roken/version-script.map | 4 + 9 files changed, 525 insertions(+), 4 deletions(-) create mode 100644 lib/roken/search.hin create mode 100644 lib/roken/tsearch-test.c create mode 100644 lib/roken/tsearch.3 create mode 100644 lib/roken/tsearch.c diff --git a/cf/roken-frag.m4 b/cf/roken-frag.m4 index c105ae1a0..4264956f4 100644 --- a/cf/roken-frag.m4 +++ b/cf/roken-frag.m4 @@ -65,6 +65,7 @@ AC_CHECK_HEADERS([\ poll.h \ pwd.h \ rpcsvc/ypclnt.h \ + search.h \ shadow.h \ stdint.h \ sys/bswap.h \ @@ -149,6 +150,7 @@ AC_REQUIRE([CHECK_NETINET_IP_AND_TCP]) AM_CONDITIONAL(have_err_h, test "$ac_cv_header_err_h" = yes) AM_CONDITIONAL(have_ifaddrs_h, test "$ac_cv_header_ifaddrs_h" = yes) +AM_CONDITIONAL(have_search_h, test "$ac_cv_header_search_h" = yes) AM_CONDITIONAL(have_vis_h, test "$ac_cv_header_vis_h" = yes) dnl Check for functions and libraries @@ -197,6 +199,10 @@ AC_CHECK_FUNCS([ \ svis \ sysconf \ sysctl \ + tdelete \ + tfind \ + tsearch \ + twalk \ uname \ unvis \ vasnprintf \ diff --git a/lib/roken/Makefile.am b/lib/roken/Makefile.am index 6c203e692..762f3c835 100644 --- a/lib/roken/Makefile.am +++ b/lib/roken/Makefile.am @@ -32,7 +32,8 @@ check_PROGRAMS = \ parse_reply-test \ parse_time-test \ snprintf-test \ - strpftime-test + strpftime-test \ + tsearch-test TESTS = $(check_PROGRAMS) @@ -58,6 +59,9 @@ strpftime_test_CFLAGS = -DTEST_STRPFTIME snprintf_test_SOURCES = snprintf-test.c snprintf_test_LDADD = libtest.la $(LDADD) snprintf_test_CFLAGS = -DTEST_SNPRINTF +tsearch_test_SOURCES = tsearch-test.c +tsearch_test_LDADD = libtest.la $(LDADD) +tsearch_test_CFLAGS = -DTEST_TSEARCH resolve_test_SOURCES = resolve-test.c @@ -107,6 +111,7 @@ libroken_la_SOURCES = \ strerror_r.c \ strpool.c \ timeval.c \ + tsearch.c \ tm2time.c \ unvis.c \ verify.c \ @@ -121,6 +126,7 @@ EXTRA_libroken_la_SOURCES = \ glob.hin \ fnmatch.hin \ ifaddrs.hin \ + search.hin \ vis.hin libroken_la_LIBADD = @LTLIBOBJS@ $(LIB_crypt) @@ -153,6 +159,12 @@ else ifaddrs_h = ifaddrs.h endif +if have_search_h +search_h = +else +search_h = search.h +endif + if have_vis_h vis_h = else @@ -160,8 +172,8 @@ vis_h = vis.h endif ## these are controlled by configure -XHEADERS = $(err_h) $(fnmatch_h) $(glob_h) $(ifaddrs_h) $(vis_h) -CLEANFILES += err.h fnmatch.h glob.h ifaddrs.h vis.h +XHEADERS = $(err_h) $(fnmatch_h) $(glob_h) $(ifaddrs_h) $(search_h) $(vis_h) +CLEANFILES += err.h fnmatch.h glob.h ifaddrs.h search.h vis.h dist_include_HEADERS = \ base64.h \ @@ -186,7 +198,7 @@ nodist_include_HEADERS = roken.h rokenincludedir = $(includedir)/roken nodist_rokeninclude_HEADERS = $(XHEADERS) -man_MANS = getarg.3 parse_time.3 rtbl.3 ecalloc.3 +man_MANS = getarg.3 parse_time.3 rtbl.3 ecalloc.3 tsearch.3 SUFFIXES += .hin .hin.h: diff --git a/lib/roken/NTMakefile b/lib/roken/NTMakefile index 00031c5cb..fab0a0cc0 100644 --- a/lib/roken/NTMakefile +++ b/lib/roken/NTMakefile @@ -103,6 +103,7 @@ libroken_la_OBJS = \ $(OBJ)\timegm.obj \ $(OBJ)\timeval.obj \ $(OBJ)\tm2time.obj \ + $(OBJ)\tsearch.obj \ $(OBJ)\unvis.obj \ $(OBJ)\verr.obj \ $(OBJ)\verrx.obj \ @@ -155,6 +156,7 @@ INCFILES = \ $(INCDIR)\roken.h \ $(INCDIR)\roken-common.h \ $(INCDIR)\rtbl.h \ + $(INCDIR)\search.h \ $(INCDIR)\stdbool.h \ $(INCDIR)\syslog.h \ $(INCDIR)\vis.h \ diff --git a/lib/roken/roken.h.in b/lib/roken/roken.h.in index 8be137137..7324acf64 100644 --- a/lib/roken/roken.h.in +++ b/lib/roken/roken.h.in @@ -1099,6 +1099,18 @@ rk_qsort(void *, size_t, size_t, int (*)(const void *, const void *)); #define rk_random() rand() #endif +#ifndef HAVE_TDELETE +#define tdelete(a,b,c) rk_tdelete(a,b,c) +#endif +#ifndef HAVE_TFIND +#define tfind(a,b,c) rk_tfind(a,b,c) +#endif +#ifndef HAVE_TSEARCH +#define tsearch(a,b,c) rk_tsearch(a,b,c) +#endif +#ifndef HAVE_TWALK +#define twalk(a,b) rk_twalk(a,b) +#endif #if defined(__linux__) && defined(SOCK_CLOEXEC) && !defined(SOCKET_WRAPPER_REPLACE) && !defined(__SOCKET_WRAPPER_H__) #undef socket diff --git a/lib/roken/search.hin b/lib/roken/search.hin new file mode 100644 index 000000000..9e45c2a9e --- /dev/null +++ b/lib/roken/search.hin @@ -0,0 +1,42 @@ +/*- + * Written by J.T. Conklin + * Public domain. + * + * $NetBSD: search.h,v 1.12 1999/02/22 10:34:28 christos Exp $ + */ + +#ifndef _rk_SEARCH_H_ +#define _rk_SEARCH_H_ 1 + +#ifndef ROKEN_LIB_FUNCTION +#ifdef _WIN32 +#define ROKEN_LIB_FUNCTION +#define ROKEN_LIB_CALL __cdecl +#else +#define ROKEN_LIB_FUNCTION +#define ROKEN_LIB_CALL +#endif +#endif + +#include +#include + +typedef enum { + preorder, + postorder, + endorder, + leaf +} VISIT; + +ROKEN_CPP_START + +ROKEN_LIB_FUNCTION void * ROKEN_LIB_CALL rk_tdelete(const void * __restrict, void ** __restrict, + int (*)(const void *, const void *)); +ROKEN_LIB_FUNCTION void * ROKEN_LIB_CALL rk_tfind(const void *, void * const *, + int (*)(const void *, const void *)); +ROKEN_LIB_FUNCTION void * ROKEN_LIB_CALL rk_tsearch(const void *, void **, int (*)(const void *, const void *)); +ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL rk_twalk(const void *, void (*)(const void *, VISIT, int)); + +ROKEN_CPP_END + +#endif /* !_rk_SEARCH_H_ */ diff --git a/lib/roken/tsearch-test.c b/lib/roken/tsearch-test.c new file mode 100644 index 000000000..a073821b8 --- /dev/null +++ b/lib/roken/tsearch-test.c @@ -0,0 +1,125 @@ +/* + * Tree search generalized from Knuth (6.2.2) Algorithm T just like + * the AT&T man page says. + * + * The node_t structure is for internal use only, lint doesn't grok it. + * + * Written by reading the System V Interface Definition, not the code. + * + * Totally public domain. + */ + +#include + +#include "roken.h" +#include "search.h" + +struct node { + char *string; + int order; +}; + +extern void *rk_tdelete(const void * __restrict, void ** __restrict, + int (*)(const void *, const void *)); +extern void *rk_tfind(const void *, void * const *, + int (*)(const void *, const void *)); +extern void *rk_tsearch(const void *, void **, int (*)(const void *, const void *)); +extern void rk_twalk(const void *, void (*)(const void *, VISIT, int)); + +void *rootnode = NULL; +int numerr = 0; + +/* + * This routine compares two nodes, based on an + * alphabetical ordering of the string field. + */ +int +node_compare(const void *node1, const void *node2) +{ + return strcmp(((const struct node *) node1)->string, + ((const struct node *) node2)->string); +} + +static int walkorder = -1; + +void +list_node(const void *ptr, VISIT order, int level) +{ + const struct node *p = *(const struct node **) ptr; + + if (order == postorder || order == leaf) { + walkorder++; + if (p->order != walkorder) { + warnx("sort failed: expected %d next, got %d\n", walkorder, + p->order); + numerr++; + } + } +} + +int +main(int argc, char **argv) +{ + int numtest = 1; + struct node *t, *p, tests[] = { + { "", 0 }, + { "ab", 3 }, + { "abc", 4 }, + { "abcdefg", 8 }, + { "abcd", 5 }, + { "a", 2 }, + { "abcdef", 7 }, + { "abcde", 6 }, + { "=", 1 }, + { NULL } + }; + + for(t = tests; t->string; t++) { + /* Better not be there */ + p = (struct node *)rk_tfind((void *)t, (void **)&rootnode, + node_compare); + + if (p) { + warnx("erroneous list: found %d\n", p->order); + numerr++; + } + + /* Put node into the tree. */ + p = (struct node *) rk_tsearch((void *)t, (void **)&rootnode, + node_compare); + + if (!p) { + warnx("erroneous list: missing %d\n", t->order); + numerr++; + } + } + + rk_twalk(rootnode, list_node); + + for(t = tests; t->string; t++) { + /* Better be there */ + p = (struct node *) rk_tfind((void *)t, (void **)&rootnode, + node_compare); + + if (!p) { + warnx("erroneous list: missing %d\n", t->order); + numerr++; + } + + /* pull out node */ + (void) rk_tdelete((void *)t, (void **)&rootnode, + node_compare); + + /* Better not be there */ + p = (struct node *) rk_tfind((void *)t, (void **)&rootnode, + node_compare); + + if (p) { + warnx("erroneous list: found %d\n", p->order); + numerr++; + } + + } + + return numerr; +} diff --git a/lib/roken/tsearch.3 b/lib/roken/tsearch.3 new file mode 100644 index 000000000..082acf042 --- /dev/null +++ b/lib/roken/tsearch.3 @@ -0,0 +1,138 @@ +.\" $NetBSD$ +.\" Copyright (c) 1997 Todd C. Miller +.\" 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. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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. +.\" +.\" OpenBSD: tsearch.3,v 1.2 1998/06/21 22:13:49 millert Exp +.\" $FreeBSD: src/lib/libc/stdlib/tsearch.3,v 1.15 2006/06/23 13:36:33 keramida Exp $ +.\" $DragonFly: src/lib/libc/stdlib/tsearch.c,v 1.6 2005/11/24 17:18:30 swildner Exp $ +.\" +.Dd June 15, 1997 +.Dt TSEARCH 3 +.Os +.Sh NAME +.Nm tsearch , +.Nm tfind , +.Nm tdelete , +.Nm twalk +.Nd manipulate binary search trees +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In search.h +.Ft void * +.Fn tdelete "const void * restrict key" "void ** restrict rootp" "int (*compar) (const void *, const void *)" +.Ft void * +.Fn tfind "const void *key" "void * const *rootp" "int (*compar) (const void *, const void *)" +.Ft void * +.Fn tsearch "const void *key" "void **rootp" "int (*compar) (const void *, const void *)" +.Ft void +.Fn twalk "const void *root" "void (*action) (const void *, VISIT, int)" +.Sh DESCRIPTION +The +.Fn tdelete , +.Fn tfind , +.Fn tsearch , +and +.Fn twalk +functions manage binary search trees based on algorithms T and D +from Knuth (6.2.2). +The comparison function passed in by +the user has the same style of return values as +.Xr strcmp 3 . +.Pp +The +.Fn tfind +function +searches for the datum matched by the argument +.Fa key +in the binary tree rooted at +.Fa rootp , +returning a pointer to the datum if it is found and NULL +if it is not. +.Pp +The +.Fn tsearch +function +is identical to +.Fn tfind +except that if no match is found, +.Fa key +is inserted into the tree and a pointer to it is returned. +If +.Fa rootp +points to a NULL value a new binary search tree is created. +.Pp +The +.Fn tdelete +function +deletes a node from the specified binary search tree and returns +a pointer to the parent of the node to be deleted. +It takes the same arguments as +.Fn tfind +and +.Fn tsearch . +If the node to be deleted is the root of the binary search tree, +.Fa rootp +will be adjusted. +.Pp +The +.Fn twalk +function +walks the binary search tree rooted in +.Fa root +and calls the function +.Fa action +on each node. +The +.Fa action +function +is called with three arguments: a pointer to the current node, +a value from the enum +.Sy "typedef enum { preorder, postorder, endorder, leaf } VISIT;" +specifying the traversal type, and a node level (where level +zero is the root of the tree). +.Sh RETURN VALUES +The +.Fn tsearch +function returns NULL if allocation of a new node fails (usually +due to a lack of free memory). +.Pp +The +.Fn tfind , +.Fn tsearch , +and +.Fn tdelete +functions +return NULL if +.Fa rootp +is NULL or the datum cannot be found. +.Pp +The +.Fn twalk +function returns no value. +.Sh SEE ALSO +.Xr bsearch 3 , +.Xr hsearch 3 , +.Xr lsearch 3 diff --git a/lib/roken/tsearch.c b/lib/roken/tsearch.c new file mode 100644 index 000000000..c51a64339 --- /dev/null +++ b/lib/roken/tsearch.c @@ -0,0 +1,180 @@ +/* + * Tree search generalized from Knuth (6.2.2) Algorithm T just like + * the AT&T man page says. + * + * The node_t structure is for internal use only, lint doesn't grok it. + * + * Written by reading the System V Interface Definition, not the code. + * + * Totally public domain. + * + * $NetBSD: tsearch.c,v 1.3 1999/09/16 11:45:37 lukem Exp $ + * $NetBSD: twalk.c,v 1.1 1999/02/22 10:33:16 christos Exp $ + * $NetBSD: tdelete.c,v 1.2 1999/09/16 11:45:37 lukem Exp $ + * $NetBSD: tfind.c,v 1.2 1999/09/16 11:45:37 lukem Exp $ + */ + +#include +#include "roken.h" +#include "search.h" +#include + +typedef struct node { + char *key; + struct node *llink, *rlink; +} node_t; + +#ifndef __DECONST +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) +#endif + +/* + * find or insert datum into search tree + * + * Parameters: + * vkey: key to be located + * vrootp: address of tree root + */ + +ROKEN_LIB_FUNCTION void * +rk_tsearch(const void *vkey, void **vrootp, + int (*compar)(const void *, const void *)) +{ + node_t *q; + node_t **rootp = (node_t **)vrootp; + + if (rootp == NULL) + return NULL; + + while (*rootp != NULL) { /* Knuth's T1: */ + int r; + + if ((r = (*compar)(vkey, (*rootp)->key)) == 0) /* T2: */ + return *rootp; /* we found it! */ + + rootp = (r < 0) ? + &(*rootp)->llink : /* T3: follow left branch */ + &(*rootp)->rlink; /* T4: follow right branch */ + } + + q = malloc(sizeof(node_t)); /* T5: key not found */ + if (q != 0) { /* make new node */ + *rootp = q; /* link new node to old */ + /* LINTED const castaway ok */ + q->key = __DECONST(void *, vkey); /* initialize new node */ + q->llink = q->rlink = NULL; + } + return q; +} + +/* + * Walk the nodes of a tree + * + * Parameters: + * root: Root of the tree to be walked + */ +static void +trecurse(const node_t *root, void (*action)(const void *, VISIT, int), + int level) +{ + + if (root->llink == NULL && root->rlink == NULL) + (*action)(root, leaf, level); + else { + (*action)(root, preorder, level); + if (root->llink != NULL) + trecurse(root->llink, action, level + 1); + (*action)(root, postorder, level); + if (root->rlink != NULL) + trecurse(root->rlink, action, level + 1); + (*action)(root, endorder, level); + } +} + +/* + * Walk the nodes of a tree + * + * Parameters: + * vroot: Root of the tree to be walked + */ +ROKEN_LIB_FUNCTION void +rk_twalk(const void *vroot, + void (*action)(const void *, VISIT, int)) +{ + if (vroot != NULL && action != NULL) + trecurse(vroot, action, 0); +} + +/* + * delete node with given key + * + * vkey: key to be deleted + * vrootp: address of the root of the tree + * compar: function to carry out node comparisons + */ +ROKEN_LIB_FUNCTION void * +rk_tdelete(const void * __restrict vkey, void ** __restrict vrootp, + int (*compar)(const void *, const void *)) +{ + node_t **rootp = (node_t **)vrootp; + node_t *p, *q, *r; + int cmp; + + if (rootp == NULL || (p = *rootp) == NULL) + return NULL; + + while ((cmp = (*compar)(vkey, (*rootp)->key)) != 0) { + p = *rootp; + rootp = (cmp < 0) ? + &(*rootp)->llink : /* follow llink branch */ + &(*rootp)->rlink; /* follow rlink branch */ + if (*rootp == NULL) + return NULL; /* key not found */ + } + r = (*rootp)->rlink; /* D1: */ + if ((q = (*rootp)->llink) == NULL) /* Left NULL? */ + q = r; + else if (r != NULL) { /* Right link is NULL? */ + if (r->llink == NULL) { /* D2: Find successor */ + r->llink = q; + q = r; + } else { /* D3: Find NULL link */ + for (q = r->llink; q->llink != NULL; q = r->llink) + r = q; + r->llink = q->rlink; + q->llink = (*rootp)->llink; + q->rlink = (*rootp)->rlink; + } + } + free(*rootp); /* D4: Free node */ + *rootp = q; /* link parent to new node */ + return p; +} + +/* + * find a node, or return 0 + * + * Parameters: + * vkey: key to be found + * vrootp: address of the tree root + */ +ROKEN_LIB_FUNCTION void * +rk_tfind(const void *vkey, void * const *vrootp, + int (*compar)(const void *, const void *)) +{ + node_t **rootp = (node_t **)vrootp; + + if (rootp == NULL) + return NULL; + + while (*rootp != NULL) { /* T1: */ + int r; + + if ((r = (*compar)(vkey, (*rootp)->key)) == 0) /* T2: */ + return *rootp; /* key found */ + rootp = (r < 0) ? + &(*rootp)->llink : /* T3: follow left branch */ + &(*rootp)->rlink; /* T4: follow right branch */ + } + return NULL; +} diff --git a/lib/roken/version-script.map b/lib/roken/version-script.map index 1baa4b182..1df4ba57c 100644 --- a/lib/roken/version-script.map +++ b/lib/roken/version-script.map @@ -139,6 +139,10 @@ HEIMDAL_ROKEN_1.0 { rk_timevaladd; rk_timevalfix; rk_timevalsub; + rk_tdelete; + rx_tfind; + rk_tsearch; + rk_twalk; rk_undumpdata; rk_unvis; rk_vasnprintf;