modules/rp/rp_search.c
/* [<][>][^][v][top][bottom][index][help] */
FUNCTIONS
This source file includes following functions.
- rp_exclude_datlink
- rp_preflist_search
- rp_find_smallest_span
- rp_leaf_occ_inc
- rp_exclude_exact_match
- rp_find_longest_prefix
- rp_asc_process_datlist
- rp_mod_preflist
- rp_asc_append_datref
- rp_srch_copyresults
- rp_begend_preselection
- RP_asc_search
1 /***************************************
2 $Revision: 1.11 $
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 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
36 #include <rp.h>
37
38 static
39 void
40 rp_exclude_datlink(GList **datlist, GList *element)
/* [<][>][^][v][top][bottom][index][help] */
41 {
42 /* remove element from list(becomes a self-consistent list) */
43 *datlist = g_list_remove_link(*datlist, element);
44
45 /* free it and the payload */
46 wr_clear_list( &element );
47 }
48
49
50 /**************************************************************************/
51 /*+++++++++++
52 helper:
53 this routine goes through the list of prefixes and performs a bin_search
54 on each of them; attaches the results to datlist.
55 +++++++++++*/
56 static
57 er_ret_t
58 rp_preflist_search (
/* [<][>][^][v][top][bottom][index][help] */
59 rx_srch_mt search_mode,
60 int par_a,
61 int par_b,
62 rx_tree_t *mytree,
63 GList **preflist,
64 GList **datlist
65 )
66
67 {
68 char prefstr[IP_PREFSTR_MAX];
69 GList *qitem;
70 ip_prefix_t *querypref;
71 er_ret_t err;
72
73 for( qitem = g_list_first(*preflist);
74 qitem != NULL;
75 qitem = g_list_next(qitem)) {
76
77 querypref = qitem->data;
78
79 if( IP_pref_b2a( querypref, prefstr, IP_PREFSTR_MAX) != IP_OK ) {
80 die;
81 }
82 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
83 "rx_preflist_search: mode %d (%s) (par %d) for %s",
84 search_mode, RX_text_srch_mode(search_mode), par_a, prefstr);
85
86 if (mytree->num_nodes > 0) {
87 err = RX_bin_search( search_mode, par_a, par_b, mytree, querypref,
88 datlist, RX_ANS_ALL);
89 if( err != RX_OK ) {
90 return err;
91 }
92 }
93 }
94
95 return RX_OK;
96 }
97
98 /*++++
99 this is a helper: goes through a datlist and returns the smallest
100 size of a range
101
102 works for IPv4 only
103 +++*/
104 static
105 ip_rangesize_t
106 rp_find_smallest_span( GList *datlist ) {
/* [<][>][^][v][top][bottom][index][help] */
107 ip_rangesize_t min_span, span;
108 GList *ditem;
109
110 min_span = 0xffffffff; /* IPv4 only!!!!*/
111
112 /* go through the list and find the shortest range. */
113 for(ditem = g_list_first(datlist);
114 ditem != NULL;
115 ditem = g_list_next(ditem)) {
116 rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
117
118 span = IP_rang_span( & refptr->leafptr->iprange);
119
120 if( span < min_span ) {
121 min_span = span;
122 }
123 }
124 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
125 "rp_find_smallest_span: minimal span is %d", min_span);
126
127 return min_span;
128 }
129
130
131
132 /* helper for the inetnum/exless search - for this one a hash of pairs
133 (leafptr,occurences) must be maintained.
134
135 This routine increments the counter for a leafptr, creating a new
136 pair if this leafptr was not referenced before.
137
138 */
139 static
140 int rp_leaf_occ_inc(GHashTable *hash, rx_dataleaf_t *leafptr)
/* [<][>][^][v][top][bottom][index][help] */
141 {
142 /* one little trick: store the number of occurences
143 as cast (void *) */
144 int val;
145
146 val = (int) g_hash_table_lookup(hash, leafptr);
147 /* 0 if it's not known yet. anyway: put it in the hash (value==key) */
148
149 g_hash_table_insert(hash, leafptr, (void *) ++val);
150
151 return val;
152 }
153
154 /* exclude exact match - not to be merged with preselction :-( */
155 static void
156 rp_exclude_exact_match( GList **datlist, ip_range_t *testrang)
/* [<][>][^][v][top][bottom][index][help] */
157 {
158 GList *ditem, *newitem;
159
160 ditem = g_list_first(*datlist);
161
162 while( ditem != NULL ) {
163 rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
164
165 newitem = g_list_next(ditem);
166
167 if( memcmp( & refptr->leafptr->iprange,
168 testrang, sizeof(ip_range_t)) == 0 ) {
169 rp_exclude_datlink(datlist, ditem);
170 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
171 "process_datlist: discarded an exact match");
172 }
173 ditem = newitem;
174 } /* while */
175 }
176
177 static int
178 rp_find_longest_prefix(GList **datlist)
/* [<][>][^][v][top][bottom][index][help] */
179 {
180 GList *ditem;
181 int max_pref=0;
182
183 for(ditem = g_list_first(*datlist);
184 ditem != NULL;
185 ditem = g_list_next(ditem)) {
186 rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
187
188 if( refptr->leafptr->preflen > max_pref ) {
189 max_pref = refptr->leafptr->preflen;
190 }
191 }
192
193 return max_pref;
194 }
195
196
197 /*+ rp_asc_process_datlist() - helper for RP_asc_search()
198
199 fetches the copies of objects from the radix tree into datlist
200
201 ASSUMES LOCKED TREE
202
203 the behaviour for a default inetnum (range) query is:
204 do an exact match;
205 if it fails, do an exless match on the encompassing prefix
206 for routes(prefixes):
207 do an exless match
208
209 So if it's the default search mode on an inetnum tree,
210 and the key is a range,
211 then an exact search is performed on one of the composing prefixes.
212
213 Then the resulting data leaves are checked for exact matching with
214 the range queried for.
215 Any dataleaves that do not match are discarded, and if none are left,
216 the procedure falls back to searching for the encompassing prefix.
217 (calculated in the smart_conv routine).
218 Add the dataleaf copies to the list of answers,
219 taking span into account
220 +*/
221 static
222 er_ret_t
223 rp_asc_process_datlist(
/* [<][>][^][v][top][bottom][index][help] */
224 rx_srch_mt search_mode,
225 int par_a,
226 rx_fam_t fam_id,
227 int prefnumber,
228 GList **datlist,
229 ip_range_t *testrang,
230 int *hits
231 )
232 {
233 ip_rangesize_t min_span=0, span;
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 }
269 else {
270 /* in IPv6 and RT trees in general, we can obtain the same
271 result by selecting the longest prefix */
272 max_pref = rp_find_longest_prefix(datlist);
273 }
274 }
275
276 /* Process the dataleaf copies and add to the list of answers. */
277 ditem = g_list_first(*datlist);
278 while(ditem != NULL) {
279 rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
280 int exclude = 0;
281
282 if(search_mode == RX_SRCH_EXLESS || search_mode == RX_SRCH_LESS ) {
283
284 /* min_span defined <=> EXLESS or LESS(1) search of INETNUMS:
285 the smallest span must be returned */
286 if( !exclude && min_span != 0
287 && (span = IP_rang_span( &refptr->leafptr->iprange))!=min_span) {
288 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
289 "process_datlist: (EX)LESS: discarded object with span %d", span);
290 exclude = 1;
291 }
292 /* max_pref defined <=> EXLESS search of INETNUMS or LESS(1) of RT:
293 */
294 if( !exclude && max_pref >= 0
295 && refptr->leafptr->preflen < max_pref ) {
296 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
297 "process_datlist: (EX)LESS: discarded object with preflen %d",
298 refptr->leafptr->preflen);
299 exclude = 1;
300 }
301
302 /* number of occurences */
303 /* XXX this will go when the old algorithm goes */
304 if( !exclude
305 && prefnumber > 1 ) { /* do not check if all will be approved */
306
307 if( rp_leaf_occ_inc(lohash, refptr->leafptr) < prefnumber ) {
308 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
309 "process_datlist: (EX)LESS: leafptr %x not enough",refptr->leafptr);
310 exclude = 1;
311 }
312 else {
313 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
314 "process_datlist: (EX)LESS: leafptr %x GOOD enough",refptr->leafptr);
315 }
316 }
317 }
318 else if( search_mode == RX_SRCH_EXACT ) {
319 /* EXACT search - discard if the range does not match */
320 if( memcmp( & refptr->leafptr->iprange,
321 testrang, sizeof(ip_range_t)) != 0) {
322
323 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
324 "process_datlist: EXACT; discarded a mismatch");
325 exclude = 1;
326 } /* EXACT match */
327 }
328 else if( search_mode == RX_SRCH_MORE ) {
329 /* MORE: exclude if not fully contained in the search term */
330 if( ! (IP_addr_in_rang(&refptr->leafptr->iprange.begin, testrang )
331 && IP_addr_in_rang(&refptr->leafptr->iprange.end, testrang ))) {
332 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
333 "process_datlist: MORE; discarded a not-fully contained one");
334 exclude = 1;
335 }
336 }
337
338
339 /* get next item now, before the current gets deleted */
340 newitem = g_list_next(ditem);
341 if( exclude ) {
342 /* get rid of it */
343 rp_exclude_datlink(datlist, ditem);
344 }
345 else {
346 /* OK, so we ACCEPT these results*/
347 /* uniqueness ensured in copy_results */
348 (*hits)++;
349 }
350 ditem = newitem;
351 } /* while ditem */
352
353 /* wr_clear_list(&lolist); */
354 g_hash_table_destroy(lohash);
355 return RX_OK;
356 }
357
358 /*
359 rp_mod_preflist() is a helper function for rp_asc_search().
360
361 modifies the list of prefixes to search for,
362
363 special treatment for inetnum/exact:
364 + a range that is equivalent to the search key (which may be a prefix)
365 is made, to be used later for comparisons
366
367 special treatment for inetnum/exless/composed:
368 + the first pass mode is set to exact (otherwise to search_mode)
369
370 a few optimisations are made:
371 + for a route/composed_range/exact : the search is nuked
372 + for an inetnum/composed_range/(exless|exact) : the list is truncated
373 to one prefix, because in an exact search, it must be there anyway,
374 and for the exless, the smallest encompassing one must match
375
376
377 */
378
379 static
380 er_ret_t
381 rp_mod_preflist(
/* [<][>][^][v][top][bottom][index][help] */
382 rx_srch_mt search_mode,
383 char *key,
384 ip_keytype_t key_type,
385 rx_fam_t fam_id,
386 GList **preflist,
387 ip_range_t *testrang,
388 rx_srch_mt *first_pass_mode
389 )
390 {
391 int prefcount;
392
393 prefcount = g_list_length(*preflist);
394
395 /* EXACT search of a route tree for a composed range makes no sense */
396 if( fam_id == RX_FAM_RT && search_mode == RX_SRCH_EXACT
397 && key_type == IPK_RANGE && prefcount > 1 ) {
398 /* abort search - i.e. clear the preflist*/
399
400 wr_clear_list( preflist);
401
402 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
403 "rp_mod_preflist: route/exact/composed - preflist cleared");
404 }
405
406 /*+ inetnum / exact|exless specific :
407 optimise: (composed range)
408
409 perform a separate first pass, with just one exact search on one of
410 the composing prefixes - the object must be found if it's in the
411 database.
412
413 So a little cheat: remove all but one prefixes from preflist
414 and force a different search mode
415 +*/
416 if( fam_id == RX_FAM_IN
417 && (search_mode == RX_SRCH_EXLESS || search_mode == RX_SRCH_EXACT)
418 && key_type == IPK_RANGE && prefcount > 1 ) {
419
420 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
421 "rp_mod_preflist: inet/ex***/composed - first pass EXACT forced");
422
423 *first_pass_mode = RX_SRCH_EXACT;
424 } /* inetnum / exact|exless specific */
425
426 /* exact: set range so a comparison can be performed */
427 /* must succeed after smart_conv succeeded */
428 IP_smart_range(key, testrang, IP_EXPN, &key_type);
429
430 return RX_OK;
431 }
432 /**************************************************************************/
433
434 /*+ appends the element pointed to by datref to finallist +*/
435 static
436 er_ret_t
437 rp_asc_append_datref(rx_datref_t *refptr, GList **finallist)
/* [<][>][^][v][top][bottom][index][help] */
438 {
439 er_ret_t err;
440 rx_datcpy_t *datcpy;
441 void *dataptr;
442
443 /* OK, so we ACCEPT this result. Copy it.*/
444
445 if( (err=wr_calloc( (void **)& datcpy, 1, sizeof(rx_datcpy_t))) != UT_OK) {
446 return err; /* die;*/
447 }
448
449 datcpy->leafcpy = *(refptr->leafptr);
450
451 /* copy the immediate data too. Set the ptr.*/
452
453 if( (err=wr_calloc( (void **) & dataptr, 1, refptr->leafptr->data_len))
454 != UT_OK) {
455 return err; /* die;*/
456 }
457 memcpy(dataptr, refptr->leafptr->data_ptr, refptr->leafptr->data_len);
458
459 datcpy->leafcpy.data_ptr = dataptr;
460
461 *finallist = g_list_prepend(*finallist, datcpy);
462
463 /* XXX this wouldn't work in access_control */
464 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DATA,
465 "rp_asc_append 'ed: %s", dataptr);
466
467 return RX_OK;
468 }
469
470 /*+ goes through datlist (list of references "datref") and add copies of
471 leaves referenced to the finallist
472
473 maintains its own uniqhash which holds pointers to copied dataleaves.
474
475 modifies: finallist
476
477 returns: error from wr_malloc
478
479 +*/
480 static
481 er_ret_t
482 rp_srch_copyresults(GList *datlist,
/* [<][>][^][v][top][bottom][index][help] */
483 GList **finallist,
484 int maxcount)
485 {
486 er_ret_t err;
487 GList *ditem;
488 GHashTable *uniqhash = g_hash_table_new(NULL, NULL); /* defaults */
489 int count = 0;
490
491 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, "srch_copyresults");
492
493 /* copy dataleaves pointed to by entries from the datlist
494 only once (check uniqueness in the hash table) */
495 for(ditem = g_list_first(datlist);
496 ditem != NULL;
497 ditem = g_list_next(ditem)) {
498 rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
499 rx_dataleaf_t *ansptr = refptr->leafptr;
500
501 /* search for every ansptr (dataleaf pointer) in uniqhash */
502 if( g_hash_table_lookup(uniqhash, ansptr) == NULL ) {
503
504 /* it's not known yet. OK: put it in the hash (value==key) */
505 g_hash_table_insert(uniqhash, ansptr, ansptr);
506
507 /* and copy the dataleaf */
508 if( !NOERR(err = rp_asc_append_datref(refptr, finallist)) ) {
509 return err;
510 }
511 }
512
513 /* check the limit on number of objects if defined ( >0) */
514 count++;
515 if( maxcount > 0 && count > maxcount ) {
516 break;
517 }
518
519 } /* foreach (datlist) */
520
521 g_hash_table_destroy(uniqhash); /* elements are still linked to through datlist */
522
523 return RP_OK;
524 }
525
526 static
527 void
528 rp_begend_preselection(GList **datlist, rx_fam_t fam_id, ip_range_t *testrang)
/* [<][>][^][v][top][bottom][index][help] */
529 {
530 GList *ditem, *newitem;
531
532 ditem = g_list_first(*datlist);
533
534 while( ditem != NULL ) {
535 rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
536 newitem = g_list_next(ditem);
537
538 /* the test is indentical for route & inetnum trees */
539 if( IP_addr_in_rang(&testrang->end, &refptr->leafptr->iprange) == 0 ) {
540
541 rp_exclude_datlink(datlist, ditem);
542 ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET,
543 "process_datlist: discarded an uncovering leafptr %x",
544 refptr->leafptr);
545
546 }
547 ditem = newitem;
548 } /* while */
549 }
550
551 /*+++++++++++++++
552 search.
553
554 2 approaches:
555
556 1. (most modes): look up all less specifics of beginning and end of range,
557 compare/select/etc.
558
559 2. More spec mode: break up the query range into prefixes, [erform a search
560 for each of them. Add all results together.
561
562 translates a query into a binary prefix (or prefixes, if range).
563 for registry+space (or if they are zero, for all
564 registries/spaces)
565 finds tree
566 calls RX_bin_search (returning node copies).
567 will not put duplicate entries (composed inetnums).
568 returns some sort of error code :-)
569
570 Cuts the number of answers from RX_bin_search down to max_count,
571 but since some of the answers may have been "normalized" in the
572 underlying functions (multiple occurences removed),
573 the result is _at_most_ max_count.
574
575 appends to a given list of data blocks (not nodes!)
576
577 The EXLESS search on inetnum tree should return the shortest range
578 that was found, by means of comparing span (size) of the range.
579 If there are more of size equal to the smallest one, they are also
580 returned.
581
582 returns RX_OK or a code from an underlying function
583 ++++++++++++*/
584 er_ret_t
585 RP_asc_search (
/* [<][>][^][v][top][bottom][index][help] */
586 rx_srch_mt search_mode,
587 int par_a,
588 int par_b,
589 char *key, /*+ search term: (string) prefix/range/IP +*/
590 rp_regid_t reg_id,
591 rp_attr_t attr, /*+ extra tree id (within the same reg/spc/fam +*/
592 GList **finallist, /*+ answers go here, please +*/
593 int max_count /*+ max # of answers. RX_ALLANS == unlimited +*/
594 )
595 {
596 GList *preflist = NULL;
597 GList *datlist = NULL;
598 er_ret_t err;
599 ip_range_t testrang;
600 int locked = 0;
601 ip_keytype_t key_type;
602 ip_space_t spc_id;
603 rx_fam_t fam_id = RP_attr2fam( attr );
604 rx_tree_t *mytree;
605 int hits=0;
606 ip_prefix_t beginpref;
607
608
609 /* abort on error (but unlock the tree) */
610 ER_dbg_va(FAC_RP, ASP_RP_SRCH_GEN,
611 "RP_NEW_asc_search: query %s : mode %d (%s) (par %d) for %s",
612 DF_get_attribute_name(attr),
613 search_mode, RX_text_srch_mode(search_mode), par_a, key);
614
615
616 /* parse the key into a prefix list */
617 if( ( err = IP_smart_conv(key, 0, 0,
618 &preflist, IP_EXPN, &key_type)) != IP_OK ) {
619 /* operational trouble (UT_*) or invalid key (IP_INVARG)*/
620 return err;
621 }
622
623 /* set the test values */
624 IP_smart_range(key, &testrang, IP_EXPN, &key_type);
625
626 /* find the tree */
627 if( NOERR(err) ) {
628 spc_id = IP_pref_b2_space( g_list_first(preflist)->data );
629 if( ! NOERR(err = RP_tree_get( &mytree, reg_id, spc_id, attr ))) {
630 return err;
631 }
632 }
633 /* the point of no return: now we lock the tree. From here, even if errors
634 occur, we still go through all procedure to unlock the tree at the end */
635
636 /* lock the tree */
637 TH_acquire_read_lock( &(mytree->rwlock) );
638 locked = 1;
639
640 /* Collection: this procedure is used for some search_modes only */
641 if( search_mode == RX_SRCH_EXLESS
642 || search_mode == RX_SRCH_LESS
643 || search_mode == RX_SRCH_EXACT ) {
644
645 /* 1. compose a /32(/128) prefix for beginning of range */
646 beginpref.ip = testrang.begin;
647 beginpref.bits = IP_sizebits(spc_id);
648
649 /* 2. dataleaves collection: look up the beginning prefix in LESS(255) mode */
650 if( NOERR(err) ) {
651 err = RX_bin_search( RX_SRCH_LESS, 255, 0, mytree, &beginpref,
652 &datlist, RX_ANS_ALL);
653 }
654
655 /* 3. preselection: exclude those that do not include end of range
656 */
657 if( NOERR(err) ) {
658 rp_begend_preselection(&datlist, fam_id, &testrang);
659 }
660
661 } /* if exless|less|exact */
662 else {
663 /* MORE */
664
665 /* standard collection using the traditional method:
666 repeat the search for all prefixes and join results */
667
668 if( NOERR(err) ) {
669 err = rp_preflist_search ( search_mode, par_a, par_b,
670 mytree, &preflist, &datlist);
671 }
672 } /* collection */
673
674 ER_dbg_va(FAC_RP, ASP_RP_SRCH_GEN,
675 "RP_NEW_asc_search: collected %d references ",
676 g_list_length(datlist));
677
678
679 /* 5. processing - using the same processing function */
680 if( NOERR(err) ) {
681 err = rp_asc_process_datlist( search_mode, par_a, fam_id,
682 1, /* one occurence is enough */
683 &datlist,
684 &testrang, &hits );
685 }
686
687 /* 6. copy results */
688 if( NOERR(err) ) {
689 err = rp_srch_copyresults(datlist, finallist, max_count); /* and uniq */
690 }
691
692 if( locked ) {
693 /* 100. unlock the tree */
694 TH_release_read_lock( &(mytree->rwlock) );
695 }
696
697 /* clean up */
698 wr_clear_list( &preflist );
699 wr_clear_list( &datlist );
700
701 /* NOTE if error occured, finallist may be partly filled in. */
702 return err;
703 }
704