1    | /***************************************
2    |   $Revision: 1.17 $
3    | 
4    |   Radix payload (rp) - user level functions for storing data in radix trees
5    | 
6    |   rp_search = search the loaded radix trees using an ascii key
7    | 
8    |               Motto: "And all that for inetnums..."
9    | 
10   |   Status: NOT REVIEWED, TESTED
11   |   
12   |   Design and implementation by: Marek Bukowy
13   |   
14   |   ******************/ /******************
15   |   Copyright (c) 1999,2000,2001,2002               RIPE NCC
16   |  
17   |   All Rights Reserved
18   |   
19   |   Permission to use, copy, modify, and distribute this software and its
20   |   documentation for any purpose and without fee is hereby granted,
21   |   provided that the above copyright notice appear in all copies and that
22   |   both that copyright notice and this permission notice appear in
23   |   supporting documentation, and that the name of the author not be
24   |   used in advertising or publicity pertaining to distribution of the
25   |   software without specific, written prior permission.
26   |   
27   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
28   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
29   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
30   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
31   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33   |   ***************************************/
34   | 
35   | #include "rip.h"
36   | 
37   | static
38   | void
39   | rp_exclude_datlink(GList    **datlist, GList    *element)
40   | {
41   |   /* remove element from list(becomes a self-consistent list) */
42   |   *datlist = g_list_remove_link(*datlist, element);
43   |   
44   |   /* free it and the payload */
45   |   wr_clear_list( &element );
46   | }
47   | 
48   | 
49   | /**************************************************************************/
50   | /*+++++++++++
51   |    helper: 
52   |    this routine goes through the list of prefixes and performs a bin_search
53   |    on each of them; attaches the results to datlist.
54   | +++++++++++*/
55   | static
56   | er_ret_t
57   | rp_preflist_search (
58   |                     rx_srch_mt search_mode, 
59   |                     int par_a,
60   |                     int par_b,
61   |                     rx_tree_t  *mytree,
62   |                     GList    **preflist,
63   |                     GList    **datlist
64   |                     )
65   | 
66   | { 
67   |   char   prefstr[IP_PREFSTR_MAX];
68   |   GList   *qitem;
69   |   ip_prefix_t *querypref;
70   |   er_ret_t err;
71   |   
72   |   for( qitem = g_list_first(*preflist);
73   |        qitem != NULL;
74   |        qitem = g_list_next(qitem)) {
75   |     
76   |     querypref = qitem->data;
77   |     
78   |     if( IP_pref_b2a( querypref, prefstr, IP_PREFSTR_MAX) != IP_OK ) {
79   |       die;
80   |     }
81   |     ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
82   |               "rx_preflist_search: mode %d (%s) (par %d) for %s", 
83   |               search_mode, RX_text_srch_mode(search_mode), par_a, prefstr);
84   |     
85   |     if (mytree->num_nodes > 0) {
86   |       err = RX_bin_search( search_mode, par_a, par_b, mytree, querypref, 
87   |                    datlist, RX_ANS_ALL);
88   |       if( err != RX_OK ) {
89   |         return err;
90   |       }
91   |     }
92   |   }
93   |   
94   |   return RX_OK;
95   | }
96   | 
97   | /*++++
98   |   this is a helper: goes through a datlist and returns the smallest
99   |   size of a range
100  | 
101  |   works for IPv4 only
102  |   +++*/
103  | static
104  | ip_rangesize_t
105  | rp_find_smallest_span( GList *datlist ) {
106  |   ip_rangesize_t  min_span, span;
107  |   GList *ditem;
108  | 
109  |   min_span = 0xffffffff; /* IPv4 only!!!!*/
110  | 
111  |     /* go through the list and find the shortest range.    */
112  |     for(ditem = g_list_first(datlist);
113  |         ditem != NULL;
114  |         ditem = g_list_next(ditem)) {
115  |       rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
116  |       
117  |       span = IP_rang_span( & refptr->leafptr->iprange);
118  |       
119  |       if( span < min_span ) {
120  |         min_span = span;
121  |       }
122  |     }
123  |     ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
124  |               "rp_find_smallest_span: minimal span is %d", min_span);
125  | 
126  |     return min_span;
127  | }
128  | 
129  | 
130  | 
131  | /* helper for the inetnum/exless search - for this one a hash of pairs
132  | (leafptr,occurences) must be maintained.
133  | 
134  | This routine increments the counter for a leafptr, creating a new
135  | pair if this leafptr was not referenced before.
136  | 
137  | */
138  | static
139  | int rp_leaf_occ_inc(GHashTable *hash, rx_dataleaf_t *leafptr)
140  | {
141  |   /* one little trick: store the number of occurences 
142  |      as cast (void *) */
143  |   int val;
144  |   
145  |   val = (int) g_hash_table_lookup(hash, leafptr);
146  |   /* 0 if it's not known yet. anyway: put it in the hash (value==key) */
147  |   
148  |   g_hash_table_insert(hash, leafptr, (void *) ++val); 
149  |   
150  |   return val;
151  | }
152  | 
153  | /* exclude exact match - not to be merged with preselction :-( */
154  | static void
155  | rp_exclude_exact_match( GList **datlist, ip_range_t *testrang) 
156  | {
157  |   GList *ditem, *newitem;
158  |   
159  |   ditem = g_list_first(*datlist);
160  | 
161  |   while( ditem != NULL ) {
162  |     rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
163  | 
164  |     newitem = g_list_next(ditem);
165  |     
166  |     if( memcmp( & refptr->leafptr->iprange, 
167  | 		testrang, sizeof(ip_range_t)) == 0 ) {  
168  |       rp_exclude_datlink(datlist, ditem);
169  |       ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
170  | 		"process_datlist: discarded an exact match");
171  |     }
172  |     ditem = newitem;
173  |   } /* while */
174  | }
175  | 
176  | static int
177  | rp_find_longest_prefix(GList **datlist)
178  | {
179  |   GList *ditem;
180  |   int max_pref=0;
181  | 
182  |   for(ditem = g_list_first(*datlist);
183  |       ditem != NULL;
184  |       ditem = g_list_next(ditem)) {
185  |     rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
186  |     
187  |     if( refptr->leafptr->preflen > max_pref ) {
188  |       max_pref = refptr->leafptr->preflen;
189  |     }
190  |   }
191  |   
192  |   return max_pref;
193  | }
194  | 
195  | 
196  | /*+ rp_asc_process_datlist() - helper for RP_asc_search()
197  |   
198  |   fetches the copies of objects from the radix tree into datlist
199  | 
200  |      ASSUMES LOCKED TREE
201  | 
202  |      the behaviour for a default inetnum (range) query is: 
203  |        do an exact match; 
204  |        if it fails, do an exless match on the encompassing prefix
205  |      for routes(prefixes):
206  |        do an exless match
207  |      
208  |      So if it's the default search mode on an inetnum tree,
209  |      and the key is a range, 
210  |      then an exact search is performed on one of the composing prefixes.
211  | 
212  |      Then the resulting data leaves are checked for exact matching with 
213  |      the range queried for.
214  |      Any dataleaves that do not match are discarded, and if none are left,
215  |      the procedure falls back to searching for the encompassing prefix.
216  |      (calculated in the smart_conv routine). 
217  |      Add the dataleaf copies to the list of answers, 
218  |      taking span into account 
219  | +*/
220  | static
221  | er_ret_t
222  | rp_asc_process_datlist(
223  | 		       rx_srch_mt search_mode,
224  | 		       int        par_a,
225  | 		       rx_fam_t   fam_id,
226  | 		       int        prefnumber,
227  | 		       GList    **datlist,
228  | 		       ip_range_t *testrang,
229  | 		       int       *hits
230  | 		       )
231  | {
232  |   ip_rangesize_t  min_span=0, span;
233  |   int use_span = 0;
234  |   int max_pref = -1;
235  |   GList    *ditem, *newitem;
236  |   GHashTable *lohash = g_hash_table_new(NULL, NULL);
237  |  
238  |   /* in MORE and LESS(1) search exact match must not be displayed */
239  |   if ( search_mode == RX_SRCH_MORE 
240  |        || ( search_mode == RX_SRCH_LESS && par_a == 1 ) ) {
241  |     rp_exclude_exact_match(datlist, testrang);
242  |   }
243  |   
244  |   /* Preselection moved to processing, only span calculation done here *
245  |    * 
246  |     
247  |    EXLESS and LESS(1) search: the smallest span must be found,
248  |    but if the less spec node is not the same for all composing prefixes,
249  |    it means it's not really this one.
250  |    
251  |    we check that by the number of references to this node is less than
252  |    the number of composing prefixes
253  |    
254  |    We do the same for the less specific search - a node must be less 
255  |    specific to all prefixes.
256  |    
257  |    if the number of references is  not enough, then return no hits,
258  |    another try will be made, this time with one, encompassing prefix.
259  |   */
260  |   
261  |   if ( (search_mode == RX_SRCH_EXLESS )   
262  |        || ( search_mode == RX_SRCH_LESS && par_a == 1 ) )  {
263  |     /* span works only for IP_V4. We use it only for inetnums,
264  |        although RT/v4 would work too */
265  |     if( testrang->begin.space == IP_V4 &&
266  | 	fam_id == RX_FAM_IN ) {
267  |       min_span = rp_find_smallest_span(*datlist);
268  |       use_span = 1;
269  |     }
270  |     else {
271  |       /* in IPv6 and RT trees in general,  we can obtain the same
272  | 	 result by selecting the longest prefix */
273  |       max_pref = rp_find_longest_prefix(datlist);
274  |     }
275  |   }
276  |   
277  |   /* Process the dataleaf copies and add to the list of answers. */  
278  |   ditem = g_list_first(*datlist);
279  |   while(ditem != NULL) {
280  |     rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
281  |     int exclude = 0;
282  |     
283  |     if(search_mode == RX_SRCH_EXLESS || search_mode == RX_SRCH_LESS ) {
284  |       
285  |       /* min_span defined <=> EXLESS or LESS(1) search of INETNUMS: 
286  | 	 the smallest span must be returned */
287  |       if( !exclude && use_span 
288  | 	  && (span = IP_rang_span( &refptr->leafptr->iprange))!=min_span) {
289  | 	ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
290  | 		  "process_datlist: (EX)LESS: discarded object with span %d", span);
291  | 	exclude = 1;
292  |       }
293  |       /* max_pref defined <=> EXLESS search of INETNUMS or LESS(1) of RT:
294  |        */
295  |       if( !exclude && max_pref >= 0
296  | 	  && refptr->leafptr->preflen < max_pref ) {
297  | 	ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
298  | 		  "process_datlist: (EX)LESS: discarded object with preflen %d", 
299  | 		  refptr->leafptr->preflen);
300  | 	exclude = 1;
301  |       }
302  | 
303  |       /* number of occurences */
304  |       /* XXX this will go when the old algorithm goes */
305  |       if( !exclude 
306  | 	  && prefnumber > 1 ) { /* do not check if all will be approved */
307  | 	
308  | 	if( rp_leaf_occ_inc(lohash, refptr->leafptr) < prefnumber ) {
309  | 	  ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
310  | 		    "process_datlist: (EX)LESS: leafptr %x not enough",refptr->leafptr);
311  | 	  exclude = 1;
312  | 	} 
313  | 	else {
314  | 	  ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
315  | 		    "process_datlist: (EX)LESS: leafptr %x GOOD enough",refptr->leafptr);
316  | 	}
317  |       }
318  |     } 
319  |     else if( search_mode ==  RX_SRCH_EXACT ) {
320  |       /* EXACT search - discard if the range does not match */
321  |       if( memcmp( & refptr->leafptr->iprange, 
322  | 		  testrang, sizeof(ip_range_t)) != 0) {
323  | 	
324  | 	ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
325  | 		  "process_datlist: EXACT; discarded a mismatch");
326  | 	exclude = 1;
327  |       } /*  EXACT match */
328  |     } 
329  |     else if( search_mode ==  RX_SRCH_MORE ) {
330  |       /* MORE: exclude if not fully contained in the search term */
331  |       if( ! (IP_addr_in_rang(&refptr->leafptr->iprange.begin, testrang )
332  | 	  && IP_addr_in_rang(&refptr->leafptr->iprange.end, testrang ))) {
333  | 	ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
334  | 		  "process_datlist: MORE; discarded a not-fully contained one");
335  | 	exclude = 1;
336  |       }
337  |     }
338  |     
339  |     
340  |     /* get next item now, before the current gets deleted */
341  |     newitem = g_list_next(ditem);
342  |     if( exclude ) {
343  |       /* get rid of it */
344  |       rp_exclude_datlink(datlist, ditem);
345  |     } 
346  |     else {
347  |       /* OK, so we ACCEPT these results*/
348  |       /* uniqueness ensured in copy_results */
349  |       (*hits)++;
350  |     }
351  |     ditem = newitem;
352  |   } /* while ditem */ 
353  |   
354  |   /* wr_clear_list(&lolist); */
355  |   g_hash_table_destroy(lohash);
356  |   return RX_OK;
357  | }      
358  | 
359  | /**************************************************************************/
360  | 
361  | /*+ appends the element pointed to by datref to finallist +*/
362  | static
363  | er_ret_t
364  | rp_asc_append_datref(rx_datref_t *refptr, GList **finallist)
365  | {
366  |   rx_datcpy_t *datcpy;
367  |   void *dataptr;
368  | 
369  |     /* OK, so we ACCEPT this result. Copy it.*/
370  | 
371  |     datcpy = (rx_datcpy_t *)UT_calloc(1, sizeof(rx_datcpy_t));
372  |     
373  |     datcpy->leafcpy = *(refptr->leafptr);
374  |     
375  |     /* copy the immediate data too. Set the ptr.*/
376  |     
377  |     dataptr = UT_calloc(1, refptr->leafptr->data_len);
378  |     memcpy(dataptr, refptr->leafptr->data_ptr, refptr->leafptr->data_len);
379  |     
380  |     datcpy->leafcpy.data_ptr = dataptr;
381  | 
382  |     *finallist = g_list_prepend(*finallist, datcpy);
383  | 
384  |     /* XXX this wouldn't work in access_control */
385  |     ER_dbg_va(FAC_RP, ASP_RP_SRCH_DATA,
386  | 	      "rp_asc_append 'ed: %s", dataptr);
387  | 
388  |     return RX_OK;
389  | }
390  | 
391  | /*+ goes through datlist (list of references "datref") and add copies of 
392  | leaves referenced to the finallist 
393  | 
394  | maintains its own uniqhash which holds pointers to copied dataleaves.
395  | 
396  | modifies: finallist
397  | 
398  | returns: error from wr_malloc
399  | 
400  | +*/
401  | static
402  | er_ret_t
403  | rp_srch_copyresults(GList *datlist,
404  | 		    GList **finallist,
405  | 		    int maxcount)
406  | {
407  |   er_ret_t err;
408  |   GList    *ditem;
409  |   GHashTable *uniqhash = g_hash_table_new(NULL, NULL); /* defaults */
410  |   int count = 0;
411  | 
412  |   ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, "srch_copyresults");
413  | 
414  |   /*  copy dataleaves pointed to by entries from the datlist
415  |       only once (check uniqueness in the hash table) */
416  |   for(ditem = g_list_first(datlist);
417  |       ditem != NULL;
418  |       ditem = g_list_next(ditem)) {
419  |     rx_datref_t   *refptr = (rx_datref_t *) (ditem->data);
420  |     rx_dataleaf_t *ansptr = refptr->leafptr;
421  | 
422  |     /* search for every ansptr (dataleaf pointer) in uniqhash */
423  |     if( g_hash_table_lookup(uniqhash, ansptr) == NULL ) {
424  |       
425  |       /* it's not known yet. OK: put it in the hash (value==key) */
426  |       g_hash_table_insert(uniqhash, ansptr, ansptr); 
427  |       
428  |       /* and copy the dataleaf */
429  |       if( !NOERR(err = rp_asc_append_datref(refptr, finallist)) ) {
430  | 	return err;
431  |       }
432  |     }
433  | 
434  |     /* check the limit on number of objects if defined ( >0)  */
435  |     count++;
436  |     if( maxcount > 0 && count > maxcount ) {
437  |       break;
438  |     }
439  | 
440  |   } /*  foreach (datlist) */
441  |     
442  |   g_hash_table_destroy(uniqhash); /* elements are still linked to through datlist */
443  | 
444  |   return RP_OK;
445  | }
446  | 
447  | static 
448  | void
449  | rp_begend_preselection(GList **datlist, rx_fam_t fam_id, ip_range_t *testrang) 
450  | {
451  |   GList *ditem, *newitem; 
452  | 
453  |   ditem = g_list_first(*datlist);
454  | 
455  |   while( ditem != NULL ) {
456  |     rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
457  |     newitem = g_list_next(ditem);
458  | 
459  |     /* the test is indentical for route & inetnum trees */
460  |     if( IP_addr_in_rang(&testrang->end, &refptr->leafptr->iprange) == 0 ) {
461  |       
462  |       ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
463  | 		"process_datlist: discarded an uncovering leafptr %x",
464  | 		refptr->leafptr);
465  |       rp_exclude_datlink(datlist, ditem);
466  |     }
467  |     ditem = newitem;
468  |   } /* while */
469  | }
470  | 
471  | /*+++++++++++++++
472  |   search.
473  | 
474  |   2 approaches: 
475  | 
476  |   1. (most modes): look up all less specifics of beginning and end of range,
477  |   compare/select/etc.
478  | 
479  |   2. More spec mode: break up the query range into prefixes, [erform a search
480  |   for each of them. Add all results together.
481  | 
482  |   translates a query into a binary prefix (or prefixes, if range).
483  |   for registry+space (or if they are zero, for all
484  |   registries/spaces)
485  |   finds tree 
486  |   calls RX_bin_search (returning node copies).
487  |   will not put duplicate entries (composed inetnums).
488  |   returns some sort of error code :-) 
489  |   
490  |   Cuts the number of answers from RX_bin_search down to max_count,
491  |   but since some of the answers may have been "normalized" in the
492  |   underlying functions (multiple occurences removed), 
493  |   the result is _at_most_ max_count.
494  |   
495  |   appends to a given list of data blocks (not nodes!)
496  | 
497  |   The EXLESS search on inetnum tree should return the shortest range 
498  |   that was found, by means of comparing span (size) of the range.
499  |   If there are more of size equal to the smallest one, they are also
500  |   returned.
501  | 
502  |   returns RX_OK or a code from an underlying function
503  | ++++++++++++*/
504  | er_ret_t
505  | RP_asc_search ( 
506  |                rx_srch_mt search_mode, 
507  |                int par_a,
508  |                int par_b,
509  |                char *key,     /*+ search term: (string) prefix/range/IP +*/
510  |                rp_regid_t  reg_id,
511  | 	       rp_attr_t  attr,    /*+ extra tree id (within the same reg/spc/fam +*/
512  |                GList **finallist,    /*+ answers go here, please +*/
513  |                int    max_count    /*+ max # of answers. RX_ALLANS == unlimited +*/
514  |                )
515  | { 
516  |   GList    *preflist = NULL;
517  |   GList    *datlist = NULL;
518  |   er_ret_t   err; 
519  |   ip_range_t  testrang;
520  |   int        locked = 0;
521  |   ip_keytype_t key_type;
522  |   ip_space_t   spc_id;
523  |   rx_fam_t   fam_id = RP_attr2fam( attr );
524  |   rx_tree_t   *mytree;
525  |   int hits=0;
526  |   ip_prefix_t beginpref;
527  |   
528  | 
529  |   /*  abort on error (but unlock the tree) */  
530  |   ER_dbg_va(FAC_RP, ASP_RP_SRCH_GEN,
531  | 	    "RP_NEW_asc_search:  query %s : mode %d (%s) (par %d) for %s",
532  | 	    DF_get_attribute_name(attr),
533  | 	    search_mode, RX_text_srch_mode(search_mode), par_a, key);
534  | 
535  |   
536  |   /* parse the key into a prefix list */
537  |   if( ( err = IP_smart_conv(key, 0, 0,
538  | 			    &preflist, IP_EXPN, &key_type)) != IP_OK ) {
539  |     /* operational trouble (UT_*) or invalid key (IP_INVARG)*/
540  |     return err; 
541  |   }
542  | 
543  |   /* set the test values */
544  |   IP_smart_range(key, &testrang, IP_EXPN, &key_type);
545  |   
546  |   /* find the tree */
547  |   /* I took out the surrounding "if" because it is always taken when 
548  |      we get to this point, and it causes compiler warnings otherwise - shane */
549  |   /*if( NOERR(err) ) {*/
550  |     spc_id = IP_pref_b2_space( g_list_first(preflist)->data );
551  |     if( ! NOERR(err = RP_tree_get( &mytree, reg_id, spc_id, attr ))) {
552  |       return err;
553  |     }
554  |   /*}*/
555  |   /* the point of no return: now we lock the tree. From here, even if errors
556  |      occur, we still go through all procedure to unlock the tree at the end */
557  |   
558  |   /* lock the tree */
559  |   TH_acquire_read_lockw( &(mytree->rwlock) );
560  |   locked = 1;
561  | 
562  |   /* Collection: this procedure is used for some search_modes only */
563  |   if(    search_mode == RX_SRCH_EXLESS 
564  |       || search_mode == RX_SRCH_LESS 
565  |       || search_mode == RX_SRCH_EXACT )  {
566  | 
567  |     /* 1. compose a /32(/128) prefix for beginning of range */
568  |     beginpref.ip = testrang.begin;
569  |     beginpref.bits = IP_sizebits(spc_id);
570  |     
571  |     /* 2. dataleaves collection: look up the beginning prefix in LESS(255) mode */
572  |     if( NOERR(err) ) {
573  |       err = RX_bin_search( RX_SRCH_LESS, 255, 0, mytree, &beginpref, 
574  | 			   &datlist, RX_ANS_ALL);
575  |     }
576  |     
577  |     /* 3. preselection: exclude those that do not include end of range 
578  |      */
579  |     if( NOERR(err) ) {
580  |       rp_begend_preselection(&datlist, fam_id, &testrang);
581  |     }
582  | 
583  |   } /* if exless|less|exact */
584  |   else {
585  |     /* MORE */
586  | 
587  |     /* standard collection using the traditional method: 
588  |        repeat the search for all prefixes and join results */
589  | 
590  |     if( NOERR(err) ) {
591  |       err = rp_preflist_search ( search_mode, par_a, par_b, 
592  | 				 mytree, &preflist, &datlist);
593  |     }
594  |   } /* collection */
595  | 
596  |   ER_dbg_va(FAC_RP, ASP_RP_SRCH_GEN,
597  | 	    "RP_NEW_asc_search: collected %d references ",
598  | 	    g_list_length(datlist));
599  | 
600  | 
601  |   /* 5. processing - using the same processing function */
602  |   if( NOERR(err) ) {
603  |     err = rp_asc_process_datlist( search_mode, par_a, fam_id, 
604  | 				  1, /* one occurence is enough */
605  | 				  &datlist,  
606  | 				  &testrang,  &hits );
607  |   }
608  |   
609  |   /* 6. copy results */
610  |   if( NOERR(err) ) {
611  |     err = rp_srch_copyresults(datlist, finallist, max_count); /* and uniq */
612  |   }
613  | 
614  |   if( locked ) {
615  |     /* 100. unlock the tree */
616  |     TH_release_read_lockw( &(mytree->rwlock) );
617  |   }
618  | 
619  |   /* clean up */
620  |   wr_clear_list( &preflist ); 
621  |   wr_clear_list( &datlist );  
622  | 
623  |   /* NOTE if error occured, finallist may be partly filled in. */
624  |   return err;
625  | }
626  |