xref: /illumos-gate/usr/src/lib/libnisdb/db_mindex.cc (revision bc1f688b4872ace323eaddbb1a6365d054e7bf56)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  *	db_mindex.cc
23  *
24  *  Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25  *  Use is subject to license terms.
26  */
27 
28 #include <stdio.h>
29 
30 #include <malloc.h>
31 #include <strings.h>
32 #include <string.h>
33 #include <sys/param.h>
34 #include "db_headers.h"
35 #include "db.h"
36 #include "db_mindex.h"
37 #include "db_pickle.h"
38 #include "nisdb_mt.h"
39 #include "nisdb_ldap.h"
40 #include "ldap_nisdbquery.h"
41 #include "ldap_map.h"
42 #include "ldap_ruleval.h"
43 #include "ldap_scheme.h"
44 #include "ldap_parse.h"
45 #include "nis_hashitem.h"
46 
47 /*
48  *  Constructor:  Create new table using scheme defintion supplied.
49  *  (Make copy of scheme and keep it with table.)
50  */
51 db_mindex::db_mindex(db_scheme *how, char *tablePath) : rversion()
52 {
53 	noWriteThrough.flag = 0;
54 	noLDAPquery.flag = 0;
55 	initialLoad.flag = 0;
56 	objPath.ptr = NULL;
57 	init(how);
58 	if (tablePath != NULL)
59 		configure(tablePath);
60 }
61 
62 /* Constructor:  Create empty table (no scheme, no table or indices). */
63 db_mindex::db_mindex() : rversion()
64 {
65 	scheme = NULL;
66 	table = NULL;
67 	indices.indices_len = 0;
68 	indices.indices_val = NULL;
69 	noWriteThrough.flag = 0;
70 	noLDAPquery.flag = 0;
71 	initialLoad.flag = 0;
72 	objPath.ptr = NULL;
73 	INITRW(mindex);
74 }
75 
76 db_mindex::~db_mindex()
77 {
78 	reset();   /* get rid of data structures first */
79 	DESTROYRW(mindex);
80 }
81 
82 /*
83  * Initialize table using information given in scheme 'how'.
84  * Record the scheme for later use (make copy of it);
85  * create the required number of indices; and create table for storing
86  * entries.
87  */
88 void
89 db_mindex::init(db_scheme * how)
90 {
91 	scheme = new db_scheme(how);		// make copy
92 	if (scheme == NULL)
93 		FATAL("db_mindex::init: could not allocate space for scheme",
94 			DB_MEMORY_LIMIT);
95 
96 	if (scheme->numkeys() == 0) {
97 	    WARNING("db_mindex::init: empty scheme encountered");
98 	    /* what action should we take here? */
99 	}
100 
101 	indices.indices_len = how->numkeys();
102 	db_key_desc * keys = how->keyloc();
103 	int i;
104 
105 	/* homogeneous indices for now */
106 	indices.indices_val = new db_index[indices.indices_len];
107 	if (indices.indices_val == NULL) {
108 		delete scheme;
109 		indices.indices_len = 0;
110 		scheme = NULL;
111 		FATAL("db_mindex::init: could not allocate space for indices",
112 			DB_MEMORY_LIMIT);
113 	}
114 	for (i = 0; i < indices.indices_len; i++) {
115 		indices.indices_val[i].init(&(keys[i]));
116 	}
117 	table = new db_table();
118 	if (table == NULL) {
119 		delete scheme;
120 		scheme = NULL;
121 		delete indices.indices_val;
122 		indices.indices_val = NULL;
123 		indices.indices_len = 0;
124 		FATAL("db_mindex::init: could not allocate space for table",
125 			DB_MEMORY_LIMIT);
126 	}
127 	rversion.zero();
128 	INITRW(mindex);
129 	objPath.ptr = NULL;
130 }
131 
132 /* empty associated tables associated */
133 void
134 db_mindex::reset_tables()
135 {
136 	int i;
137 
138 	WRITELOCKV(this, "w db_mindex::reset_tables");
139 	/* Add sanity check in case of table corruption */
140 	if (indices.indices_val != NULL) {
141 		for (i = 0; i < indices.indices_len; i++) {
142 			indices.indices_val[i].reset();
143 		}
144 	}
145 	if (table) table->reset();
146 	WRITEUNLOCKV(this, "wu db_mindex::reset_tables");
147 }
148 
149 
150 /*
151  * Return a list of index_entries that satsify the given query 'q'.
152  * Return the size of the list in 'count'. Return NULL if list is empty.
153  * Return in 'valid' FALSE if query is not well formed.
154 */
155 db_index_entry_p
156 db_mindex::satisfy_query(db_query *q, long *count, bool_t *valid) {
157 	return (satisfy_query(q, count, valid, FALSE));
158 }
159 
160 db_index_entry_p
161 db_mindex::satisfy_query(db_query *q, long *count, bool_t *valid,
162 			bool_t fromLDAP) {
163 	db_index_entry_p	ret;
164 	bool_t			validRequest;
165 	int			queryRes;
166 
167 	/* Make sure we have somewhere to store the "request valid" status */
168 	if (valid == NULL)
169 		valid = &validRequest;
170 
171 	/* Prepare for a failed lock */
172 	*count = 0;
173 	*valid = FALSE;
174 
175 	READLOCK(this, NULL, "r db_mindex::satisfy_query");
176 
177 	/*
178 	 * Only get data from LDAP if the caller requested it,
179 	 * and if we're mapping for this table.
180 	 */
181 	fromLDAP = (fromLDAP && !noLDAPquery.flag &&
182 		(table->mapping.fromLDAP ||
183 			table->mapping.objType != NIS_TABLE_OBJ));
184 
185 	/*
186 	 * If we always fetch data from LDAP for query's, then do so now,
187 	 * before invoking the "real" satisfy_query().
188 	 */
189 	if (fromLDAP && table->mapping.matchFetch == mat_always) {
190 		int	lockcode = 0;
191 
192 		READLOCKNR(table, lockcode,
193 				"r db_mindex::satisfy_query table");
194 		if (lockcode != 0) {
195 			READUNLOCK(this, NULL, "ru db_mindex::satisfy_query");
196 			return (NULL);
197 		}
198 
199 		queryRes = queryLDAP(q, 0, 1);
200 
201 		READUNLOCKNR(table, lockcode,
202 				"ru db_mindex::satisfy_query table");
203 		if (lockcode != 0) {
204 			READUNLOCK(this, NULL, "ru db_mindex::satisfy_query");
205 			return (NULL);
206 		}
207 		if (queryRes != LDAP_SUCCESS) {
208 			/* queryLDAP() sets error codes etc. */
209 			READUNLOCK(this, NULL, "ru db_mindex::satisfy_query");
210 			return (NULL);
211 		}
212 
213 	}
214 
215 	ret = satisfy_query_dbonly(q, count, fromLDAP ? TRUE : FALSE, valid);
216 
217 	/* If we found it, or if we're not mapping, return */
218 	if (ret != NULL || !fromLDAP) {
219 		READUNLOCK(this, NULL, "ru db_mindex::satisfy_query");
220 		return (ret);
221 	} else if (ret == NULL && !(*valid)) {
222 		/* No result, and the request wasn't valid */
223 		READUNLOCK(this, NULL, "ru db_mindex::satisfy_query");
224 		return (NULL);
225 	}
226 
227 	/* Get data from LDAP */
228 	if (table->mapping.matchFetch != mat_never) {
229 		queryRes = queryLDAP(q, 0, 1);
230 	} else {
231 		/*
232 		 * We'll now go on to check for an un-expired entry again,
233 		 * even though we're pretty sure that won't work (already
234 		 * did that, and nothing's changed). However, we accept that
235 		 * slight inefficiency in the interest of keeping the code
236 		 * simple; we expect 'mat_never' to be used very rarely.
237 		 */
238 		queryRes = LDAP_SUCCESS;
239 	}
240 
241 	if (queryRes == LDAP_SUCCESS) {
242 		/*
243 		 * Check if we've got a match now. If not, try one
244 		 * last time for an expired match.
245 		 */
246 		ret = satisfy_query_dbonly(q, count, TRUE, valid);
247 		if (ret == NULL) {
248 			ret = satisfy_query_dbonly(q, count, FALSE, valid);
249 		}
250 	} else {
251 		/*
252 		 * Check if we have an expired entry; if so, return
253 		 * it with an appropriate status.
254 		 */
255 		ret = satisfy_query_dbonly(q, count, FALSE, valid);
256 	}
257 
258 	READUNLOCK(this, NULL, "ru db_mindex::satisfy_query");
259 
260 	return (ret);
261 }
262 
263 db_index_entry_p
264 db_mindex::satisfy_query_dbonly(db_query *q, long *count,
265 				bool_t checkExpire, bool_t *valid)
266 {
267 	db_index_entry_p oldres = NULL, newres;
268 	int i, curr_ind;
269 	long num_new, num_old = 0;
270 	int limit = q->size();
271 	db_qcomp * comps = q->queryloc();
272 
273 	if (valid) *valid = TRUE;   /* True to begin with. */
274 
275 	/* Add sanity check in case table corrupted */
276 	if (indices.indices_len != 0 && indices.indices_val == NULL) {
277 		WARNING("db_mindex::satisfy_query: table has no indices");
278 		if (valid) *valid = FALSE;
279 		*count = 0;
280 		return (NULL);
281 	}
282 
283 	for (i = 0; i < limit; i++) {
284 		if ((curr_ind = comps[i].which_index) < indices.indices_len) {
285 			newres = indices.indices_val[curr_ind].lookup(
286 					comps[i].index_value, &num_new,
287 					table, checkExpire);
288 			if (newres == NULL) {
289 				*count = 0;
290 				return (NULL);
291 			}
292 			if (oldres == NULL) {
293 				oldres = newres;
294 				num_old = num_new;
295 			} else {
296 				oldres = newres->join(num_new, num_old,
297 							oldres, &num_old);
298 				if (oldres == NULL) {
299 					*count = 0;
300 					return (NULL);
301 				}
302 			}
303 		} else {
304 			WARNING("db_mindex::satisfy_query: index out of range");
305 			if (valid) *valid = FALSE;
306 			*count = 0;
307 			return (NULL);
308 		}
309 	}
310 	*count = num_old;
311 	return (oldres);
312 }
313 
314 /*
315  * Returns an array of size 'count' of 'entry_object_p's, pointing to
316  * copies of entry_objects named by the result list of db_index_entries 'res'.
317  * Sets db_status 'statp' if error encountered; otherwise, leaves it unchanged.
318 */
319 entry_object_p *
320 db_mindex::prepare_results(int count, db_index_entry_p res, db_status *statp)
321 {
322 	READLOCK(this, NULL, "r db_mindex::prepare_results");
323 	READLOCK2(table, NULL, "r table db_mindex::prepare_results", this);
324 	entry_object_p * entries = new entry_object_p[count];
325 	int i;
326 
327 	if (entries == NULL) {
328 		READUNLOCK2(this, table, NULL, NULL,
329 	"ru db_mindex::prepare_results: could not allocate space",
330 	"ru table db_mindex::prepare_results: could not allocate space");
331 		FATAL3("db_mindex::prepare_results: could not allocate space",
332 			DB_MEMORY_LIMIT, NULL);
333 	}
334 
335 	for (i = 0; i < count; i++) {
336 		if (res == NULL) {
337 			int j;
338 			for (j = 0; j < i; j++) // cleanup
339 				free_entry(entries[j]);
340 			syslog(LOG_ERR,
341 				"db_mindex::prepare_results: incorrect count");
342 			*statp = DB_INTERNAL_ERROR;
343 		} else {
344 			entries[i] =
345 				new_entry(table->get_entry(res->getlocation()));
346 			res = res->getnextresult();
347 		}
348 	}
349 	READUNLOCK2(this, table, entries, entries,
350 			"ru db_mindex::prepare_results",
351 			"ru db_mindex::prepare_results");
352 
353 	return (entries);
354 }
355 
356 /*
357  * Returns a newly created db_query structure containing the index values
358  * as obtained from the record named by 'recnum'.  The record itself, along
359  * with information on the schema definition of this table, will determine
360  * which values are extracted from the record and placed into the result.
361  * Returns NULL if recnum is not a valid entry.
362  * Note that space is allocated for the query and the index values
363  * (i.e. do not share pointers with strings in 'obj'.)
364  */
365 db_query *
366 db_mindex::extract_index_values_from_record(entryp recnum)
367 {
368 	db_query	*ret;
369 
370 	ret = extract_index_values_from_object(table->get_entry(recnum));
371 	return (ret);
372 }
373 
374 /*
375  * Returns a newly created db_query containing the index values as
376  * obtained from the given object.  The object itself,
377  * along with information on the scheme given, will determine
378  * which values are extracted from the object and placed into the query.
379  * Returns an empty query if 'obj' is not a valid entry.
380  * Note that space is allocated for the query and the index values
381  * (i.e. do not share pointers with strings in 'obj'.)
382 */
383 db_query *
384 db_mindex::extract_index_values_from_object(entry_object_p obj)
385 {
386 	READLOCK(this, NULL, "r db_mindex::extract_index_values_from_object");
387 	if (scheme->numkeys() != indices.indices_len) { // probably built wrong
388 		syslog(LOG_ERR,
389 	    "number of keys (%d) does not equal number of indices (%d)",
390 	    scheme->numkeys(), indices.indices_len);
391 		READUNLOCK(this, NULL,
392 			"ru db_mindex::extract_index_values_from_object");
393 		return (new db_query());	// null query
394 	} else if (obj == NULL) {
395 		READUNLOCK(this, NULL,
396 			"ru db_mindex::extract_index_values_from_object");
397 		return (NULL);
398 	} else {
399 		db_query* answer = new db_query(scheme, obj);
400 		if (answer) {
401 			/*
402 			 * XXX If the unlock fails, and we return NULL,
403 			 * we leak 'answer'. On the other hand, if we
404 			 * return 'answer', the object may remain locked,
405 			 * but the caller doesn't know that anything
406 			 * went wrong.
407 			 */
408 			READUNLOCK(this, NULL,
409 			"ru db_mindex::extract_index_values_from_object");
410 			return (answer);
411 		} else {
412 			FATAL3("db_mindex::extract: could not allocate space",
413 				DB_MEMORY_LIMIT, NULL);
414 		}
415 	}
416 	READUNLOCK(this, NULL,
417 		"ru db_mindex::extract_index_values_from_object");
418 	return (NULL);
419 }
420 
421 /*
422  * Returns the first entry found in the table by setting 'answer' to
423  * point to the a copy of entry_object.  Returns DB_SUCCESS if found;
424  * DB_NOTFOUND otherwise.
425 */
426 db_status
427 db_mindex::first(entryp *where, entry_object ** answer)
428 {
429 	db_status	ret = DB_SUCCESS;
430 
431 	/*
432 	 * table->first_entry() returns a pointer into the table, so
433 	 * we must keep the table read locked until we've copied the
434 	 * entry_object. In order to maintain lock integrity, we must
435 	 * lock the db_mindex (this) before the db_table (table).
436 	 */
437 	READLOCK(this, DB_LOCK_ERROR, "r db_mindex::first");
438 	READLOCK2(table, DB_LOCK_ERROR, "r table db_mindex::first", this);
439 	if (table->mapping.fromLDAP) {
440 		struct timeval	now;
441 		(void) gettimeofday(&now, NULL);
442 		if (now.tv_sec >= table->mapping.enumExpire) {
443 			int queryRes = queryLDAP(0, 0, 1);
444 			if (queryRes == LDAP_SUCCESS)
445 				table->mapping.enumExpire = now.tv_sec +
446 					table->mapping.ttl;
447 			else {
448 				READUNLOCK2(this, table,
449 					DB_LOCK_ERROR, DB_LOCK_ERROR,
450 					"ru db_mindex::first LDAP",
451 					"ru table db_mindex::first LDAP");
452 				return (DB_INTERNAL_ERROR);
453 			}
454 		}
455 	}
456 	entry_object_p ptr = table->first_entry(where);
457 	if (ptr == NULL)
458 		ret = DB_NOTFOUND;
459 	else
460 		*answer = new_entry(ptr);
461 	READUNLOCK2(this, table, ret, ret,
462 		"ru db_mindex::first", "ru table db_mindex::first");
463 	return (ret);
464 }
465 
466 /*
467  * Returns the next entry in the table after 'previous' by setting 'answer' to
468  * point to copy of the entry_object.  Returns DB_SUCCESS if 'previous' is
469  * valid and next entry is found; DB_NOTFOUND otherwise.  Sets 'where' to
470  * location of where entry is found for input as subsequent 'next' operation.
471 */
472 db_status
473 db_mindex::next(entryp previous, entryp *where, entry_object **answer)
474 {
475 	db_status	ret = DB_SUCCESS;
476 
477 	READLOCK(this, DB_LOCK_ERROR, "r db_mindex::next");
478 	READLOCK2(table, DB_LOCK_ERROR, "r db_mindex::next", this);
479 	if (!(table->entry_exists_p(previous)))
480 		ret = DB_NOTFOUND;
481 	else {
482 		entry_object * ptr = table->next_entry(previous, where);
483 		if (ptr == NULL)
484 			ret = DB_NOTFOUND;
485 		else
486 			*answer = new_entry(ptr);
487 	}
488 	READUNLOCK2(this, table, ret, ret,
489 		"ru db_mindex::next", "ru table db_mindex::next");
490 	return (ret);
491 }
492 
493 static void
494 delete_result_list(db_next_index_desc* orig)
495 {
496 	db_next_index_desc* curr, *save_next;
497 	for (curr = orig; curr != NULL; 0) {
498 		save_next = curr->next;
499 		delete curr;
500 		curr = save_next;
501 	}
502 }
503 
504 
505 static db_next_index_desc *
506 copy_result_list(db_index_entry* orig)
507 {
508 	db_next_index_desc *head = NULL, *curr;
509 	db_index_entry *current;
510 
511 	for (current = orig; current != NULL;
512 		current = current->getnextresult()) {
513 		curr = new db_next_index_desc(current->getlocation(), head);
514 		if (curr == NULL) {
515 			FATAL3(
516 			"db_mindex::copy_result_list: could not allocate space",
517 			DB_MEMORY_LIMIT, NULL);
518 		}
519 		head = curr;  // list is actually reversed
520 	}
521 	return (head);
522 }
523 
524 /*
525  * Delete the given list of results; used when no longer interested in
526  * the results of the first/next query that returned this list.
527  */
528 db_status
529 db_mindex::reset_next(db_next_index_desc *orig)
530 {
531 	if (orig == NULL)
532 		return (DB_NOTFOUND);
533 
534 	delete_result_list(orig);
535 	return (DB_SUCCESS);
536 }
537 
538 /*
539 * Finds entry that satisfy the query 'q'.  Returns the first answer by
540 * setting the pointer 'answer' to point to a copy of it.  'where' is set
541 * so that the other answers could be gotten by passing 'where' to 'next'
542 * successively.   Note that the answer is a  pointer to a copy of the entry.
543 * Returns DB_SUCCESS if search was successful; DB_NOTFOUND otherwise.
544  */
545 db_status
546 db_mindex::first(db_query *q,
547 		db_next_index_desc **where, entry_object ** answer)
548 {
549 	READLOCK(this, DB_LOCK_ERROR, "r db_mindex::first");
550 	READLOCK2(table, DB_LOCK_ERROR, "r table db_mindex::first", this);
551 	long count;
552 	bool_t valid_query;
553 	db_status	ret = DB_SUCCESS;
554 	db_index_entry * rp = satisfy_query(q, &count, &valid_query, TRUE);
555 
556 	if (valid_query != TRUE)
557 		ret =  DB_BADQUERY;
558 	else if (rp == NULL) {
559 		*answer = NULL;
560 		ret = DB_NOTFOUND;
561 	} else {
562 		*where = copy_result_list(rp);
563 
564 		entry_object_p ptr = table->get_entry((*where)->location);
565 		if (ptr == NULL)
566 			ret = DB_NOTFOUND;
567 		else
568 			*answer = new_entry(ptr);
569 	}
570 	READUNLOCK2(this, table, ret, ret,
571 		"ru db_mindex::first", "ru table db_mindex::first");
572 	return (ret);
573 }
574 
575 /*
576  * Returns the next entry in the table after 'previous' by setting 'answer' to
577  * point to copy of the entry_object.  Next is next in chain of answers found
578  * in previous first search with query.   Returns DB_SUCCESS if 'previous' is
579  * valid and next entry is found; DB_NOTFOUND otherwise.  Sets 'where' to
580  * location of where entry is found for input as subsequent 'next' operation.
581 */
582 db_status
583 db_mindex::next(db_next_index_desc *previous, db_next_index_desc **where,
584 		entry_object **answer)
585 {
586 	READLOCK(this, DB_LOCK_ERROR, "r db_mindex::next");
587 	READLOCK2(table, DB_LOCK_ERROR, "r table db_mindex::next", this);
588 	db_status	ret = DB_SUCCESS;
589 
590 	if (previous == NULL)
591 		ret = DB_NOTFOUND;
592 	else {
593 		// should further check validity of 'previous' pointer
594 		*where = previous->next;
595 		delete previous;    // delete previous entry
596 		if (*where == NULL)
597 			ret = DB_NOTFOUND;
598 		else {
599 			entry_object * ptr =
600 				table->get_entry((*where)->location);
601 			if (ptr == NULL)
602 				ret = DB_NOTFOUND;
603 			else {
604 				*answer = new_entry(ptr);
605 				ret = DB_SUCCESS;
606 			}
607 		}
608 	}
609 	READUNLOCK2(this, table, ret, ret,
610 		"ru db_mindex::next", "ru table db_mindex::next");
611 	return (ret);
612 }
613 
614 /*
615  * Finds entry that satisfy the query 'q'.  Returns the answer by
616  * setting the pointer 'rp' to point to the list of answers.
617  * Note that the answers are pointers to the COPIES of entries.
618  * Returns the number of answers find in 'count'.
619  * Returns DB_SUCCESS if search found at least one answer;
620  * returns DB_NOTFOUND if none is found.
621 */
622 db_status
623 db_mindex::lookup(db_query *q, long *count, entry_object_p **result)
624 {
625 	bool_t valid_query;
626 	db_index_entry * rp = satisfy_query(q, count, &valid_query, TRUE);
627 	db_status stat = DB_SUCCESS;
628 
629 	if (valid_query != TRUE)
630 		return (DB_BADQUERY);
631 
632 	if (rp == NULL) {
633 		*result = NULL;
634 		return (DB_NOTFOUND);
635 	}
636 
637 	*result = prepare_results((int)*count, rp, &stat);
638 
639 	return (stat);
640 }
641 
642 /*
643  * Return all entries within table.  Returns the answer by
644  * setting the pointer 'rp' to point to the list of answers.
645  * Note that the answers are pointers to copies of the entries.
646  * Returns the number of answers find in 'count'.
647  * Returns DB_SUCCESS if search found at least one answer;
648  * returns DB_NOTFOUND if none is found.
649 */
650 db_status
651 db_mindex::all(long *count, entry_object_p **result)
652 {
653 	entry_object *ptr;
654 	entryp where;
655 	long how_many, i;
656 	int	lret = 0;
657 
658 	if (table == NULL) {
659 		*result = NULL;
660 		return (DB_NOTFOUND);
661 	}
662 
663 	READLOCK(this, DB_LOCK_ERROR, "r db_mindex::all");
664 	/* Read lock 'table' while we're traversing it */
665 	READLOCKNR(table, lret, "r table db_mindex::all");
666 	if (lret != 0) {
667 		READUNLOCK(this, DB_LOCK_ERROR, "ru db_mindex::all");
668 		return (DB_LOCK_ERROR);
669 	}
670 
671 	if (table->mapping.fromLDAP) {
672 		struct timeval	now;
673 		(void) gettimeofday(&now, NULL);
674 		if (now.tv_sec >= table->mapping.enumExpire) {
675 			int	queryRes = queryLDAP(0, 0, 1);
676 			if (queryRes != LDAP_SUCCESS) {
677 				READUNLOCKNR(table, lret,
678 					"ru table db_mindex::all LDAP");
679 				READUNLOCK(this, DB_LOCK_ERROR,
680 					"ru db_mindex::all LDAP");
681 				return (DB_INTERNAL_ERROR);
682 			}
683 		}
684 	}
685 
686 	if ((how_many = table->fullness()) <= 0) {
687 		/*
688 		 * Set '*count' so that the caller avoids putting garbage
689 		 * in an 'objects_len' field.
690 		 */
691 		*count = 0;
692 		*result = NULL;
693 		READUNLOCKNR(table, lret, "ru table db_mindex::all");
694 		READUNLOCK(this, DB_NOTFOUND, "ru db_mindex::all");
695 		return (DB_NOTFOUND);
696 	}
697 
698 	entry_object_p * answer = new entry_object_p[how_many];
699 	if (answer == NULL) {
700 		READUNLOCKNR(table, lret, "ru table db_mindex::all");
701 		READUNLOCK(this, DB_MEMORY_LIMIT, "ru db_mindex::all");
702 		FATAL3("db_mindex::all: could not allocate space",
703 			DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
704 	}
705 
706 	*count = how_many;
707 
708 	ptr = table->first_entry(&where);
709 	if (ptr != NULL)
710 		answer[0] = new_entry(ptr);
711 	else {
712 		WARNING("db_mindex::all: null first entry found in all");
713 		answer[0] = NULL;
714 	}
715 	for (i = 1; i < how_many; i++) {
716 		ptr = table->next_entry(where, &where);
717 		if (ptr != NULL)
718 			answer[i] = new_entry(ptr);
719 		else {
720 			WARNING(
721 			    "db_mindex::all: null internal entry found in all");
722 			answer[i] = NULL; /* Answer gets null too. -CM */
723 		}
724 	}
725 
726 	READUNLOCKNR(table, lret, "ru table db_mindex::all");
727 
728 	*result = answer;
729 	READUNLOCK(this, DB_SUCCESS, "ru db_mindex::all");
730 	return (DB_SUCCESS);
731 }
732 
733 /*
734  * Remove the entry identified by 'recloc' from:
735  * 1.  all indices, as obtained by extracting the index values from the entry
736  * 2.  table where entry is stored.
737 */
738 db_status
739 db_mindex::remove_aux(entryp recloc)
740 {
741 	int i, curr_ind;
742 	db_status	res = DB_SUCCESS;
743 
744 	WRITELOCK(this, DB_LOCK_ERROR, "w db_mindex::remove_aux");
745 	/* get index values of this record */
746 	db_query * cq = extract_index_values_from_record(recloc);
747 	if (cq == NULL) {
748 		WRITEUNLOCK(this, DB_MEMORY_LIMIT, "wu db_mindex::remove_aux");
749 		FATAL3("db_mindex::remove_aux: could not allocate space",
750 			DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
751 	}
752 	if (cq->size() != indices.indices_len) { /* something is wrong */
753 		delete cq; // clean up
754 		syslog(LOG_ERR,
755 	    "db_mindex::remove_aux: record contains wrong number of indices");
756 		WRITEUNLOCK(this, DB_INTERNAL_ERROR,
757 			"wu db_mindex::remove_aux");
758 		return (DB_INTERNAL_ERROR);
759 	}
760 
761 	if (!noWriteThrough.flag) {
762 		nis_object	*o = 0;
763 		entry_object    *e = table->get_entry(recloc);
764 		int		queryRes, doingModify;
765 
766 		/*
767 		 * If the removal is part of a modify operation, we
768 		 * defer the LDAP update until the modified NIS+ object
769 		 * is added back.
770 		 */
771 		if (saveOldObjForModify((entry_obj *)e, &doingModify) == 0)
772 			res = DB_INTERNAL_ERROR;
773 
774 		if (res == DB_SUCCESS && !doingModify) {
775 			/*
776 			 * If we're removing a directory entry, and the
777 			 * entry is LDAP-mapped, but the directory isn't,
778 			 * we need a copy of the entry object in order
779 			 * to remove if from LDAP.
780 			 */
781 			if (e != 0 && e->en_type != 0 &&
782 					strcmp(e->en_type, "IN_DIRECTORY") == 0)
783 				o = unmakePseudoEntryObj(e, 0);
784 			queryRes = removeLDAP(cq, o);
785 			if (queryRes != LDAP_SUCCESS) {
786 				if (table->mapping.storeErrorDisp == abandon)
787 					res = DB_INTERNAL_ERROR;
788 			}
789 			if (o != 0)
790 				nis_destroy_object(o);
791 		}
792 	}
793 
794 	if (res == DB_SUCCESS) {
795 		db_qcomp * comps = cq->queryloc();
796 
797 		/* Add sanity check in case of corrupted table */
798 		if (indices.indices_val != NULL) {
799 			/* update indices */
800 			for (i = 0; i < indices.indices_len; i++) {
801 				/* unnec. if sorted */
802 				curr_ind = comps[i].which_index;
803 				indices.indices_val[curr_ind].remove(
804 						comps[i].index_value, recloc);
805 			}
806 		}
807 
808 		/* update table where record is stored */
809 		table->delete_entry(recloc);
810 	}
811 
812 	/* delete query */
813 	delete cq;
814 
815 	WRITEUNLOCK(this, DB_SUCCESS, "wu db_mindex::remove_aux");
816 
817 	return (res);
818 }
819 
820 /*
821  * Removes the entry in the table named by given query 'q'.
822  * If a NULL query is supplied, all entries in table are removed.
823  * Returns DB_NOTFOUND if no entry is found.
824  * Returns DB_SUCCESS if one entry is found; this entry is removed from
825  * its record storage, and it is also removed from all the indices of the
826  * table. If more than one entry satisfying 'q' is found, all are removed.
827  */
828 db_status
829 db_mindex::remove(db_query *q)
830 {
831 	long count = 0;
832 	db_index_entry *rp;
833 	db_status rstat;
834 	bool_t valid_query;
835 
836 	WRITELOCK(this, DB_LOCK_ERROR, "w db_mindex::remove");
837 	WRITELOCK2(table, DB_LOCK_ERROR, "w table db_mindex::remove", this);
838 	if (q == NULL)  {  /* remove all entries in table */
839 		if (table->mapping.toLDAP && !noWriteThrough.flag) {
840 			int	queryRes = removeLDAP(q, 0);
841 #ifdef	NISDB_LDAP_DEBUG
842 			if (queryRes != LDAP_SUCCESS)
843 				abort();
844 #endif	/* NISDB_LDAP_DEBUG */
845 		}
846 		if (table != NULL && table->getsize() > 0) {
847 			reset_tables();
848 			WRITEUNLOCK2(table, this, DB_SUCCESS, DB_SUCCESS,
849 					"wu table db_mindex::remove",
850 					"wu db_mindex::remove");
851 			return (DB_SUCCESS);
852 		} else {
853 			WRITEUNLOCK2(table, this, DB_NOTFOUND, DB_NOTFOUND,
854 					"wu table db_mindex::remove",
855 					"wu db_mindex::remove");
856 			return (DB_NOTFOUND);
857 		}
858 	}
859 
860 	rp = satisfy_query(q, &count, &valid_query, FALSE);
861 
862 	if (valid_query != TRUE) {
863 		WRITEUNLOCK2(table, this, DB_BADQUERY, DB_BADQUERY,
864 			"wu table db_mindex::remove", "wu db_mindex::remove");
865 		return (DB_BADQUERY);
866 	}
867 
868 	if (count == 0) {	/* not found */
869 		WRITEUNLOCK2(table, this, DB_NOTFOUND, DB_NOTFOUND,
870 			"wu table db_mindex::remove", "wu db_mindex::remove");
871 		return (DB_NOTFOUND);
872 	} else if (count == 1) {	/* found, update indices  */
873 		db_status	s;
874 
875 		s = remove_aux(rp->getlocation());
876 
877 		WRITEUNLOCK2(table, this, s, s,
878 			"wu table db_mindex::remove", "wu db_mindex::remove");
879 		return (s);
880 	} else {		/* ambiguous, remove all entries */
881 		int i;
882 		db_index_entry *next_entry;
883 		for (i = 0; i < count; i++) {
884 			if (rp == NULL) {
885 				syslog(LOG_ERR,
886 			"db_mindex::remove:  incorrect number of indices");
887 				WRITEUNLOCK2(table, this, DB_INTERNAL_ERROR,
888 					DB_INTERNAL_ERROR,
889 					"wu table db_mindex::remove",
890 					"wu db_mindex::remove");
891 				return (DB_INTERNAL_ERROR);
892 			}
893 
894 			next_entry = rp->getnextresult(); // save before removal
895 			rstat = remove_aux(rp->getlocation());
896 			if (rstat != DB_SUCCESS) {
897 				WRITEUNLOCK2(table, this, rstat, rstat,
898 					"wu table db_mindex::remove",
899 					"wu db_mindex::remove");
900 				return (rstat);
901 			}
902 			rp = next_entry;		// go on to next
903 		}
904 		WRITEUNLOCK2(table, this, DB_SUCCESS, DB_SUCCESS,
905 			"wu table db_mindex::remove", "wu db_mindex::remove");
906 		return (DB_SUCCESS);
907 	}
908 }
909 
910 /*
911  * Add copy of given entry to table.  Entry is identified by query 'q'.
912  * The entry (if any) satisfying the query is first deleted, then
913  *  added to the indices (using index values extracted form the given entry)
914  * and the table.
915  * Returns DB_NOTUNIQUE if more than one entry satisfies the query.
916  * Returns DB_NOTFOUND if query is not well-formed.
917  * Returns DB_SUCCESS if entry can be added.
918 */
919 db_status
920 db_mindex::add(db_query *q, entry_object * obj)
921 {
922 	long count = 0;
923 	int i, curr_ind;
924 	bool_t valid;
925 	db_index_entry *rp = NULL;
926 	db_status rstat;
927 	const char	*myself = "db_mindex::add";
928 
929 	/*
930 	 *  The argument q is only NULL when we know that there are
931 	 *  no objects in the database that match the object.
932 	 */
933 	WRITELOCK(this, DB_LOCK_ERROR, "w db_mindex::add");
934 	WRITELOCK2(table, DB_LOCK_ERROR, "w table db_mindex::add", this);
935 	if (q) {
936 		rp = satisfy_query(q, &count, &valid, FALSE);
937 		if (!valid) {
938 			WRITEUNLOCK2(this, table, DB_LOCK_ERROR, DB_LOCK_ERROR,
939 					"wu db_mindex::add",
940 					"wu table db_mindex::add");
941 			return (DB_BADQUERY);
942 		}
943 	}
944 	if (count == 1) {	/* found, first delete */
945 		rstat = remove_aux(rp->getlocation());
946 		if (rstat != DB_SUCCESS) {
947 			WRITEUNLOCK2(this, table, rstat, rstat,
948 				"wu db_mindex::add",
949 				"wu table db_mindex::add");
950 			return (rstat);
951 		}
952 		count = 0;	/* fall through to add */
953 	}
954 
955 	if (count == 0) { 	/* not found, insert */
956 		/* add object to table */
957 		entryp recloc = table->add_entry(obj, initialLoad.flag);
958 		/* get index values of this object, might be same as 'q' */
959 		db_query *cq = extract_index_values_from_object(obj);
960 		if (cq == NULL) {
961 			table->delete_entry(recloc);
962 			WRITEUNLOCK2(this, table,
963 				DB_MEMORY_LIMIT, DB_MEMORY_LIMIT,
964 				"wu db_mindex::add DB_MEMORY_LIMIT",
965 				"wu table db_mindex::add DB_MEMORY_LIMIT");
966 			FATAL3("db_mindex::add: could not allocate space for",
967 				DB_MEMORY_LIMIT, DB_MEMORY_LIMIT);
968 		}
969 		if (cq ->size() != indices.indices_len) { /* something wrong */
970 			table->delete_entry(recloc);
971 			delete cq; // clean up
972 			syslog(LOG_ERR,
973 		    "db_mindex::add: record contains wrong number of indices");
974 			WRITEUNLOCK2(this, table,
975 				DB_INTERNAL_ERROR, DB_INTERNAL_ERROR,
976 				"wu db_mindex::add DB_INTERNAL_ERROR",
977 				"wu table db_mindex::add DB_INTERNAL_ERROR");
978 			return (DB_INTERNAL_ERROR);
979 		}
980 		db_qcomp * comps = cq->queryloc();
981 
982 		/* update indices */
983 		if (indices.indices_val != NULL) {
984 			for (i = 0; i < indices.indices_len; i++) {
985 				curr_ind = comps[i].which_index;
986 				indices.indices_val[curr_ind].add(
987 					comps[i].index_value, recloc);
988 			}
989 		}
990 		delete cq;  // clean up
991 		if (!noWriteThrough.flag) {
992 			int		queryRes;
993 			entry_object	*e = 0;
994 
995 			if (retrieveOldObjForModify((entry_obj **)&e) == 0) {
996 				logmsg(MSG_NOTIMECHECK, LOG_ERR,
997 			"%s: Error retrieving old object for LDAP update",
998 					myself);
999 				return (DB_INTERNAL_ERROR);
1000 			}
1001 
1002 			queryRes = storeLDAP(q, obj, 0, e, 0);
1003 			if (queryRes != LDAP_SUCCESS) {
1004 				if (table->mapping.storeErrorDisp == abandon) {
1005 					WRITEUNLOCK2(this, table,
1006 						DB_INTERNAL_ERROR,
1007 						DB_INTERNAL_ERROR,
1008 						"wu db_mindex::add LDAP",
1009 						"wu table db_mindex::add LDAP");
1010 					return (DB_INTERNAL_ERROR);
1011 				} else {
1012 					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
1013 						"%s: LDAP store failed: %s",
1014 						myself,
1015 						ldap_err2string(queryRes));
1016 				}
1017 			}
1018 		}
1019 		rstat = DB_SUCCESS;
1020 	} else  /* ambiguous */
1021 		rstat = DB_NOTUNIQUE;
1022 
1023 	WRITEUNLOCK2(this, table, rstat, rstat,
1024 			"wu db_mindex::add",
1025 			"wu table db_mindex::add");
1026 	return (rstat);
1027 }
1028 
1029 /* ************************* pickle_mindex ********************* */
1030 /* Does the actual writing to/from file specific for db_mindex structure. */
1031 static bool_t
1032 transfer_aux(XDR* x, pptr rp)
1033 {
1034 	return (xdr_db_mindex(x, (db_mindex*) rp));
1035 }
1036 
1037 class pickle_mindex: public pickle_file {
1038     public:
1039 	pickle_mindex(char *f, pickle_mode m) : pickle_file(f, m) {}
1040 
1041 	/* Transfers db_mindex structure pointed to by dp to/from file. */
1042 	int transfer(db_mindex* dp)
1043 		{
1044 			int	ret;
1045 
1046 			WRITELOCK(dp, -1, "w pickle_mindex::transfer");
1047 			ret = pickle_file::transfer((pptr) dp, &transfer_aux);
1048 			WRITEUNLOCK(dp, ret, "wu pickle_mindex::transfer");
1049 			return (ret);
1050 		}
1051 };
1052 
1053 /* Write this structure (table, indices, scheme) into the specified file. */
1054 int
1055 db_mindex::dump(char *file)
1056 {
1057 	pickle_mindex f(file, PICKLE_WRITE);
1058 	int status = f.transfer(this);
1059 
1060 	if (status == 1)
1061 		return (-1); /* could not open for write */
1062 	else
1063 		return (status);
1064 }
1065 
1066 /*
1067  * Reset the table by: deleting all the indices, table of entries, and its
1068  * scheme.
1069 */
1070 void
1071 db_mindex::reset()
1072 {
1073 	WRITELOCKV(this, "w db_mindex::reset");
1074 	reset_tables();   /* clear table contents first */
1075 
1076 	if (indices.indices_val) {
1077 		delete [] indices.indices_val;
1078 		indices.indices_val = NULL;
1079 	}
1080 	if (table) { delete table; table = NULL;  }
1081 	if (scheme) { delete scheme; scheme = NULL;  }
1082 	indices.indices_len = 0;
1083 	rversion.zero();
1084 	if (objPath.ptr != 0) {
1085 		free(objPath.ptr);
1086 		objPath.ptr = 0;
1087 	}
1088 	WRITEUNLOCKV(this, "wu db_mindex::reset");
1089 }
1090 
1091 /*
1092  * Initialize table using information from specified file.
1093  * The table is first 'reset', then the attempt to load from the file
1094  * is made.  If the load failed, the table is again reset.
1095  * Therefore, the table will be modified regardless of the success of the
1096  * load.  Returns 0 if successful, 1 if DB disk file couldn't be opened,
1097  * -1 for various other failures.
1098 */
1099 int
1100 db_mindex::load(char *file)
1101 {
1102 	pickle_mindex f(file, PICKLE_READ);
1103 	int status;
1104 	int	init_table = (this->table == NULL);
1105 	int	init_scheme = (this->scheme == NULL);
1106 
1107 	WRITELOCK(this, -1, "w db_mindex::load");
1108 	reset();
1109 
1110 	/* load new mindex */
1111 	if ((status = f.transfer(this)) != 0) {
1112 		/* load failed.  Reset. */
1113 		reset();
1114 	}
1115 
1116 	/* Initialize the 'scheme' locking */
1117 	if (status == 0 && this->scheme != 0 && init_scheme) {
1118 		/*
1119 		 * Since we've added fields to the db_scheme that aren't
1120 		 * read from disk, we need to re-allocate so that the
1121 		 * db_scheme instance is large enough.
1122 		 */
1123 		db_scheme	*tmpscheme = new db_scheme();
1124 		if (tmpscheme != 0) {
1125 			(void) memcpy(tmpscheme, this->scheme,
1126 					this->scheme->oldstructsize());
1127 			free(this->scheme);
1128 			this->scheme = tmpscheme;
1129 		} else {
1130 			status = -1;
1131 		}
1132 	}
1133 	/*
1134 	 * If the 'table' field was NULL before the load, but not now,
1135 	 * initialize the table locking and mapping.
1136 	 */
1137 	if (status == 0 && this->table != 0 && init_table) {
1138 		/*
1139 		 * As for the db_scheme, make sure the db_table is large
1140 		 * enough.
1141 		 */
1142 		db_table	*tmptable = new db_table();
1143 		if (tmptable != 0) {
1144 			(void) memcpy(tmptable, this->table,
1145 					this->table->oldstructsize());
1146 			free(this->table);
1147 			this->table = tmptable;
1148 			(void) this->configure(file);
1149 		} else {
1150 			status = -1;
1151 		}
1152 	}
1153 
1154 	if (status == 0 && this->indices.indices_val != NULL) {
1155 		/*
1156 		 * Recreate the db_index instance so that it is
1157 		 * correctly initialized.
1158 		 */
1159 		db_index *tmp_indices;
1160 		int	n_index = this->indices.indices_len;
1161 
1162 		tmp_indices = new db_index[n_index];
1163 		if (tmp_indices != NULL) {
1164 			for (int i = 0; i < n_index; i++) {
1165 			    if (tmp_indices[i].move_xdr_db_index
1166 				(&this->indices.indices_val[i]) != DB_SUCCESS) {
1167 					status = -1;
1168 					break;
1169 			    }
1170 			}
1171 			free(this->indices.indices_val);
1172 			this->indices.indices_val = tmp_indices;
1173 			this->indices.indices_len = n_index;
1174 		} else {
1175 			status = -1;
1176 		}
1177 	}
1178 
1179 	WRITEUNLOCK(this, status, "wu db_mindex::load");
1180 	return (status);
1181 }
1182 
1183 /*
1184  * Prints statistics of the table.  This includes the size of the table,
1185  * the number of entries, and the index sizes.
1186  */
1187 void
1188 db_mindex::print_stats()
1189 {
1190 	long size, count, i;
1191 	long *stats = table->stats(TRUE);
1192 
1193 	printf("table_size = %d\n", stats[0]);
1194 	printf("last_used = %d\n", stats[1]);
1195 	printf("count = %d\n", stats[2]);
1196 	printf("free list size = %d\n", stats[3]);
1197 	printf("free list count = %d\n", stats[4]);
1198 
1199 	for (i = 5; i < 5+stats[4]; i++) {
1200 		printf("%d, ", stats[i]);
1201 	}
1202 	printf("\n");
1203 	free((char *)stats);
1204 
1205 	/* Add sanity check in case of corrupted table */
1206 	if (indices.indices_val == NULL) {
1207 		printf("No indices to print\n");
1208 		return;
1209 	}
1210 	for (i = 0; i < indices.indices_len; i++) {
1211 		printf("***** INDEX %d ******\n", i);
1212 		indices.indices_val[i].stats(&size, &count);
1213 		printf("index table size = %d\ncount = %d\n", size, count);
1214 	}
1215 }
1216 
1217 /* Prints statistics about all indices of table. */
1218 void
1219 db_mindex::print_all_indices()
1220 {
1221 	int i;
1222 
1223 	READLOCKV(this, "r db_mindex::print_all_indices");
1224 	/* Add sanity check in case of corrupted table */
1225 	if (indices.indices_val == NULL) {
1226 		printf("No indices to print\n");
1227 		READUNLOCKV(this, "ru db_mindex::print_all_indices");
1228 		return;
1229 	}
1230 	for (i = 0; i < indices.indices_len; i++) {
1231 		printf("***** INDEX %d ******\n", i);
1232 		indices.indices_val[i].print();
1233 	}
1234 	READUNLOCKV(this, "ru db_mindex::print_all_indices");
1235 }
1236 
1237 /* Prints statistics about indices identified by 'n'. */
1238 void
1239 db_mindex::print_index(int n)
1240 {
1241 	READLOCKV(this, "r db_mindex::print_index");
1242 	if (n >= 0 && n < indices.indices_len)
1243 		indices.indices_val[n].print();
1244 	READUNLOCKV(this, "ru db_mindex::print_index");
1245 }
1246