diff --git a/configure b/configure index 27f55f51..c633b567 100755 --- a/configure +++ b/configure @@ -11700,6 +11700,44 @@ $as_echo "#define HAVE_ATTR_FORMAT 1" >>confdefs.h fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler (${CC-cc}) accepts the \"unused\" attribute" >&5 +$as_echo_n "checking whether the C compiler (${CC-cc}) accepts the \"unused\" attribute... " >&6; } +if ${ac_cv_c_unused_attribute+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_c_unused_attribute=no +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +void f (char *u __attribute__((unused))); + +int +main () +{ + + f ("x"); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_unused_attribute="yes" +else + ac_cv_c_unused_attribute="no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_unused_attribute" >&5 +$as_echo "$ac_cv_c_unused_attribute" >&6; } +if test $ac_cv_c_unused_attribute = yes; then + +$as_echo "#define HAVE_ATTR_UNUSED 1" >>confdefs.h + +fi + ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy" if test "x$ac_cv_func_strlcpy" = xyes; then : $as_echo "#define HAVE_STRLCPY 1" >>confdefs.h @@ -11716,6 +11754,44 @@ fi +$as_echo "#define USE_MINI_EVENT 1" >>confdefs.h + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking return type of signal handlers" >&5 +$as_echo_n "checking return type of signal handlers... " >&6; } +if ${ac_cv_type_signal+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include + +int +main () +{ +return *(signal (0, 0)) (0) == 1; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_type_signal=int +else + ac_cv_type_signal=void +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_signal" >&5 +$as_echo "$ac_cv_type_signal" >&6; } + +cat >>confdefs.h <<_ACEOF +#define RETSIGTYPE $ac_cv_type_signal +_ACEOF + + + + diff --git a/configure.ac b/configure.ac index 8a667dac..9bc9bca5 100644 --- a/configure.ac +++ b/configure.ac @@ -404,8 +404,29 @@ if test $ac_cv_c_format_attribute = yes; then AC_DEFINE(HAVE_ATTR_FORMAT, 1, [Whether the C compiler accepts the "format" attribute]) fi +AC_MSG_CHECKING(whether the C compiler (${CC-cc}) accepts the "unused" attribute) +AC_CACHE_VAL(ac_cv_c_unused_attribute, +[ac_cv_c_unused_attribute=no +AC_TRY_COMPILE( +[#include +void f (char *u __attribute__((unused))); +], [ + f ("x"); +], +[ac_cv_c_unused_attribute="yes"], +[ac_cv_c_unused_attribute="no"]) +]) +AC_MSG_RESULT($ac_cv_c_unused_attribute) +if test $ac_cv_c_unused_attribute = yes; then + AC_DEFINE(HAVE_ATTR_UNUSED, 1, [Whether the C compiler accepts the "unused" attribute]) +fi + AC_REPLACE_FUNCS(strlcpy) +AC_DEFINE(USE_MINI_EVENT, 1, [Needed for sync stub resolver functions]) + +AC_TYPE_SIGNAL + AH_BOTTOM([ /** Use on-board gldns */ @@ -445,6 +466,23 @@ AH_BOTTOM([ # define ATTR_FORMAT(archetype, string_index, first_to_check) /* empty */ #endif /* !HAVE_ATTR_FORMAT */ +#if defined(DOXYGEN) +# define ATTR_UNUSED(x) x +#elif defined(__cplusplus) +# define ATTR_UNUSED(x) +#elif defined(HAVE_ATTR_UNUSED) +# define ATTR_UNUSED(x) x __attribute__((unused)) +#else /* !HAVE_ATTR_UNUSED */ +# define ATTR_UNUSED(x) x +#endif /* !HAVE_ATTR_UNUSED */ + +/* detect if we need to cast to unsigned int for FD_SET to avoid warnings */ +#ifdef HAVE_WINSOCK2_H +#define FD_SET_T (u_int) +#else +#define FD_SET_T +#endif + #ifdef TIME_WITH_SYS_TIME # include # include diff --git a/src/Makefile.in b/src/Makefile.in index d6228fe4..b17cf7a3 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -87,6 +87,8 @@ LIBOBJDIR= LIBOBJS=@LIBOBJS@ COMPAT_OBJ=$(LIBOBJS:.o=.lo) +UTIL_OBJ=mini_event.lo rbtree.lo + .SUFFIXES: .c .o .a .lo .h .c.o: @@ -95,7 +97,7 @@ COMPAT_OBJ=$(LIBOBJS:.o=.lo) .c.lo: $(LIBTOOL) --quiet --tag=CC --mode=compile $(CC) $(CFLAGS) -c $< -o $@ -$(GLDNS_OBJ) $(COMPAT_OBJ): +$(GLDNS_OBJ) $(COMPAT_OBJ) $(UTIL_OBJ): @: $(LIBTOOL) --quiet --tag=CC --mode=compile $(CC) $(CFLAGS) -c $< -o $@ @@ -134,8 +136,8 @@ libgetdns_ext_ev.la: libgetdns.la extension/libev.lo $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) -o $@ extension/libev.lo ./.libs/libgetdns.la $(EXTENSION_LIBEV_LDFLAGS) $(EXTENSION_LIBEV_EXT_LIBS) -rpath $(libdir) -version-info $(libversion) -no-undefined -export-symbols $(srcdir)/extension/libev.symbols -libgetdns.la: $(GETDNS_OBJ) $(GLDNS_OBJ) $(COMPAT_OBJ) - $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) -o $@ $(GETDNS_OBJ) $(GLDNS_OBJ) $(COMPAT_OBJ) $(LDFLAGS) -rpath $(libdir) -version-info $(libversion) -no-undefined -export-symbols $(srcdir)/libgetdns.symbols +libgetdns.la: $(GETDNS_OBJ) $(GLDNS_OBJ) $(COMPAT_OBJ) $(UTIL_OBJ) + $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) -o $@ $(GETDNS_OBJ) $(GLDNS_OBJ) $(COMPAT_OBJ) $(UTIL_OBJ) $(LDFLAGS) -rpath $(libdir) -version-info $(libversion) -no-undefined -export-symbols $(srcdir)/libgetdns.symbols test: FORCE @@ -191,9 +193,10 @@ configure.status: configure depend: (cd $(srcdir) ; awk 'BEGIN{P=1}{if(P)print}/^# Dependencies/{P=0}' Makefile.in > Makefile.in.new ) - (cd $(srcdir) ; gcc -MM -I. gldns/*.c compat/*.c | \ + (cd $(srcdir) ; gcc -MM -I. gldns/*.c compat/*.c util/*.c | \ sed -e 's?gldns/?$$(srcdir)/gldns/?g' \ -e 's?compat/?$$(srcdir)/compat/?g' \ + -e 's?util/?$$(srcdir)/util/?g' \ -e 's!\(.*\)\.o[ :]*!\1.lo \1.o: !g' >> Makefile.in.new ) (cd $(srcdir) ; diff Makefile.in.new Makefile.in && rm Makefile.in.new \ || mv Makefile.in.new Makefile.in ) @@ -214,3 +217,7 @@ wire2str.lo wire2str.o: $(srcdir)/gldns/wire2str.c config.h $(srcdir)/gldns/wire $(srcdir)/gldns/rrdef.h $(srcdir)/gldns/pkthdr.h $(srcdir)/gldns/parseutil.h $(srcdir)/gldns/gbuffer.h \ $(srcdir)/gldns/keyraw.h strlcpy.lo strlcpy.o: $(srcdir)/compat/strlcpy.c config.h +mini_event.lo mini_event.o: $(srcdir)/util/mini_event.c config.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h \ + $(srcdir)/util/fptr_wlist.h +rbtree.lo rbtree.o: $(srcdir)/util/rbtree.c config.h $(srcdir)/util/log.h $(srcdir)/util/fptr_wlist.h \ + $(srcdir)/util/rbtree.h diff --git a/src/config.h.in b/src/config.h.in index 2febc465..43ff20d0 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -6,6 +6,9 @@ /* Whether the C compiler accepts the "format" attribute */ #undef HAVE_ATTR_FORMAT +/* Whether the C compiler accepts the "unused" attribute */ +#undef HAVE_ATTR_UNUSED + /* Define to 1 if you have the header file. */ #undef HAVE_BSD_STRING_H @@ -106,6 +109,9 @@ /* Define to the version of this package. */ #undef PACKAGE_VERSION +/* Define as the return type of signal handlers (`int' or `void'). */ +#undef RETSIGTYPE + /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS @@ -115,6 +121,9 @@ /* Default trust anchor file */ #undef TRUST_ANCHOR_FILE +/* Needed for sync stub resolver functions */ +#undef USE_MINI_EVENT + /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ @@ -188,6 +197,23 @@ # define ATTR_FORMAT(archetype, string_index, first_to_check) /* empty */ #endif /* !HAVE_ATTR_FORMAT */ +#if defined(DOXYGEN) +# define ATTR_UNUSED(x) x +#elif defined(__cplusplus) +# define ATTR_UNUSED(x) +#elif defined(HAVE_ATTR_UNUSED) +# define ATTR_UNUSED(x) x __attribute__((unused)) +#else /* !HAVE_ATTR_UNUSED */ +# define ATTR_UNUSED(x) x +#endif /* !HAVE_ATTR_UNUSED */ + +/* detect if we need to cast to unsigned int for FD_SET to avoid warnings */ +#ifdef HAVE_WINSOCK2_H +#define FD_SET_T (u_int) +#else +#define FD_SET_T +#endif + #ifdef TIME_WITH_SYS_TIME # include # include diff --git a/src/util/fptr_wlist.h b/src/util/fptr_wlist.h new file mode 100644 index 00000000..d98741df --- /dev/null +++ b/src/util/fptr_wlist.h @@ -0,0 +1,42 @@ +/** + * + * /brief dummy prototypes for function pointer whitelisting + * + */ + +/* + * Copyright (c) 2013, NLnet Labs, Verisign, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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 Verisign, Inc. 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. + */ + +#ifndef UTIL_FPTR_WLIST_H +#define UTIL_FPTR_WLIST_H + +#define fptr_ok(x) +#define fptr_whitelist_event(x) +#define fptr_whitelist_rbtree_cmp(x) + +#endif /* UTIL_FPTR_WLIST_H */ + diff --git a/src/util/import.sh b/src/util/import.sh new file mode 100755 index 00000000..f797ba5a --- /dev/null +++ b/src/util/import.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# Meant to be run from this directory + +mkdir ub || true +cd ub +for f in mini_event.c mini_event.h rbtree.c rbtree.h +do + wget http://unbound.net/svn/trunk/util/$f + sed -e 's/event_/getdns_event_/g' -e 's/signal_add/getdns_signal_add/g' -e 's/signal_del/getdns_signal_del/g' -e 's/struct event/struct getdns_event/g' -e 's/mini_ev_cmp/getdns_mini_ev_cmp/g' -e 's/#include "rbtree\.h"/#include "util\/rbtree.h"/g' -e 's/rbnode_/getdns_rbnode_/g' -e 's/rbtree_/getdns_rbtree_/g' -e 's/#include "fptr_wlist\.h"/#include "util\/fptr_wlist.h"/g' -e 's/#include "log\.h"/#include "util\/log.h"/g' $f > ../$f +done +cd .. +rm -r ub diff --git a/src/util/log.h b/src/util/log.h new file mode 100644 index 00000000..febf3700 --- /dev/null +++ b/src/util/log.h @@ -0,0 +1,40 @@ +/** + * + * /brief dummy prototypes for logging a la unbound + * + */ + +/* + * Copyright (c) 2013, NLnet Labs, Verisign, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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 Verisign, Inc. 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. + */ + +#ifndef UTIL_LOG_H +#define UTIL_LOG_H + +#define log_assert(x) + +#endif /* UTIL_LOG_H */ + diff --git a/src/util/mini_event.c b/src/util/mini_event.c new file mode 100644 index 00000000..2c9f5aac --- /dev/null +++ b/src/util/mini_event.c @@ -0,0 +1,394 @@ +/* + * mini_event.c - implementation of part of libevent api, portably. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 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. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 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 + * HOLDER 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 + * fake libevent implementation. Less broad in functionality, and only + * supports select(2). + */ + +#include "config.h" +#ifdef HAVE_TIME_H +#include +#endif +#include + +#if defined(USE_MINI_EVENT) && !defined(USE_WINSOCK) +#include +#include "util/mini_event.h" +#include "util/fptr_wlist.h" + +/** compare events in tree, based on timevalue, ptr for uniqueness */ +int getdns_mini_ev_cmp(const void* a, const void* b) +{ + const struct getdns_event *e = (const struct getdns_event*)a; + const struct getdns_event *f = (const struct getdns_event*)b; + if(e->ev_timeout.tv_sec < f->ev_timeout.tv_sec) + return -1; + if(e->ev_timeout.tv_sec > f->ev_timeout.tv_sec) + return 1; + if(e->ev_timeout.tv_usec < f->ev_timeout.tv_usec) + return -1; + if(e->ev_timeout.tv_usec > f->ev_timeout.tv_usec) + return 1; + if(e < f) + return -1; + if(e > f) + return 1; + return 0; +} + +/** set time */ +static int +settime(struct getdns_event_base* base) +{ + if(gettimeofday(base->time_tv, NULL) < 0) { + return -1; + } +#ifndef S_SPLINT_S + *base->time_secs = (time_t)base->time_tv->tv_sec; +#endif + return 0; +} + +/** create event base */ +void *getdns_event_init(time_t* time_secs, struct timeval* time_tv) +{ + struct getdns_event_base* base = (struct getdns_event_base*)malloc( + sizeof(struct getdns_event_base)); + if(!base) + return NULL; + memset(base, 0, sizeof(*base)); + base->time_secs = time_secs; + base->time_tv = time_tv; + if(settime(base) < 0) { + getdns_event_base_free(base); + return NULL; + } + base->times = getdns_rbtree_create(getdns_mini_ev_cmp); + if(!base->times) { + getdns_event_base_free(base); + return NULL; + } + base->capfd = MAX_FDS; +#ifdef FD_SETSIZE + if((int)FD_SETSIZE < base->capfd) + base->capfd = (int)FD_SETSIZE; +#endif + base->fds = (struct getdns_event**)calloc((size_t)base->capfd, + sizeof(struct getdns_event*)); + if(!base->fds) { + getdns_event_base_free(base); + return NULL; + } + base->signals = (struct getdns_event**)calloc(MAX_SIG, sizeof(struct getdns_event*)); + if(!base->signals) { + getdns_event_base_free(base); + return NULL; + } +#ifndef S_SPLINT_S + FD_ZERO(&base->reads); + FD_ZERO(&base->writes); +#endif + return base; +} + +/** get version */ +const char *getdns_event_get_version(void) +{ + return "mini-event-"PACKAGE_VERSION; +} + +/** get polling method, select */ +const char *getdns_event_get_method(void) +{ + return "select"; +} + +/** call timeouts handlers, and return how long to wait for next one or -1 */ +static void handle_timeouts(struct getdns_event_base* base, struct timeval* now, + struct timeval* wait) +{ + struct getdns_event* p; +#ifndef S_SPLINT_S + wait->tv_sec = (time_t)-1; +#endif + + while((getdns_rbnode_t*)(p = (struct getdns_event*)getdns_rbtree_first(base->times)) + !=RBTREE_NULL) { +#ifndef S_SPLINT_S + if(p->ev_timeout.tv_sec > now->tv_sec || + (p->ev_timeout.tv_sec==now->tv_sec && + p->ev_timeout.tv_usec > now->tv_usec)) { + /* there is a next larger timeout. wait for it */ + wait->tv_sec = p->ev_timeout.tv_sec - now->tv_sec; + if(now->tv_usec > p->ev_timeout.tv_usec) { + wait->tv_sec--; + wait->tv_usec = 1000000 - (now->tv_usec - + p->ev_timeout.tv_usec); + } else { + wait->tv_usec = p->ev_timeout.tv_usec + - now->tv_usec; + } + return; + } +#endif + /* event times out, remove it */ + (void)getdns_rbtree_delete(base->times, p); + p->ev_events &= ~EV_TIMEOUT; + fptr_ok(fptr_whitelist_event(p->ev_callback)); + (*p->ev_callback)(p->ev_fd, EV_TIMEOUT, p->ev_arg); + } +} + +/** call select and callbacks for that */ +static int handle_select(struct getdns_event_base* base, struct timeval* wait) +{ + fd_set r, w; + int ret, i; + +#ifndef S_SPLINT_S + if(wait->tv_sec==(time_t)-1) + wait = NULL; +#endif + memmove(&r, &base->reads, sizeof(fd_set)); + memmove(&w, &base->writes, sizeof(fd_set)); + memmove(&base->ready, &base->content, sizeof(fd_set)); + + if((ret = select(base->maxfd+1, &r, &w, NULL, wait)) == -1) { + ret = errno; + if(settime(base) < 0) + return -1; + errno = ret; + if(ret == EAGAIN || ret == EINTR) + return 0; + return -1; + } + if(settime(base) < 0) + return -1; + + for(i=0; imaxfd+1; i++) { + short bits = 0; + if(!base->fds[i] || !(FD_ISSET(i, &base->ready))) { + continue; + } + if(FD_ISSET(i, &r)) { + bits |= EV_READ; + ret--; + } + if(FD_ISSET(i, &w)) { + bits |= EV_WRITE; + ret--; + } + bits &= base->fds[i]->ev_events; + if(bits) { + fptr_ok(fptr_whitelist_event( + base->fds[i]->ev_callback)); + (*base->fds[i]->ev_callback)(base->fds[i]->ev_fd, + bits, base->fds[i]->ev_arg); + if(ret==0) + break; + } + } + return 0; +} + +/** run select in a loop */ +int getdns_event_base_dispatch(struct getdns_event_base* base) +{ + struct timeval wait; + if(settime(base) < 0) + return -1; + while(!base->need_to_exit) + { + /* see if timeouts need handling */ + handle_timeouts(base, base->time_tv, &wait); + if(base->need_to_exit) + return 0; + /* do select */ + if(handle_select(base, &wait) < 0) { + if(base->need_to_exit) + return 0; + return -1; + } + } + return 0; +} + +/** exit that loop */ +int getdns_event_base_loopexit(struct getdns_event_base* base, + struct timeval* ATTR_UNUSED(tv)) +{ + base->need_to_exit = 1; + return 0; +} + +/* free event base, free events yourself */ +void getdns_event_base_free(struct getdns_event_base* base) +{ + if(!base) + return; + if(base->times) + free(base->times); + if(base->fds) + free(base->fds); + if(base->signals) + free(base->signals); + free(base); +} + +/** set content of event */ +void getdns_event_set(struct getdns_event* ev, int fd, short bits, + void (*cb)(int, short, void *), void* arg) +{ + ev->node.key = ev; + ev->ev_fd = fd; + ev->ev_events = bits; + ev->ev_callback = cb; + fptr_ok(fptr_whitelist_event(ev->ev_callback)); + ev->ev_arg = arg; + ev->added = 0; +} + +/* add event to a base */ +int getdns_event_base_set(struct getdns_event_base* base, struct getdns_event* ev) +{ + ev->ev_base = base; + ev->added = 0; + return 0; +} + +/* add event to make it active, you may not change it with getdns_event_set anymore */ +int getdns_event_add(struct getdns_event* ev, struct timeval* tv) +{ + if(ev->added) + getdns_event_del(ev); + if(ev->ev_fd != -1 && ev->ev_fd >= ev->ev_base->capfd) + return -1; + if( (ev->ev_events&(EV_READ|EV_WRITE)) && ev->ev_fd != -1) { + ev->ev_base->fds[ev->ev_fd] = ev; + if(ev->ev_events&EV_READ) { + FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->reads); + } + if(ev->ev_events&EV_WRITE) { + FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->writes); + } + FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->content); + FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->ready); + if(ev->ev_fd > ev->ev_base->maxfd) + ev->ev_base->maxfd = ev->ev_fd; + } + if(tv && (ev->ev_events&EV_TIMEOUT)) { +#ifndef S_SPLINT_S + struct timeval *now = ev->ev_base->time_tv; + ev->ev_timeout.tv_sec = tv->tv_sec + now->tv_sec; + ev->ev_timeout.tv_usec = tv->tv_usec + now->tv_usec; + while(ev->ev_timeout.tv_usec > 1000000) { + ev->ev_timeout.tv_usec -= 1000000; + ev->ev_timeout.tv_sec++; + } +#endif + (void)getdns_rbtree_insert(ev->ev_base->times, &ev->node); + } + ev->added = 1; + return 0; +} + +/* remove event, you may change it again */ +int getdns_event_del(struct getdns_event* ev) +{ + if(ev->ev_fd != -1 && ev->ev_fd >= ev->ev_base->capfd) + return -1; + if((ev->ev_events&EV_TIMEOUT)) + (void)getdns_rbtree_delete(ev->ev_base->times, &ev->node); + if((ev->ev_events&(EV_READ|EV_WRITE)) && ev->ev_fd != -1) { + ev->ev_base->fds[ev->ev_fd] = NULL; + FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->reads); + FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->writes); + FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->ready); + FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->content); + } + ev->added = 0; + return 0; +} + +/** which base gets to handle signals */ +static struct getdns_event_base* signal_base = NULL; +/** signal handler */ +static RETSIGTYPE sigh(int sig) +{ + struct getdns_event* ev; + if(!signal_base || sig < 0 || sig >= MAX_SIG) + return; + ev = signal_base->signals[sig]; + if(!ev) + return; + fptr_ok(fptr_whitelist_event(ev->ev_callback)); + (*ev->ev_callback)(sig, EV_SIGNAL, ev->ev_arg); +} + +/** install signal handler */ +int getdns_signal_add(struct getdns_event* ev, struct timeval* ATTR_UNUSED(tv)) +{ + if(ev->ev_fd == -1 || ev->ev_fd >= MAX_SIG) + return -1; + signal_base = ev->ev_base; + ev->ev_base->signals[ev->ev_fd] = ev; + ev->added = 1; + if(signal(ev->ev_fd, sigh) == SIG_ERR) { + return -1; + } + return 0; +} + +/** remove signal handler */ +int getdns_signal_del(struct getdns_event* ev) +{ + if(ev->ev_fd == -1 || ev->ev_fd >= MAX_SIG) + return -1; + ev->ev_base->signals[ev->ev_fd] = NULL; + ev->added = 0; + return 0; +} + +#else /* USE_MINI_EVENT */ +#ifndef USE_WINSOCK +int getdns_mini_ev_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) +{ + return 0; +} +#endif /* not USE_WINSOCK */ +#endif /* USE_MINI_EVENT */ diff --git a/src/util/mini_event.h b/src/util/mini_event.h new file mode 100644 index 00000000..9f0fab23 --- /dev/null +++ b/src/util/mini_event.h @@ -0,0 +1,177 @@ +/* + * mini-event.h - micro implementation of libevent api, using select() only. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 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. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 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 + * HOLDER 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 + * This file implements part of the event(3) libevent api. + * The back end is only select. Max number of fds is limited. + * Max number of signals is limited, one handler per signal only. + * And one handler per fd. + * + * Although limited to select() and a max (1024) open fds, it + * is efficient: + * o dispatch call caches fd_sets to use. + * o handler calling takes time ~ to the number of fds. + * o timeouts are stored in a redblack tree, sorted, so take log(n). + * Timeouts are only accurate to the second (no subsecond accuracy). + * To avoid cpu hogging, fractional timeouts are rounded up to a whole second. + */ + +#ifndef MINI_EVENT_H +#define MINI_EVENT_H + +#if defined(USE_MINI_EVENT) && !defined(USE_WINSOCK) + +#ifndef HAVE_EVENT_BASE_FREE +#define HAVE_EVENT_BASE_FREE +#endif + +/** event timeout */ +#define EV_TIMEOUT 0x01 +/** event fd readable */ +#define EV_READ 0x02 +/** event fd writable */ +#define EV_WRITE 0x04 +/** event signal */ +#define EV_SIGNAL 0x08 +/** event must persist */ +#define EV_PERSIST 0x10 + +/* needs our redblack tree */ +#include "util/rbtree.h" + +/** max number of file descriptors to support */ +#define MAX_FDS 1024 +/** max number of signals to support */ +#define MAX_SIG 32 + +/** event base */ +struct getdns_event_base +{ + /** sorted by timeout (absolute), ptr */ + getdns_rbtree_t* times; + /** array of 0 - maxfd of ptr to event for it */ + struct getdns_event** fds; + /** max fd in use */ + int maxfd; + /** capacity - size of the fds array */ + int capfd; + /* fdset for read write, for fds ready, and added */ + fd_set + /** fds for reading */ + reads, + /** fds for writing */ + writes, + /** fds determined ready for use */ + ready, + /** ready plus newly added events. */ + content; + /** array of 0 - maxsig of ptr to event for it */ + struct getdns_event** signals; + /** if we need to exit */ + int need_to_exit; + /** where to store time in seconds */ + time_t* time_secs; + /** where to store time in microseconds */ + struct timeval* time_tv; +}; + +/** + * Event structure. Has some of the event elements. + */ +struct getdns_event { + /** node in timeout rbtree */ + getdns_rbnode_t node; + /** is event already added */ + int added; + + /** event base it belongs to */ + struct getdns_event_base *ev_base; + /** fd to poll or -1 for timeouts. signal number for sigs. */ + int ev_fd; + /** what events this event is interested in, see EV_.. above. */ + short ev_events; + /** timeout value */ + struct timeval ev_timeout; + + /** callback to call: fd, eventbits, userarg */ + void (*ev_callback)(int, short, void *arg); + /** callback user arg */ + void *ev_arg; +}; + +/* function prototypes (some are as they appear in event.h) */ +/** create event base */ +void *getdns_event_init(time_t* time_secs, struct timeval* time_tv); +/** get version */ +const char *getdns_event_get_version(void); +/** get polling method, select */ +const char *getdns_event_get_method(void); +/** run select in a loop */ +int getdns_event_base_dispatch(struct getdns_event_base *); +/** exit that loop */ +int getdns_event_base_loopexit(struct getdns_event_base *, struct timeval *); +/** free event base. Free events yourself */ +void getdns_event_base_free(struct getdns_event_base *); +/** set content of event */ +void getdns_event_set(struct getdns_event *, int, short, void (*)(int, short, void *), void *); +/** add event to a base. You *must* call this for every event. */ +int getdns_event_base_set(struct getdns_event_base *, struct getdns_event *); +/** add event to make it active. You may not change it with getdns_event_set anymore */ +int getdns_event_add(struct getdns_event *, struct timeval *); +/** remove event. You may change it again */ +int getdns_event_del(struct getdns_event *); + +/** add a timer */ +#define evtimer_add(ev, tv) getdns_event_add(ev, tv) +/** remove a timer */ +#define evtimer_del(ev) getdns_event_del(ev) + +/* uses different implementation. Cannot mix fd/timeouts and signals inside + * the same struct getdns_event. create several event structs for that. */ +/** install signal handler */ +int getdns_signal_add(struct getdns_event *, struct timeval *); +/** set signal event contents */ +#define signal_set(ev, x, cb, arg) \ + getdns_event_set(ev, x, EV_SIGNAL|EV_PERSIST, cb, arg) +/** remove signal handler */ +int getdns_signal_del(struct getdns_event *); + +#endif /* USE_MINI_EVENT and not USE_WINSOCK */ + +/** compare events in tree, based on timevalue, ptr for uniqueness */ +int getdns_mini_ev_cmp(const void* a, const void* b); + +#endif /* MINI_EVENT_H */ diff --git a/src/util/rbtree.c b/src/util/rbtree.c new file mode 100644 index 00000000..34e7a428 --- /dev/null +++ b/src/util/rbtree.c @@ -0,0 +1,620 @@ +/* + * rbtree.c -- generic red black tree + * + * Copyright (c) 2001-2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 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. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 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 + * HOLDER 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 + * Implementation of a redblack tree. + */ + +#include "config.h" +#include "util/log.h" +#include "util/fptr_wlist.h" +#include "util/rbtree.h" + +/** Node colour black */ +#define BLACK 0 +/** Node colour red */ +#define RED 1 + +/** the NULL node, global alloc */ +getdns_rbnode_t getdns_rbtree_null_node = { + RBTREE_NULL, /* Parent. */ + RBTREE_NULL, /* Left. */ + RBTREE_NULL, /* Right. */ + NULL, /* Key. */ + BLACK /* Color. */ +}; + +/** rotate subtree left (to preserve redblack property) */ +static void getdns_rbtree_rotate_left(getdns_rbtree_t *rbtree, getdns_rbnode_t *node); +/** rotate subtree right (to preserve redblack property) */ +static void getdns_rbtree_rotate_right(getdns_rbtree_t *rbtree, getdns_rbnode_t *node); +/** Fixup node colours when insert happened */ +static void getdns_rbtree_insert_fixup(getdns_rbtree_t *rbtree, getdns_rbnode_t *node); +/** Fixup node colours when delete happened */ +static void getdns_rbtree_delete_fixup(getdns_rbtree_t* rbtree, getdns_rbnode_t* child, getdns_rbnode_t* child_parent); + +/* + * Creates a new red black tree, intializes and returns a pointer to it. + * + * Return NULL on failure. + * + */ +getdns_rbtree_t * +getdns_rbtree_create (int (*cmpf)(const void *, const void *)) +{ + getdns_rbtree_t *rbtree; + + /* Allocate memory for it */ + rbtree = (getdns_rbtree_t *) malloc(sizeof(getdns_rbtree_t)); + if (!rbtree) { + return NULL; + } + + /* Initialize it */ + getdns_rbtree_init(rbtree, cmpf); + + return rbtree; +} + +void +getdns_rbtree_init(getdns_rbtree_t *rbtree, int (*cmpf)(const void *, const void *)) +{ + /* Initialize it */ + rbtree->root = RBTREE_NULL; + rbtree->count = 0; + rbtree->cmp = cmpf; +} + +/* + * Rotates the node to the left. + * + */ +static void +getdns_rbtree_rotate_left(getdns_rbtree_t *rbtree, getdns_rbnode_t *node) +{ + getdns_rbnode_t *right = node->right; + node->right = right->left; + if (right->left != RBTREE_NULL) + right->left->parent = node; + + right->parent = node->parent; + + if (node->parent != RBTREE_NULL) { + if (node == node->parent->left) { + node->parent->left = right; + } else { + node->parent->right = right; + } + } else { + rbtree->root = right; + } + right->left = node; + node->parent = right; +} + +/* + * Rotates the node to the right. + * + */ +static void +getdns_rbtree_rotate_right(getdns_rbtree_t *rbtree, getdns_rbnode_t *node) +{ + getdns_rbnode_t *left = node->left; + node->left = left->right; + if (left->right != RBTREE_NULL) + left->right->parent = node; + + left->parent = node->parent; + + if (node->parent != RBTREE_NULL) { + if (node == node->parent->right) { + node->parent->right = left; + } else { + node->parent->left = left; + } + } else { + rbtree->root = left; + } + left->right = node; + node->parent = left; +} + +static void +getdns_rbtree_insert_fixup(getdns_rbtree_t *rbtree, getdns_rbnode_t *node) +{ + getdns_rbnode_t *uncle; + + /* While not at the root and need fixing... */ + while (node != rbtree->root && node->parent->color == RED) { + /* If our parent is left child of our grandparent... */ + if (node->parent == node->parent->parent->left) { + uncle = node->parent->parent->right; + + /* If our uncle is red... */ + if (uncle->color == RED) { + /* Paint the parent and the uncle black... */ + node->parent->color = BLACK; + uncle->color = BLACK; + + /* And the grandparent red... */ + node->parent->parent->color = RED; + + /* And continue fixing the grandparent */ + node = node->parent->parent; + } else { /* Our uncle is black... */ + /* Are we the right child? */ + if (node == node->parent->right) { + node = node->parent; + getdns_rbtree_rotate_left(rbtree, node); + } + /* Now we're the left child, repaint and rotate... */ + node->parent->color = BLACK; + node->parent->parent->color = RED; + getdns_rbtree_rotate_right(rbtree, node->parent->parent); + } + } else { + uncle = node->parent->parent->left; + + /* If our uncle is red... */ + if (uncle->color == RED) { + /* Paint the parent and the uncle black... */ + node->parent->color = BLACK; + uncle->color = BLACK; + + /* And the grandparent red... */ + node->parent->parent->color = RED; + + /* And continue fixing the grandparent */ + node = node->parent->parent; + } else { /* Our uncle is black... */ + /* Are we the right child? */ + if (node == node->parent->left) { + node = node->parent; + getdns_rbtree_rotate_right(rbtree, node); + } + /* Now we're the right child, repaint and rotate... */ + node->parent->color = BLACK; + node->parent->parent->color = RED; + getdns_rbtree_rotate_left(rbtree, node->parent->parent); + } + } + } + rbtree->root->color = BLACK; +} + + +/* + * Inserts a node into a red black tree. + * + * Returns NULL on failure or the pointer to the newly added node + * otherwise. + */ +getdns_rbnode_t * +getdns_rbtree_insert (getdns_rbtree_t *rbtree, getdns_rbnode_t *data) +{ + /* XXX Not necessary, but keeps compiler quiet... */ + int r = 0; + + /* We start at the root of the tree */ + getdns_rbnode_t *node = rbtree->root; + getdns_rbnode_t *parent = RBTREE_NULL; + + fptr_ok(fptr_whitelist_getdns_rbtree_cmp(rbtree->cmp)); + /* Lets find the new parent... */ + while (node != RBTREE_NULL) { + /* Compare two keys, do we have a duplicate? */ + if ((r = rbtree->cmp(data->key, node->key)) == 0) { + return NULL; + } + parent = node; + + if (r < 0) { + node = node->left; + } else { + node = node->right; + } + } + + /* Initialize the new node */ + data->parent = parent; + data->left = data->right = RBTREE_NULL; + data->color = RED; + rbtree->count++; + + /* Insert it into the tree... */ + if (parent != RBTREE_NULL) { + if (r < 0) { + parent->left = data; + } else { + parent->right = data; + } + } else { + rbtree->root = data; + } + + /* Fix up the red-black properties... */ + getdns_rbtree_insert_fixup(rbtree, data); + + return data; +} + +/* + * Searches the red black tree, returns the data if key is found or NULL otherwise. + * + */ +getdns_rbnode_t * +getdns_rbtree_search (getdns_rbtree_t *rbtree, const void *key) +{ + getdns_rbnode_t *node; + + if (getdns_rbtree_find_less_equal(rbtree, key, &node)) { + return node; + } else { + return NULL; + } +} + +/** helpers for delete: swap node colours */ +static void swap_int8(uint8_t* x, uint8_t* y) +{ + uint8_t t = *x; *x = *y; *y = t; +} + +/** helpers for delete: swap node pointers */ +static void swap_np(getdns_rbnode_t** x, getdns_rbnode_t** y) +{ + getdns_rbnode_t* t = *x; *x = *y; *y = t; +} + +/** Update parent pointers of child trees of 'parent' */ +static void change_parent_ptr(getdns_rbtree_t* rbtree, getdns_rbnode_t* parent, getdns_rbnode_t* old, getdns_rbnode_t* new) +{ + if(parent == RBTREE_NULL) + { + log_assert(rbtree->root == old); + if(rbtree->root == old) rbtree->root = new; + return; + } + log_assert(parent->left == old || parent->right == old + || parent->left == new || parent->right == new); + if(parent->left == old) parent->left = new; + if(parent->right == old) parent->right = new; +} +/** Update parent pointer of a node 'child' */ +static void change_child_ptr(getdns_rbnode_t* child, getdns_rbnode_t* old, getdns_rbnode_t* new) +{ + if(child == RBTREE_NULL) return; + log_assert(child->parent == old || child->parent == new); + if(child->parent == old) child->parent = new; +} + +getdns_rbnode_t* +getdns_rbtree_delete(getdns_rbtree_t *rbtree, const void *key) +{ + getdns_rbnode_t *to_delete; + getdns_rbnode_t *child; + if((to_delete = getdns_rbtree_search(rbtree, key)) == 0) return 0; + rbtree->count--; + + /* make sure we have at most one non-leaf child */ + if(to_delete->left != RBTREE_NULL && to_delete->right != RBTREE_NULL) + { + /* swap with smallest from right subtree (or largest from left) */ + getdns_rbnode_t *smright = to_delete->right; + while(smright->left != RBTREE_NULL) + smright = smright->left; + /* swap the smright and to_delete elements in the tree, + * but the getdns_rbnode_t is first part of user data struct + * so cannot just swap the keys and data pointers. Instead + * readjust the pointers left,right,parent */ + + /* swap colors - colors are tied to the position in the tree */ + swap_int8(&to_delete->color, &smright->color); + + /* swap child pointers in parents of smright/to_delete */ + change_parent_ptr(rbtree, to_delete->parent, to_delete, smright); + if(to_delete->right != smright) + change_parent_ptr(rbtree, smright->parent, smright, to_delete); + + /* swap parent pointers in children of smright/to_delete */ + change_child_ptr(smright->left, smright, to_delete); + change_child_ptr(smright->left, smright, to_delete); + change_child_ptr(smright->right, smright, to_delete); + change_child_ptr(smright->right, smright, to_delete); + change_child_ptr(to_delete->left, to_delete, smright); + if(to_delete->right != smright) + change_child_ptr(to_delete->right, to_delete, smright); + if(to_delete->right == smright) + { + /* set up so after swap they work */ + to_delete->right = to_delete; + smright->parent = smright; + } + + /* swap pointers in to_delete/smright nodes */ + swap_np(&to_delete->parent, &smright->parent); + swap_np(&to_delete->left, &smright->left); + swap_np(&to_delete->right, &smright->right); + + /* now delete to_delete (which is at the location where the smright previously was) */ + } + log_assert(to_delete->left == RBTREE_NULL || to_delete->right == RBTREE_NULL); + + if(to_delete->left != RBTREE_NULL) child = to_delete->left; + else child = to_delete->right; + + /* unlink to_delete from the tree, replace to_delete with child */ + change_parent_ptr(rbtree, to_delete->parent, to_delete, child); + change_child_ptr(child, to_delete, to_delete->parent); + + if(to_delete->color == RED) + { + /* if node is red then the child (black) can be swapped in */ + } + else if(child->color == RED) + { + /* change child to BLACK, removing a RED node is no problem */ + if(child!=RBTREE_NULL) child->color = BLACK; + } + else getdns_rbtree_delete_fixup(rbtree, child, to_delete->parent); + + /* unlink completely */ + to_delete->parent = RBTREE_NULL; + to_delete->left = RBTREE_NULL; + to_delete->right = RBTREE_NULL; + to_delete->color = BLACK; + return to_delete; +} + +static void getdns_rbtree_delete_fixup(getdns_rbtree_t* rbtree, getdns_rbnode_t* child, getdns_rbnode_t* child_parent) +{ + getdns_rbnode_t* sibling; + int go_up = 1; + + /* determine sibling to the node that is one-black short */ + if(child_parent->right == child) sibling = child_parent->left; + else sibling = child_parent->right; + + while(go_up) + { + if(child_parent == RBTREE_NULL) + { + /* removed parent==black from root, every path, so ok */ + return; + } + + if(sibling->color == RED) + { /* rotate to get a black sibling */ + child_parent->color = RED; + sibling->color = BLACK; + if(child_parent->right == child) + getdns_rbtree_rotate_right(rbtree, child_parent); + else getdns_rbtree_rotate_left(rbtree, child_parent); + /* new sibling after rotation */ + if(child_parent->right == child) sibling = child_parent->left; + else sibling = child_parent->right; + } + + if(child_parent->color == BLACK + && sibling->color == BLACK + && sibling->left->color == BLACK + && sibling->right->color == BLACK) + { /* fixup local with recolor of sibling */ + if(sibling != RBTREE_NULL) + sibling->color = RED; + + child = child_parent; + child_parent = child_parent->parent; + /* prepare to go up, new sibling */ + if(child_parent->right == child) sibling = child_parent->left; + else sibling = child_parent->right; + } + else go_up = 0; + } + + if(child_parent->color == RED + && sibling->color == BLACK + && sibling->left->color == BLACK + && sibling->right->color == BLACK) + { + /* move red to sibling to rebalance */ + if(sibling != RBTREE_NULL) + sibling->color = RED; + child_parent->color = BLACK; + return; + } + log_assert(sibling != RBTREE_NULL); + + /* get a new sibling, by rotating at sibling. See which child + of sibling is red */ + if(child_parent->right == child + && sibling->color == BLACK + && sibling->right->color == RED + && sibling->left->color == BLACK) + { + sibling->color = RED; + sibling->right->color = BLACK; + getdns_rbtree_rotate_left(rbtree, sibling); + /* new sibling after rotation */ + if(child_parent->right == child) sibling = child_parent->left; + else sibling = child_parent->right; + } + else if(child_parent->left == child + && sibling->color == BLACK + && sibling->left->color == RED + && sibling->right->color == BLACK) + { + sibling->color = RED; + sibling->left->color = BLACK; + getdns_rbtree_rotate_right(rbtree, sibling); + /* new sibling after rotation */ + if(child_parent->right == child) sibling = child_parent->left; + else sibling = child_parent->right; + } + + /* now we have a black sibling with a red child. rotate and exchange colors. */ + sibling->color = child_parent->color; + child_parent->color = BLACK; + if(child_parent->right == child) + { + log_assert(sibling->left->color == RED); + sibling->left->color = BLACK; + getdns_rbtree_rotate_right(rbtree, child_parent); + } + else + { + log_assert(sibling->right->color == RED); + sibling->right->color = BLACK; + getdns_rbtree_rotate_left(rbtree, child_parent); + } +} + +int +getdns_rbtree_find_less_equal(getdns_rbtree_t *rbtree, const void *key, getdns_rbnode_t **result) +{ + int r; + getdns_rbnode_t *node; + + log_assert(result); + + /* We start at root... */ + node = rbtree->root; + + *result = NULL; + fptr_ok(fptr_whitelist_getdns_rbtree_cmp(rbtree->cmp)); + + /* While there are children... */ + while (node != RBTREE_NULL) { + r = rbtree->cmp(key, node->key); + if (r == 0) { + /* Exact match */ + *result = node; + return 1; + } + if (r < 0) { + node = node->left; + } else { + /* Temporary match */ + *result = node; + node = node->right; + } + } + return 0; +} + +/* + * Finds the first element in the red black tree + * + */ +getdns_rbnode_t * +getdns_rbtree_first (getdns_rbtree_t *rbtree) +{ + getdns_rbnode_t *node; + + for (node = rbtree->root; node->left != RBTREE_NULL; node = node->left); + return node; +} + +getdns_rbnode_t * +getdns_rbtree_last (getdns_rbtree_t *rbtree) +{ + getdns_rbnode_t *node; + + for (node = rbtree->root; node->right != RBTREE_NULL; node = node->right); + return node; +} + +/* + * Returns the next node... + * + */ +getdns_rbnode_t * +getdns_rbtree_next (getdns_rbnode_t *node) +{ + getdns_rbnode_t *parent; + + if (node->right != RBTREE_NULL) { + /* One right, then keep on going left... */ + for (node = node->right; node->left != RBTREE_NULL; node = node->left); + } else { + parent = node->parent; + while (parent != RBTREE_NULL && node == parent->right) { + node = parent; + parent = parent->parent; + } + node = parent; + } + return node; +} + +getdns_rbnode_t * +getdns_rbtree_previous(getdns_rbnode_t *node) +{ + getdns_rbnode_t *parent; + + if (node->left != RBTREE_NULL) { + /* One left, then keep on going right... */ + for (node = node->left; node->right != RBTREE_NULL; node = node->right); + } else { + parent = node->parent; + while (parent != RBTREE_NULL && node == parent->left) { + node = parent; + parent = parent->parent; + } + node = parent; + } + return node; +} + +/** recursive descent traverse */ +static void +traverse_post(void (*func)(getdns_rbnode_t*, void*), void* arg, getdns_rbnode_t* node) +{ + if(!node || node == RBTREE_NULL) + return; + /* recurse */ + traverse_post(func, arg, node->left); + traverse_post(func, arg, node->right); + /* call user func */ + (*func)(node, arg); +} + +void +traverse_postorder(getdns_rbtree_t* tree, void (*func)(getdns_rbnode_t*, void*), void* arg) +{ + traverse_post(func, arg, tree->root); +} diff --git a/src/util/rbtree.h b/src/util/rbtree.h new file mode 100644 index 00000000..89827003 --- /dev/null +++ b/src/util/rbtree.h @@ -0,0 +1,192 @@ +/* + * rbtree.h -- generic red-black tree + * + * Copyright (c) 2001-2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 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. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 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 + * HOLDER 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 + * Red black tree. Implementation taken from NSD 3.0.5, adjusted for use + * in unbound (memory allocation, logging and so on). + */ + +#ifndef UTIL_RBTREE_H_ +#define UTIL_RBTREE_H_ + +/** + * This structure must be the first member of the data structure in + * the rbtree. This allows easy casting between an getdns_rbnode_t and the + * user data (poor man's inheritance). + */ +typedef struct getdns_rbnode_t getdns_rbnode_t; +/** + * The getdns_rbnode_t struct definition. + */ +struct getdns_rbnode_t { + /** parent in rbtree, RBTREE_NULL for root */ + getdns_rbnode_t *parent; + /** left node (smaller items) */ + getdns_rbnode_t *left; + /** right node (larger items) */ + getdns_rbnode_t *right; + /** pointer to sorting key */ + const void *key; + /** colour of this node */ + uint8_t color; +}; + +/** The nullpointer, points to empty node */ +#define RBTREE_NULL &getdns_rbtree_null_node +/** the global empty node */ +extern getdns_rbnode_t getdns_rbtree_null_node; + +/** An entire red black tree */ +typedef struct getdns_rbtree_t getdns_rbtree_t; +/** definition for tree struct */ +struct getdns_rbtree_t { + /** The root of the red-black tree */ + getdns_rbnode_t *root; + + /** The number of the nodes in the tree */ + size_t count; + + /** + * Key compare function. <0,0,>0 like strcmp. + * Return 0 on two NULL ptrs. + */ + int (*cmp) (const void *, const void *); +}; + +/** + * Create new tree (malloced) with given key compare function. + * @param cmpf: compare function (like strcmp) takes pointers to two keys. + * @return: new tree, empty. + */ +getdns_rbtree_t *getdns_rbtree_create(int (*cmpf)(const void *, const void *)); + +/** + * Init a new tree (malloced by caller) with given key compare function. + * @param rbtree: uninitialised memory for new tree, returned empty. + * @param cmpf: compare function (like strcmp) takes pointers to two keys. + */ +void getdns_rbtree_init(getdns_rbtree_t *rbtree, int (*cmpf)(const void *, const void *)); + +/** + * Insert data into the tree. + * @param rbtree: tree to insert to. + * @param data: element to insert. + * @return: data ptr or NULL if key already present. + */ +getdns_rbnode_t *getdns_rbtree_insert(getdns_rbtree_t *rbtree, getdns_rbnode_t *data); + +/** + * Delete element from tree. + * @param rbtree: tree to delete from. + * @param key: key of item to delete. + * @return: node that is now unlinked from the tree. User to delete it. + * returns 0 if node not present + */ +getdns_rbnode_t *getdns_rbtree_delete(getdns_rbtree_t *rbtree, const void *key); + +/** + * Find key in tree. Returns NULL if not found. + * @param rbtree: tree to find in. + * @param key: key that must match. + * @return: node that fits or NULL. + */ +getdns_rbnode_t *getdns_rbtree_search(getdns_rbtree_t *rbtree, const void *key); + +/** + * Find, but match does not have to be exact. + * @param rbtree: tree to find in. + * @param key: key to find position of. + * @param result: set to the exact node if present, otherwise to element that + * precedes the position of key in the tree. NULL if no smaller element. + * @return: true if exact match in result. Else result points to <= element, + * or NULL if key is smaller than the smallest key. + */ +int getdns_rbtree_find_less_equal(getdns_rbtree_t *rbtree, const void *key, + getdns_rbnode_t **result); + +/** + * Returns first (smallest) node in the tree + * @param rbtree: tree + * @return: smallest element or NULL if tree empty. + */ +getdns_rbnode_t *getdns_rbtree_first(getdns_rbtree_t *rbtree); + +/** + * Returns last (largest) node in the tree + * @param rbtree: tree + * @return: largest element or NULL if tree empty. + */ +getdns_rbnode_t *getdns_rbtree_last(getdns_rbtree_t *rbtree); + +/** + * Returns next larger node in the tree + * @param rbtree: tree + * @return: next larger element or NULL if no larger in tree. + */ +getdns_rbnode_t *getdns_rbtree_next(getdns_rbnode_t *rbtree); + +/** + * Returns previous smaller node in the tree + * @param rbtree: tree + * @return: previous smaller element or NULL if no previous in tree. + */ +getdns_rbnode_t *getdns_rbtree_previous(getdns_rbnode_t *rbtree); + +/** + * Call with node=variable of struct* with getdns_rbnode_t as first element. + * with type is the type of a pointer to that struct. + */ +#define RBTREE_FOR(node, type, rbtree) \ + for(node=(type)getdns_rbtree_first(rbtree); \ + (getdns_rbnode_t*)node != RBTREE_NULL; \ + node = (type)getdns_rbtree_next((getdns_rbnode_t*)node)) + +/** + * Call function for all elements in the redblack tree, such that + * leaf elements are called before parent elements. So that all + * elements can be safely free()d. + * Note that your function must not remove the nodes from the tree. + * Since that may trigger rebalances of the rbtree. + * @param tree: the tree + * @param func: function called with element and user arg. + * The function must not alter the rbtree. + * @param arg: user argument. + */ +void traverse_postorder(getdns_rbtree_t* tree, void (*func)(getdns_rbnode_t*, void*), + void* arg); + +#endif /* UTIL_RBTREE_H_ */