int insert_rec_2(long node_idx, rec_t *rec, long *parents, int parent_cnt, int *parent_is, int parent_i_cnt) { int i; node_t node; node.idx = node_idx; load_node(&node); for (i=0;i<node.rec_cnt;i++) { int cmped = rec_cmp(&node.recs[i], rec); if (cmped > 0) break; else if (cmped == 0) return -1; } int cur_i = i; if (node.child_cnt) { fseek(fp, node.idx*sizeof(node_t), SEEK_SET); fwrite(&node, sizeof(node), 1, fp); parents[-1] = node.idx; parent_is[-1] = cur_i; return insert_rec_2(node.childs[i], rec, parents-1, parent_cnt+1, parent_is-1, parent_i_cnt+1); } else { for (i=node.rec_cnt-1;i>=cur_i;i--) node.recs[i+1] = node.recs[i]; node.rec_cnt++; node.recs[cur_i] = *rec; fseek(fp, node.idx*sizeof(node_t), SEEK_SET); fwrite(&node, sizeof(node), 1, fp); insert_rec_adjust(node_idx, parents, parent_cnt, parent_is, parent_i_cnt); return 0; } }
/*---------------------------------------------------------------------------- * rumavl_node_find * * Returns a pointer to the node that matches "record". *--------------------------------------------------------------------------*/ RUMAVL_NODE *rumavl_node_find (RUMAVL *tree, const void *find, void **record) { RUMAVL_NODE *node; int ln; if (find == NULL || tree->root == NULL) goto fail; node = tree->root; for (;;){ if ((ln = rec_cmp(tree, find, NODE_REC(node))) == 0){ if (record != NULL) *record = NODE_REC(node); return node; } ln = LINK_NO(ln); if (node->thread[ln] > 0) break; node = node->link[ln]; } /* we didn't find the desired node */ fail: if (record != NULL) *record = NULL; return NULL; }
int search_rec(long node_idx, rec_t *rec) { int i; node_t node; node.idx = node_idx; load_node(&node); for (i=0;i<node.rec_cnt;i++) { int cmped = rec_cmp(&node.recs[i], rec); if (cmped > 0) { if (node.child_cnt) return search_rec(node.childs[i], rec); else return -1; } else if (cmped == 0) { *rec = node.recs[i]; return 0; } } if (node.child_cnt) return search_rec(node.childs[node.child_cnt-1], rec); else return -1; }
int remove_rec_2(long node_idx, rec_t *rec, long *parents, int parent_cnt, int *parent_is, int parent_i_cnt) { int i, j; node_t node; node.idx = node_idx; load_node(&node); for (i=0;i<node.rec_cnt;i++) { int cmped = rec_cmp(&node.recs[i], rec); if (cmped > 0) { if (node.child_cnt) { parents[-1] = node.idx; parent_is[-1] = i; return remove_rec_2(node.childs[i], rec, parents-1, parent_cnt+1, parent_is-1, parent_i_cnt+1); } else { return -1; } } else if (cmped == 0) { if (!node.child_cnt) { for (j=i+1;j<node.rec_cnt;j++) node.recs[j-1] = node.recs[j]; node.rec_cnt--; save_node(&node); remove_rec_adjust(node.idx, parents, parent_cnt, parent_is, parent_i_cnt); return 0; } else { int prev_i = i+1; long prev_suc = node.idx; node_t suc; suc.idx = node.childs[prev_i]; load_node(&suc); int new_parent_cnt = 0; int new_parent_i_cnt = 0; parents[--new_parent_cnt] = prev_suc; parent_is[--new_parent_i_cnt] = prev_i; while (suc.child_cnt) { prev_suc = suc.idx; prev_i = 0; suc.idx = suc.childs[prev_i]; load_node(&suc); parents[--new_parent_cnt] = prev_suc; parent_is[--new_parent_i_cnt] = prev_i; } rec_t _rec = suc.recs[0]; node.recs[i] = _rec; save_node(&node); return remove_rec_2(suc.idx, &_rec, parents+new_parent_cnt, parent_cnt-new_parent_cnt, parent_is+new_parent_i_cnt, parent_i_cnt-new_parent_i_cnt); } } } if (node.child_cnt) { parents[-1] = node.idx; parent_is[-1] = node.child_cnt-1; return remove_rec_2(node.childs[node.child_cnt-1], rec, parents-1, parent_cnt+1, parent_is-1, parent_i_cnt+1); } else return -1; }
int sweep ( int opcode, /* The operation to perform. */ char *file1, /* The search/source file. */ char *file2, /* The scratch file. (Required.) */ int recsep, /* Record seperator. */ int fldsep, /* Field seperator. */ int maxlen, /* Maximum length of record. */ char *fmatch[], /* Fields to match. */ char *usrbuf, /* User's copy of matching record. */ char *usrmatch[], /* Pointers to arguments. */ int (*chop)(char *, int, char **), /* Routine to seperate the fields. */ int (*compare)(char **, char **, int) /* Routine to compare records. */ ) { char **arg = NULL, /* Pointers to the fields. */ *realrec = NULL, /* A copy of what was actually read. */ *hackrec = NULL; /* A place to keep the fields. */ int cmpstat, /* Comparison status. */ retcode, /* Return code. */ action = 0, /* Record of what we have done. */ rectype, /* FIXED or VARIED record size. */ i, j; /* Looping variable. */ FILE *fp1, *fp2 = NULL; /* Pointers to file1 and file2. */ TR("Sweep: opcode=%d file1=(%s) file2=(%s)\n", opcode, file1, file2); TR(".. recsep=(%c) fldsep=(%c) maxlen=%d ", recsep, fldsep, maxlen); TR("usrbuf=%d usrmatch=%d chop=%d\n", usrbuf, usrmatch, chop); TR(".. usrbuf=(%s) compare=%d\n", usrbuf, compare, EMPTY); #ifdef TRACE TR(".. fmatch ..\n", EMPTY, EMPTY, EMPTY); for (i = 0; fmatch[i] != EMPTY; i++) TR("%d (%s)\n", i, fmatch[i], EMPTY); #endif if ((fp1 = fopen (file1, READ)) == NULL) { if (opcode == PUTNOW || (opcode & INSERT) > 0) { if ((fp1 = fopen (file1, APP)) == NULL) return (NOSOURCE); if (fclose (fp1) == ERROR) return (NOSOURCE); if ((fp1 = fopen (file1, READ)) == NULL) return (NOSOURCE); TR("Sweep: made a blank file1\n", EMPTY, EMPTY, EMPTY); opcode = PUTNOW; /* Since buffers are unneeded. */ } else return (NOSOURCE); } if ((opcode & VERIFY) == 0) { if ((fp2 = fopen (file2, READ)) != NULL) { if (fclose (fp2) == ERROR || unlink (file2) == ERROR) { if (fclose (fp1) == ERROR) return (RECURSE + LIVEDEST); return (LIVEDEST); } } if ((fp2 = fopen (file2, APP)) == NULL) { if (fclose (fp1) == ERROR) return (RECURSE + NODEST); return (NODEST); } action += OFILE2; /* Flag file2 as open. */ } action += OFILE1; /* Flag file1 as open. */ TR("Sweep: file1%s open\n", ((action & OFILE2) > 0) ? " and file2" : "", EMPTY, EMPTY); if (opcode == PUTNOW) goto begin; /* Because we need no allocations. */ /* At this point, we begin allocating the three areas that this routine * needs. realrec will contain a copy of what was really read. hackrec * will provide space to store its individual fields. arg will provide * space for the pointers the those fields. The general scheme used for * allocation and use of these areas is as follows: * * Given: usrbuf -- usrbuf -- * usrmatch usrmatch -- -- * --------------------------------------------------- * arg usrmatch/1 usrmatch/2 ALLOC3 ALLOC3 * --------------------------------------------------- * realrec ALLOC1 ALLOC1 usrbuf/3 ALLOC1 * --------------------------------------------------- * hackrec usrbuf/4 ALLOC2 ALLOC2 ALLOC2 * --------------------------------------------------- * * Note 1: On DELETE this array will hold pointers to the actual fields * of the DELETED record. On INSERT and REPLACE, it will hold pointers * into usrbuf which will be MEANINGLESS. On VERIFY, it will also hold * pointers, but they are to the matched record. On PUTNOW, this array * is unaffected. * * Note 2: On all operations, the pointers in this array will be useless * upon return. * * Note 3: On DELETE and VERIFY, this array will hold the actual record * deleted or matched. On INSERT or REPLACE, this record will hold the * actual record inserted or replaced (and is not used internally). * PUTNOW has no effect. * * Note 4: On DELETE and VERIFY, this array will hold the actual record * deleted or matched. On INSERT and REPLACE, this array will hold the * actual record inserted or replaced (in which case, the specification * of usrmatch will put only junk into that array, as in note 1). This * area is not used internally for an INSERT or REPLACE. This array is * not affected by PUTNOW. */ if (maxlen <= 0) { retcode = BADSIZE; goto bye; } retcode = NOSPACE; /* All errors will be for this. */ if (usrbuf == EMPTY) { /* We must allocate a buffer. */ TR("Sweep: no usrbuf\n", EMPTY, EMPTY, EMPTY); if ((realrec = (char *) malloc ((unsigned) maxlen)) == EMPTY) goto bye; action += ALLOC1; /* A buffer was allocated. */ TR("Sweep: allocated (1) realrec=%d\n", realrec, EMPTY, EMPTY); if ((hackrec = (char *) malloc ((unsigned) maxlen)) == EMPTY) goto bye; action += ALLOC2; TR("Sweep: allocated (2) hackrec=%d\n", hackrec, EMPTY, EMPTY); if (usrmatch == (char**) NULL) { /* User provided no field pointers. */ j = (maxlen / 2 + 1) * sizeof (char*); if ((arg = (char**) malloc ((unsigned) j)) == (char**) NULL) goto bye; action += ALLOC3; /* Pointer space was allocated. */ TR("Sweep: allocated (3) arg=%d\n", arg, EMPTY, EMPTY); } else arg = usrmatch; } else { /* A usrbuf area was specified */ TR("Sweep: usrbuf given\n", EMPTY, EMPTY, EMPTY); if (usrmatch == (char**) NULL) { TR("Sweep: no usrmatch\n", EMPTY, EMPTY, EMPTY); if ((opcode & REPLACE) > 0 || (opcode & INSERT) > 0) { /* Alas, we cannot use the area. */ if ((realrec = (char *) malloc ((unsigned) maxlen)) == EMPTY) goto bye; action += ALLOC1; TR("Sweep: allocated (4) realrec=%d\n", realrec, EMPTY, EMPTY); } else realrec = usrbuf; if ((hackrec = (char *) malloc ((unsigned) maxlen)) == EMPTY) goto bye; action += ALLOC2; TR("Sweep: allocated (5) hackrec=%d\n", hackrec, EMPTY, EMPTY); j = (maxlen / 2 + 1) * sizeof (char*); if ((arg = (char**) malloc ((unsigned) j)) == (char**) NULL) goto bye; action += ALLOC3; TR("Sweep: allocated (6) arg=%d\n", arg, EMPTY, EMPTY); } else { /* We have usrbuf and usrmatch. */ if ((opcode & REPLACE) > 0 || (opcode & INSERT) > 0) { if ((hackrec = (char *) malloc ((unsigned) maxlen)) == EMPTY) goto bye; action += ALLOC2; TR("Sweep: allocated (7) hackrec=%d\n", hackrec, EMPTY, EMPTY); } else hackrec = usrbuf; arg = usrmatch; if ((realrec = (char *) malloc ((unsigned) maxlen)) == EMPTY) goto bye; action += ALLOC1; TR("Sweep: allocated (8) realrec=%d\n", realrec, EMPTY, EMPTY); } } begin: if (opcode == PUTNOW) { /* Insert a message at beginning. */ if ((action & OFILE2) > 0) { fprintf (fp2, "%s%c", usrbuf, recsep); retcode = DONE; goto bye; } else { retcode = NOTPUT; goto bye; } } if (recsep == 0) rectype = FIXED; else rectype = VARIED; while ((i = getrec (fp1, realrec, recsep, rectype, maxlen)) == DONE) { /* Copy the buffer */ strncpy (hackrec, realrec, (unsigned) maxlen); /* Seperate the command into arguments. */ if (chop == (int (*)(char *, int, char **)) NULL) i = argchop (hackrec, fldsep, arg); else i = (*chop) (hackrec, fldsep, arg); if (i != DONE) break; /* Something funny in record. */ /* Now, determine the alphabetic status of this record. */ if (compare == (int (*)(char **, char **, int)) NULL) cmpstat = rec_cmp (arg, fmatch, recsep); else cmpstat = (*compare) (arg, fmatch, recsep); /* Now, decide to continue or not. */ if ((opcode & SEQUENTIAL) == 0 && cmpstat != SAME) continue; /* We continue if the search is not sequential and the record * did not match. */ if (cmpstat == SAME) { TR("Sweep: Enter SAME section\n", EMPTY, EMPTY, EMPTY); if ((opcode & INSERT) > 0) retcode = NOTNEW; else if ((opcode & VERIFY) > 0) retcode = FOUND; else { if ((opcode & REPLACE) > 0 && usrbuf != EMPTY) fprintf (fp2, "%s%c", usrbuf, recsep); /* And if this was a DELETE, nothing is printed. */ retcode = DONE; } goto bye; } else if (cmpstat == BEFORE) { TR("Sweep: Enter BEFORE section\n", EMPTY, EMPTY, EMPTY); /* Put the record into the "scratch" file. */ if ((opcode & VERIFY) == 0) fprintf (fp2, "%s%c", realrec, recsep); } else { /* cmpstat == AFTER. */ TR("Sweep: enter AFTER section\n", EMPTY, EMPTY, EMPTY); /* Match goes (or should have come) before this one. */ if (opcode == SEQINSERT) { if (usrbuf != EMPTY) fprintf (fp2, "%s%c", usrbuf, recsep); /* Put in the INSERTed one, then replace the old one. */ fprintf (fp2, "%s%c", realrec, recsep); retcode = DONE; } else /* We never get here on non-sequential searches. */ retcode = NOTFOUND; goto bye; } } /* End of while loop. */ TR("Sweep: end of record loop\n", EMPTY, EMPTY, EMPTY); if (i != FILE1EOF) { TR("Sweep: funny death\n", EMPTY, EMPTY, EMPTY); retcode = i; /* So user knows about bad data. */ goto bye; } if (fclose (fp1) == ERROR) retcode = NOCLOSE; action -= OFILE1; if (((opcode & VERIFY) | (opcode & REPLACE) | (opcode & DELETE)) > 0) { TR("Sweep: not there\n", EMPTY, EMPTY, EMPTY); if ((opcode & VERIFY) == 0) { if (fclose (fp2) == ERROR) retcode = RESETERR; action -= OFILE2; if (unlink (file2) == ERROR) { retcode = RESETERR; /* Should never happen? */ goto bye; } } retcode = NOTFOUND; goto bye; } if ((opcode & INSERT) > 0) { TR("Sweep: insert tail record\n", EMPTY, EMPTY, EMPTY); if (usrbuf != EMPTY) fprintf (fp2, "%s%c", usrbuf, recsep); retcode = DONE; } else retcode = ABEND; /* Should never happen? */ bye: TR("Sweep: closing retcode=%d action=%d\n", retcode, action, EMPTY); if (retcode == DONE) { /* Successful on a file update. */ if ((action & OFILE1) > 0 && (action & OFILE2) > 0) retcode = copyrest (fp1, fp2, EMPTY, BUFSIZE); } if ((action & OFILE1) > 0) { if (fclose (fp1) == ERROR) retcode = NOCLOSE; TR("Sweep: file1 closed\n", EMPTY, EMPTY, EMPTY); } if ((action & OFILE2) > 0) { if (fclose (fp2) == ERROR) retcode = NOCLOSE; TR("Sweep: file2 closed\n", EMPTY, EMPTY, EMPTY); } /* With the files closed, we now replace file1 with file2 (if needed). */ if (retcode == DONE) { if ((retcode = ftrans (MOVE, file2, file1, EMPTY, BUFSIZE)) != DONE) retcode += RECURSE; TR("Sweep: final transfer\n", EMPTY, EMPTY, EMPTY); } if ((action & ALLOC1) > 0) { TR("Sweep: free realrec\n", EMPTY, EMPTY, EMPTY); free (realrec); } if ((action & ALLOC2) > 0) { TR("Sweep: free hackrec\n", EMPTY, EMPTY, EMPTY); free (hackrec); } if ((action & ALLOC3) > 0) { TR("Sweep: free arg\n", EMPTY, EMPTY, EMPTY); free ((char*) arg); } return (retcode); }
/*---------------------------------------------------------------------------- * rumavl_delete - deletes a node. Beware! this function is the worst part of * the library. Think (and draw pictures) when you edit this function. *--------------------------------------------------------------------------*/ int rumavl_delete (RUMAVL *tree, const void *record) { RUMAVL_NODE **node, *tmpnode; RUMAVL_STACK *stack; int dir, ln; if (tree->root == NULL) /* tree is empty */ return RUMAVL_ERR_NOENT; stack = NULL; node = &tree->root; /* Find desired node */ while ((dir = rec_cmp(tree, record, NODE_REC(*node))) != 0){ if (stack_push(tree, &stack, node, dir) != 0) goto nomemout; if ((*node)->thread[LINK_NO(dir)] > 0){ /* desired node does not exist */ stack_destroy(tree, stack); return RUMAVL_ERR_NOENT; } node = &(*node)->link[LINK_NO(dir)]; } /* OK, we got the node to be deleted, now get confirmation from user */ if (tree->delcb != NULL && (ln = tree->delcb(tree, *node, NODE_REC(*node), tree->udata)) != 0){ stack_destroy(tree, stack); return ln; } if ((*node)->thread[LEFT] > 0){ if ((*node)->thread[RIGHT] > 0){ /* ooh look, we're a leaf */ tmpnode = *node; if (stack != NULL){ /* This node has a parent, which will need to take over a * thread from the node being deleted. First we work out * which (left/right) child we are of parent, then give * parent the respective thread. If the thread destination * points back to us (edge of tree thread), update it to * point to our parent. */ ln = LINK_NO(stack->dir); (*stack->node)->link[ln] = tmpnode->link[ln]; (*stack->node)->thread[ln] = tmpnode->thread[ln]; if ((*stack->node)->thread[ln] == 2) (*stack->node)->link[ln]->link[OTHER_LINK(ln)] = *stack->node; }else{ /* * the only time stack will == NULL is when we are * deleting the root of the tree. We already know that * this is a leaf, so we will be leaving the tree empty. */ tree->root = NULL; } node_destroy(tree, tmpnode); }else{ /* *node has only one child, and can be pruned by replacing * *node with its only child. This block of code and the next * should be identical, except that all directions and link * numbers are opposite. * * Let node being deleted = DELNODE for this comment. * DELNODE only has one child (the right child). The left * most descendant of DELNODE will have a thread (left thread) * pointing to DELNODE. This thread must be updated to point * to the node currently pointed to by DELNODE's left thread. * * DELNODE's left thread may point to the opposite edge of the * BST. In this case, the destination of the thread will have * a thread back to DELNODE. This will need to be updated to * point back to the leftmost descendant of DELNODE. */ tmpnode = *node; /* node being deleted */ *node = (*node)->link[RIGHT]; /* right child */ /* find left most descendant */ while ((*node)->thread[LEFT] == 0) node = &(*node)->link[LEFT]; /* inherit thread from node being deleted */ (*node)->link[LEFT] = tmpnode->link[LEFT]; (*node)->thread[LEFT] = tmpnode->thread[LEFT]; /* update reverse thread if necessary */ if ((*node)->thread[LEFT] == 2) (*node)->link[LEFT]->link[RIGHT] = *node; node_destroy(tree, tmpnode); } }else if ((*node)->thread[RIGHT] > 0){ /* see above */ tmpnode = *node; *node = (*node)->link[LEFT]; while ((*node)->thread[RIGHT] == 0) node = &(*node)->link[RIGHT]; (*node)->link[RIGHT] = tmpnode->link[RIGHT]; (*node)->thread[RIGHT] = tmpnode->thread[RIGHT]; if ((*node)->thread[RIGHT] == 2) (*node)->link[RIGHT]->link[LEFT] = *node; node_destroy(tree, tmpnode); }else{ /* Delete a node with children on both sides. We do this by replacing * the node to be deleted (delnode) with its inner most child * on the heavier side (repnode). This in place replacement is quicker * than the previously used method of rotating delnode until it is a * (semi) leaf. * * At this point node points to delnode's parent's link to delnode. */ RUMAVL_NODE *repnode, *parent; int outdir, outln; /* find heaviest subtree */ if ((*node)->balance > 0){ outdir = +1; /* outter direction */ dir = -1; /* inner direction */ outln = 1; /* outer link number */ ln = 0; /* inner link number */ }else{ outdir = -1; /* same as above, but opposite subtree */ dir = +1; outln = 0; ln = 1; } /* Add node to be deleted to the list of nodes to be rebalanced. * Rememer that the replacement node will actually be acted apon, * and that the replacement node should feel the effect of its own * move */ if (stack_push(tree, &stack, node, outdir) != 0) goto nomemout; parent = *node; repnode = parent->link[outln]; if (repnode->thread[ln] != 0){ /* repnode inherits delnode's lighter tree, and balance, and gets * balance readjusted below */ repnode->link[ln] = (*node)->link[ln]; repnode->thread[ln] = (*node)->thread[ln]; repnode->balance = (*node)->balance; }else{ /* Now we add delnodes direct child to the list of "to update". * We pass a pointer to delnode's link to its direct child to * stack_push(), but that pointer is invalid, because when * stack_update() tries to access the link, delnode would have * been destroyed. So, we remember the stack position at which * we passed the faulty pointer to stack_push, and update its * node pointer when we find repnode to point to repnodes * link on the same side */ RUMAVL_STACK *tmpstack; if (stack_push(tree, &stack, &parent->link[outln], dir) != 0) goto nomemout; tmpstack = stack; parent = repnode; repnode = repnode->link[ln]; /* move towards the innermost child of delnode */ while (repnode->thread[ln] == 0){ if (stack_push(tree, &stack, &parent->link[ln], dir) != 0) goto nomemout; parent = repnode; repnode = repnode->link[ln]; } if (repnode->thread[outln] == 0){ /* repnode's parent inherits repnodes only child */ parent->link[ln] = repnode->link[outln]; }else{ /* parent already has a link to repnode, but it must now be * marked as a thread */ parent->thread[ln] = 1; } repnode->link[0] = (*node)->link[0]; repnode->thread[0] = (*node)->thread[0]; repnode->link[1] = (*node)->link[1]; repnode->thread[1] = (*node)->thread[1]; repnode->balance = (*node)->balance; /* see comment above */ tmpstack->node = &repnode->link[outln]; } node_destroy(tree, *node); *node = repnode; /* innermost child in lighter tree has an invalid thread to delnode, * update it to point to repnode */ repnode = seq_next(repnode, dir); repnode->link[outln] = *node; } /* update parents' balances */ stack_update(tree, stack, -1); return 0; nomemout: stack_destroy(tree, stack); return RUMAVL_ERR_NOMEM; }
/*---------------------------------------------------------------------------- * rumavl_set - set a node, overwriting if necessary, or creating if the node * does not exist *--------------------------------------------------------------------------*/ int rumavl_set (RUMAVL *tree, const void *record) { RUMAVL_NODE **node, *tmp; RUMAVL_STACK *stack; int ln; if (tree->root == NULL){ /* This is the first node in the tree */ if ((tree->root = node_new(tree, record)) == NULL) return RUMAVL_ERR_NOMEM; tree->root->link[LEFT] = tree->root; tree->root->link[RIGHT] = tree->root; tree->root->thread[LEFT] = 2; tree->root->thread[RIGHT] = 2; return 0; } /* Since the tree is not empty, we must descend towards the nodes ideal * possition, and we may even find an existing node with the same record. * We keep a list parents for the eventual node position, because these * parents may become inbalanced by a new insertion. */ stack = NULL; node = &tree->root; for (;;){ if ((ln = rec_cmp(tree, record, NODE_REC(*node))) == 0){ /* OK, we found the exact node we wish to set, and we now * overwrite it. No change happens to the tree structure */ stack_destroy(tree, stack); if (tree->owcb != NULL && (ln = tree->owcb(tree, *node, NODE_REC(*node), record, tree->udata)) != 0){ return ln; } memcpy(NODE_REC(*node), record, tree->reclen); return 0; } /* *node is not the node we seek */ if (stack_push(tree, &stack, node, ln)){ stack_destroy(tree, stack); return RUMAVL_ERR_NOMEM; } ln = LINK_NO(ln); if ((*node)->thread[ln] > 0){ /* This is as close to the correct node as we can get. We will * now break and add the new node as a leaf */ break; } node = &(*node)->link[ln]; } /* we have reached a leaf, add new node here */ if ((tmp = node_new(tree, record)) == NULL){ stack_destroy(tree, stack); return RUMAVL_ERR_NOMEM; } /* new child inherits parent thread */ tmp->link[ln] = (*node)->link[ln]; tmp->thread[ln] = (*node)->thread[ln]; if (tmp->thread[ln] == 2) tmp->link[ln]->link[OTHER_LINK(ln)] = tmp; tmp->link[OTHER_LINK(ln)] = *node; tmp->thread[OTHER_LINK(ln)] = 1; (*node)->link[ln] = tmp; (*node)->thread[ln] = 0; /* all parentage is now one level heavier - balance where necessary */ stack_update(tree, stack, +1); return 0; }