rbt.c revision 83e6eb0dfe7d3525dcf093f440e04e8971c4c5d2
/*
* Copyright (C) 1999, 2000 Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
* INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: rbt.c,v 1.91 2000/08/03 19:46:32 bwelling Exp $ */
/* Principal Authors: DCL */
#include <config.h>
/*
* This define is so dns/name.h (included by dns/fixedname.h) uses more
* efficient macro calls instead of functions for a few operations.
*/
#define DNS_NAME_USEINLINE 1
#include <dns/fixedname.h>
/*
* XXXDCL Since parent pointers were added in again, I could remove all of the
* chain junk, and replace with dns_rbt_firstnode, _previousnode, _nextnode,
* _lastnode. This would involve pretty major change to the API.
*/
struct dns_rbt {
unsigned int magic;
void (*data_deleter)(void *, void *);
void * deleter_arg;
unsigned int nodecount;
};
#define RED 0
#define BLACK 1
/*
* Elements of the rbtnode structure.
*/
/*
* Structure elements from the rbtdb.c, not
* used as part of the rbt.c algorithms.
*/
/*
* The variable length stuff stored after the node.
*/
/*
* Color management.
*/
/*
* Chain management.
*/
do { \
return (ISC_R_NOMEMORY); \
} \
} while (0)
/*
* The following macros directly access normally private name variables.
* These macros are used to avoid a lot of function calls in the critical
* path of the tree traversal code.
*/
do { \
} while (0)
#ifdef DEBUG
#define inline
/*
* A little something to help out in GDB.
*/
return (name);
}
#endif
#ifdef RBT_MEM_TEST
#endif
/*
* Forward declarations.
*/
static isc_result_t
static isc_result_t
static inline isc_result_t
static inline void
static inline void
static inline void
static void
static void
static void
/*
*/
{
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
/*
*/
void
}
unsigned int
}
/*
* The next three functions for chains, get_ancestor_mem, put_ancestor_mem
* and chain_name, appear early in this file so they can be effectively
* inlined by the other rbt functions that use them.
*/
static inline isc_result_t
if (oldsize == 0) {
} else {
if (ancestor_mem == NULL)
return (ISC_R_NOMEMORY);
}
return (ISC_R_SUCCESS);
}
/*
* This is used by functions that are popping the chain off their
* own stack, and so do not need to have ancestor_maxitems or the
* ancestors pointer reset. Functions that will be reusing a chain
* structure need to call dns_rbtnodechain_reset() instead.
*/
static inline void
* sizeof(dns_rbtnode_t *));
}
static inline isc_result_t
{
unsigned int i;
for (i = 0; i < chain->level_count; i++) {
if (result != ISC_R_SUCCESS)
break;
}
if (result == ISC_R_SUCCESS &&
}
return (result);
}
static inline isc_result_t
do {
/*
* Go as far right and then down as much as possible,
* as long as the rightmost node has a down pointer.
*/
}
break;
} while (1);
return (ISC_R_SUCCESS);
}
/*
* Add 'name' to tree, initializing its data pointer with 'data'.
*/
/*
* Does this thing have too many variables or what?
*/
int order;
/*
* Create a copy of the name so the original name structure is
* not modified. The name data needs to be modifiable when
* a node is split on a bitstring label.
*/
if (result == ISC_R_SUCCESS) {
*nodep = new_current;
}
return (result);
}
do {
&order,
&common_labels, &common_bits);
if (compared == dns_namereln_equal) {
break;
}
if (compared == dns_namereln_none) {
if (order < 0) {
} else if (order > 0) {
}
} else {
/*
* This name has some suffix in common with the
* name at the current node. If the name at
* the current node is shorter, that means the
* new name should be in a subtree. If the
* name at the current node is longer, that means
* the down pointer to this tree should point
* to a new tree that has the common suffix, and
* the non-common parts of these two names should
* start a new tree.
*/
if (compared == dns_namereln_subdomain) {
/*
* All of the existing labels are in common,
* so the new name is in a subtree.
* Whack off the common labels for the
* not-in-common part to be searched for
* in the next level.
*/
if (result != ISC_R_SUCCESS)
break;
/*
* Follow the down pointer (possibly NULL).
*/
} else {
/*
* The number of labels in common is fewer
* than the number of labels at the current
* node, so the current node must be adjusted
* to have just the common suffix, and a down
* pointer made to a new tree.
*/
|| compared == dns_namereln_contains);
/*
* Ensure the number of levels in the tree
* does not exceed the number of logical
* levels allowed by DNSSEC.
*
* XXX DCL need a better error result?
*/
if (chain.level_count ==
break;
}
/*
* Split the name into two parts, a prefix
* which is the not-in-common parts of the
* two names and a suffix that is the common
* parts of them.
*/
if (result == ISC_R_SUCCESS)
&new_current);
if (result != ISC_R_SUCCESS)
break;
/*
* Reproduce the tree attributes of the
* current node.
*/
/*
* Fix pointers that were to the current node.
*/
else
}
*root = new_current;
/*
* Now make the new root of the subtree
* as the not-in-common labels of the current
* node, keeping the same memory location so
* as not to break any external references to
* the node. The down pointer and name data
* are preserved, while left and right
* pointers are nullified when the node is
* established as the start of the next level.
*
* The name stored at the node is effectively
* truncated in place by setting the shorter
* name length, moving the offsets to the
* end of the truncated name, and then
* updating PADBYTES to reflect the truncation.
*
* When bitstring labels are involved, things
* are just a tad more complicated (aren't
* they always?) because the splitting
* has shifted the bits that this name needs,
* as well as adjusted the bit count.
* So there are convolutions to deal with it.
* There are compromises here between
* abstraction and efficiency.
*/
if (common_bits > 0) {
&label);
&label);
} else
add_bits = 0;
/*
* Set up the new root of the next level.
* By definition it will not be the top
* level tree, so clear DNS_NAMEATTR_ABSOLUTE.
*/
if (common_labels ==
common_bits == add_bits) {
/*
* The name has been added by pushing
* the not-in-common parts down to
* a new level.
*/
*nodep = new_current;
return (ISC_R_SUCCESS);
} else {
/*
* The current node has no data,
* because it is just a placeholder.
* Its data pointer is already NULL
* from create_node()), so there's
* nothing more to do to but add it to
* the ancestor chain, because it will
* be the parent node of the new node.
*/
/* The not-in-common parts of the new
* name will be inserted into the new
* level following this loop (unless
* result != ISC_R_SUCCESS, which
* is tested after the loop ends).
*/
NULL);
break;
}
}
}
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS) {
*nodep = new_current;
}
return (result);
}
/*
* Add a name to the tree of trees, associating it with some data.
*/
/*
* dns_rbt_addnode will report the node exists even when
* it does not have data associated with it, but the
* dns_rbt_*name functions all behave depending on whether
* there is data associated with a node.
*/
if (result == ISC_R_SUCCESS ||
}
return (result);
}
/*
* Find the node for "name" in the tree of trees.
*/
void *callback_arg)
{
unsigned int common_labels, common_bits;
int order;
/*
* If there is a chain it needs to appear to be in a sane state,
* otherwise a chain is still needed to generate foundname and
* callback_name.
*/
chain = &localchain;
} else
return (ISC_R_NOTFOUND);
/*
* search_name is the name segment being sought in each tree level.
* By using a fixedname, the search_name will definitely have offsets
* and a buffer for use by any splitting that happens in the middle
* of a bitstring label. By using dns_name_clone, no name data is
* copied unless a bitstring split occurs.
*/
&order,
&common_labels, &common_bits);
if (compared == dns_namereln_equal)
break;
if (compared == dns_namereln_none) {
/*
* Standard binary search tree movement.
*/
if (order < 0)
else
} else {
/*
* The names have some common suffix labels.
*
* If the number in common are equal in length to
* the current node's name length, then follow the
* down pointer and search in the new tree.
*/
if (compared == dns_namereln_subdomain) {
/*
* Whack off the current node's common parts
* for the name to search in the next level.
*/
search_name, NULL);
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* This might be the closest enclosing name.
*/
(options & DNS_RBTFIND_EMPTYDATA) != 0)
/*
* Point the chain to the next level. This
* needs to be done before 'current' is pointed
* there because the callback in the next
* block of code needs the current 'current',
* but in the event the callback requests that
* the search be stopped then the
* DNS_R_PARTIALMATCH code at the end of this
* function needs the chain pointed to the
* next level.
*/
/*
* The caller may want to interrupt the
* downward search when certain special nodes
* are traversed. If this is a special node,
* the callback is used to learn what the
* caller wants to do.
*/
FINDCALLBACK(current)) {
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != DNS_R_CONTINUE) {
/*
* Treat this node as if it
* had no down pointer.
*/
break;
}
}
/*
* Finally, head to the next tree level.
*/
} else {
/*
* Though there are labels in common, the
* entire name at this node is not common
* with the search name so the search
* name does not exist in the tree.
* Add this node to the ancestor chain
* to simplify things for the chain fixing
* logic below then end the loop.
*/
|| compared == dns_namereln_contains);
}
}
}
/*
* If current is not NULL, NOEXACT is not disallowing exact matches,
* and either the node has data or an empty node is ok, return
* ISC_R_SUCCESS to indicate an exact match.
*/
(options & DNS_RBTFIND_EMPTYDATA) != 0)) {
/*
* Found an exact match.
*/
else
if (result == ISC_R_SUCCESS) {
} else
} else {
/*
* Did not find an exact match (or did not want one).
*/
/*
* ... but found a partially matching superdomain.
* Unwind the chain to the partial match node
* to set level_matches to the level above the node,
* and then to derive the name.
*
* chain->level_count is guaranteed to be at least 1
* here because by definition of finding a superdomain,
* the chain is pointed to at least the first subtree.
*/
chain->level_matches--;
}
} else
if (result == ISC_R_SUCCESS)
} else
/*
* There was an exact match but either
* DNS_RBTFIND_NOEXACT was set, or
* DNS_RBTFIND_EMPTYDATA was set and the node had no
* data. A policy decision was made to set the
* chain to the exact match, but this is subject
* to change if it becomes apparent that something
* else would be more useful. It is important that
* this case is handled here, because the predecessor
* setting code below assumes the match was not exact.
*/
((options & DNS_RBTFIND_EMPTYDATA) == 0 &&
} else if ((options & DNS_RBTFIND_NOPREDECESSOR) != 0) {
/*
* Ensure the chain points nowhere.
*/
} else {
/*
* Since there was no exact match, the chain argument
* needs to be pointed at the DNSSEC predecessor of
* the search name.
*
* First, point current to the node that stopped the
* search, and remove that node from the ancestor
* history.
*/
/*
* Attempted to follow a down pointer that was
* NULL, which means the searched for name was
* a subdomain of a terminal name in the tree.
* Since there are no existing subdomains to
* order against, the terminal name is the
* predecessor.
*/
chain->level_count);
} else {
/*
* Reached a point within a level tree that
* positively indicates the name is not
* present, but the stop node could be either
* less than the desired name (order > 0) or
* greater than the desired name (order < 0).
*
* If the stop node is less, it is not
* necessarily the predecessor. If the stop
* node has a down pointer, then the real
* predecessor is at the end of a level below
* (not necessarily the next level).
* Move down levels until the rightmost node
* does not have a down pointer.
*
* When the stop node is greater, it is
* the successor. All the logic for finding
* the predecessor is handily encapsulated
* in dns_rbtnodechain_prev. In the event
* that the search name is less than anything
* else in the tree, the chain is reset.
* XXX DCL What is the best way for the caller
* to know that the search name has
* no predecessor?
*/
if (order > 0) {
result2 =
if (result2 != ISC_R_SUCCESS)
} else
/*
* Ah, the pure and simple
* case. The stop node is the
* predecessor.
*/
} else {
NULL,
NULL);
if (result2 == ISC_R_SUCCESS ||
; /* Nothing. */
else if (result2 == ISC_R_NOMORE)
/*
* There is no predecessor.
*/
else
}
}
}
}
if (chain == &localchain)
return (result);
}
/*
* Get the data pointer associated with 'name'.
*/
else
return (result);
}
/*
* Delete a name from the tree of trees.
*/
/*
* First, find the node.
*
* When searching, the name might not have an exact match:
* elements of a tree, which would make layer 1 a single
* node tree of "b.a.com" and layer 2 a three node tree of
* a, b, and c. Deleting a.com would find only a partial depth
* match in the first layer. Should it be a requirement that
* that the name to be deleted have data? For now, it is.
*
* ->dirty, ->locknum and ->references are ignored; they are
* solely the province of rbtdb.c.
*/
if (result == ISC_R_SUCCESS)
else
else if (result == DNS_R_PARTIALMATCH)
return (result);
}
/*
* Remove a node from the tree of trees and rejoin any levels, if possible.
*/
{
if (recurse)
else {
rbt->deleter_arg);
/*
* This node cannot be removed because it
* points down to a level that has more than
* one node, so it must continue to serve
* as the root for that level. All that
* could be done was to blast its data.
*/
return (ISC_R_SUCCESS);
/*
* There is a down pointer to a level with a single
* item. That item's name can be joined with the name
* on this level.
*/
return (result);
}
}
/*
* Note the node that points to the level of the node that is being
* deleted. If the deleted node is the top level, parent will be set
* to NULL.
*/
; /* Nothing. */
/*
* This node now has no down pointer (either because it didn't
* have one to start, or because it was recursively removed).
* So now the node needs to be removed from this level.
*/
/*
* If there is one node left on this level, and the node one level up
* that points down to here has no data, then those two nodes can be
* merged. The focus for exploring this criteria is shifted up one
* level, to the node that points to the level of the deleted node.
*/
else
return (result);
}
void
}
static isc_result_t
unsigned int labels;
/*
* Allocate space for the node structure, the name, and the offsets.
*/
return (ISC_R_NOMEMORY);
FINDCALLBACK(node) = 0;
/*
* The following is stored to make reconstructing a name from the
* stored value in the node easy: the length of the name, the number
* of labels, whether the name is absolute or not, the name itself,
* and the name's offsets table.
*
* XXX RTH
* The offsets table could be made smaller by eliminating the
* first offset, which is always 0. This requires changes to
*/
return (ISC_R_SUCCESS);
}
static isc_result_t
if (result != ISC_R_SUCCESS)
return (result);
/*
* Check whether the space needed for the joined names can
* fit within the space already available in the down node,
* so that any external references to the down node are preserved.
*
* Currently this is not very meaningful since preservation
* of the address of the down node cannot be guaranteed.
*/
else {
}
if (result == ISC_R_SUCCESS) {
/*
* Fix the pointers to the original node.
*/
else
else
else
}
}
return (result);
}
static inline void
} else {
else
}
}
static inline void
} else {
else
}
}
/*
* This is the real workhorse of the insertion code, because it does the
*/
static void
{
unsigned int depth;
/*
* First node of a level.
*/
return;
}
if (order < 0) {
} else {
}
node = grandparent;
depth -= 2;
} else {
}
}
} else {
node = grandparent;
depth -= 2;
} else {
}
}
}
}
return;
}
/*
* This is the real workhorse of the deletion code, because it does the
*/
static void
/*
* Verify that the parent history is (apparently) correct.
*/
/*
* This is the only item in the tree.
*/
return;
}
} else
/*
* This node has one child, on the right.
*/
/*
* This node has one child, on the left.
*/
else {
/*
* This node has two children, so it cannot be directly
* deleted. Find its immediate in-order successor and
* move it to this location, then do the deletion at the
* old site of the successor.
*/
/*
* The successor cannot possibly have a left child;
* if there is any child, it is on the right.
*/
/* Swap the two nodes; it would be simpler to just replace
* the value being deleted with that of the successor,
* but this rigamarole is done so the caller has complete
* control over the pointers (and memory allocation) of
* all of nodes. If just the key value were removed from
* the tree, the pointer to the node would be unchanged.
*/
/*
* First, put the successor in the tree location of the
* node to be deleted. Save its existing tree pointer
* information, which will be needed when linking up
* delete to the successor's old location.
*/
} else
else
/*
* Now relink the node to be deleted into the
* successor's previous tree location. PARENT(tmp)
* is the successor's original parent.
*/
/*
* Node being deleted was successor's parent.
*/
} else {
}
/*
* Original location of successor node has no left.
*/
}
/*
* Remove the node by removing the links from its parent.
*/
else
} else {
/*
* This is the root being deleted, and at this point
* it is known to have just one child.
*/
}
/*
* Fix color violations.
*/
}
} else {
}
}
} else {
/*
* Child is parent's right child.
* Everything is doen the same as above,
* except mirrored.
*/
}
} else {
}
}
}
}
}
}
/*
* This should only be used on the root of a tree, because no color fixup
* is done at all.
*
* NOTE: No root pointer maintenance is done, because the function is only
* used for two cases:
* + deleting everything DOWN from a node that is itself being deleted, and
* + deleting the entire tree of trees from dns_rbt_destroy.
* In each case, the root pointer is no longer relevant, so there
* is no need for a root parameter to this function.
*
* If the function is ever intended to be used to delete something where
* a pointer needs to be told that this tree no longer exists,
* this function would need to adjusted accordingly.
*/
static void
return;
}
static void
dns_rbt_indent(int depth) {
int i;
for (i = 0; i < depth; i++)
putchar('\t');
}
static void
isc_region_t r;
char buffer[1024];
dns_name_fromregion(&name, &r);
/*
* ISC_FALSE means absolute names have the final dot added.
*/
}
static void
if (parent) {
printf(" from ");
}
printf(" (BAD parent pointer! -> ");
else
printf("NULL");
printf(")");
}
printf(")\n");
depth++;
printf("++ BEG down from ");
printf("\n");
printf("-- END down from ");
printf("\n");
}
} else
printf("NULL\n");
}
void
}
/*
* Chain Functions
*/
void
/*
* Initialize 'chain'.
*/
chain->ancestor_count = 0;
chain->level_count = 0;
chain->level_matches = 0;
}
{
return (ISC_R_NOTFOUND);
if (chain->level_count == 0) {
/*
* Names in the top level tree are all absolute.
* Always make 'name' relative.
*/
/*
* This is cheaper than dns_name_getlabelsequence().
*/
}
}
if (chain->level_count > 0)
else
}
return (result);
}
{
predecessor = NULL;
}
} else {
break;
}
}
}
if (predecessor != NULL) {
/*
* The predecessor is really down at least one level.
* Go down and as far right as possible, and repeat
* as long as the rightmost node has a down pointer.
*/
do {
/*
* XXX DCL Need to do something about origins
* here. See whether to go down, and if so
* whether it is truly what Bob calls a
* new origin.
*/
/* XXX DCL duplicated from above; clever
* way to unduplicate? */
}
/* XXX DCL probably needs work on the concept */
}
} else if (chain->level_count > 0) {
/*
* Got to the root of this level without having traversed
* any right links. Ascend the tree one level.
*/
chain->ancestor_count > 0);
chain->ancestor_count--;
/* XXX DCL probably needs work on the concept */
/*
* Don't declare an origin change when the new origin is "."
* at the top level tree, because "." is declared as the origin
* for the second level tree.
*/
}
if (predecessor != NULL) {
if (new_origin) {
NULL);
if (result == ISC_R_SUCCESS)
} else
NULL);
} else
return (result);
}
{
/*
* Don't declare an origin change when the new origin is "."
* at the second level tree, because "." is already declared
* as the origin for the top level tree.
*/
if (chain->level_count > 0 ||
}
/*
* The successor is up, either in this level or a previous one.
* Head back toward the root of the tree, looking for any path
* that was via a left link; the successor is the node that has
* that left link. In the event the root of the level is
* reached without having traversed any left links, ascend one
* level and look for either a right link off the point of
* ascent, or search for a left link upward again, repeating
* ascents until either case is true.
*/
do {
NULL) {
current =
break;
}
}
/*
* Reached the root without having traversed
* any left pointers, so this level is done.
*/
if (chain->level_count == 0)
break;
chain->ancestor_count--;
break;
}
}
}
}
/*
* It is not necessary to use dns_rbtnodechain_current like
* the other functions because this function will never
* find a node in the topmost level. This is because the
* root level will never be more than one name, and everything
* in the megatree is a successor to that node, down at
* the second level or below.
*/
if (new_origin) {
if (result == ISC_R_SUCCESS)
} else
} else
return (result);
}
{
if (result == ISC_R_SUCCESS)
return (result);
}
{
if (result != ISC_R_SUCCESS)
return (result);
if (result == ISC_R_SUCCESS)
return (result);
}
void
/*
* Free any dynamic storage associated with 'chain', and then
* reinitialize 'chain'.
*/
chain->ancestor_count = 0;
chain->level_count = 0;
chain->level_matches = 0;
}
void
/*
* Free any dynamic storage associated with 'chain', and then
* invalidate 'chain'.
*/
}