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 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #pragma ident "%Z%%M% %I% %E% SMI"
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include "nscd_db.h"
33
34 /*
35 * This file implements the database functionality used by the
36 * switch and configuration components. The implementation is
37 * based on hash and has table. If the need arises in the future,
38 * the code in this file can be changed to use other algorithms.
39 * The following lists the functions implemented:
40 *
41 * _nscd_add_db_entry
42 * _nscd_delete_db_entry
43 * _nscd_get_db_entry
44 * _nscd_alloc_db
45 * _nscd_free_db
46 * _nscd_walk_db
47 * _nscd_delete_db_entry_cookie
48 */
49
50 /*
51 * This structure defines an instance of the hash entry
52 * which implements the nscd database entry. The
53 * db_entry field should always be the first one in
54 * the structure.
55 */
56 typedef struct nscd_hash {
57 nscd_db_entry_t db_entry;
58 struct nscd_hash *next_p;
59 struct nscd_hash *prev_p;
60 } nscd_hash_t;
61
62 /*
63 * This structure defines a nscd database which
64 * is implemented as an array of nscd_hash_t.
65 */
66 struct nscd_db_s {
67 int array_size; /* number of elements in hash_tbl_p */
68 nscd_hash_t **hash_tbl_p;
69 };
70
71 /*
72 * This cookie structure is used to iterate through the
73 * database entries contained in a nscd database.
74 */
75 struct cookie {
76 int idx; /* the current bucket */
77 nscd_hash_t *hash; /* the current hash entry */
78 nscd_db_t *db; /* the database */
79 };
80
81 /*
82 * FUNCTION: calc_hash
83 *
84 * Calculate a hash for a string based on the elf_hash
85 * algorithm, hash is case insensitive. Uses tolower
86 * instead of _tolower because of I18N.
87 */
88 static unsigned long
calc_hash(const char * str)89 calc_hash(
90 const char *str)
91 {
92 unsigned int hval = 0;
93 char ch;
94
95 while (*str != NULL) {
96 unsigned int g;
97
98 ch = (char)*str++;
99 if (isupper(ch))
100 ch = _tolower(ch);
101 hval = (hval << 4) + ch;
102 if ((g = (hval & 0xf0000000)) != 0)
103 hval ^= g >> 24;
104 hval &= ~g;
105 }
106 return ((unsigned long)hval);
107 }
108
109 /*
110 * FUNCTION: scan_hash
111 *
112 * Scan a hash table for a matching hash entry. Assume 'str' is
113 * not NULL. If option is NSCD_GET_NEXT_DB_ENTRY and id_num
114 * is less than zero, then treats the option as NSCD_GET_FIRST_DB_ENTRY.
115 */
116
117 static const nscd_hash_t *
scan_hash(int type,const char * str,const nscd_hash_t * idx_p,nscd_db_option_t option,int id_num)118 scan_hash(
119 int type,
120 const char *str,
121 const nscd_hash_t *idx_p,
122 nscd_db_option_t option,
123 int id_num)
124 {
125 int id_matched = 0;
126 nscd_db_entry_t *db_entry;
127
128 while (idx_p != NULL) {
129 db_entry = &((nscd_hash_t *)idx_p)->db_entry;
130 if (db_entry->type == type) {
131 if (strcasecmp(str, db_entry->name) == 0) {
132 switch (option) {
133 case NSCD_GET_FIRST_DB_ENTRY:
134 return (idx_p);
135 case NSCD_GET_EXACT_DB_ENTRY:
136 if (id_num == db_entry->id_num)
137 return (idx_p);
138 break;
139 case NSCD_GET_NEXT_DB_ENTRY:
140 if (id_num < 0)
141 return (idx_p);
142 if (id_matched == 1)
143 return (idx_p);
144 if (id_num == db_entry->id_num)
145 id_matched = 1;
146 break;
147 }
148 }
149 }
150 idx_p = idx_p->next_p;
151 }
152 return (NULL);
153 }
154
155 /*
156 * FUNCTION: _nscd_get_db_entry
157 *
158 * Find a nscd database entry from a nscd database.
159 */
160 const nscd_db_entry_t *
_nscd_get_db_entry(const nscd_db_t * db,int type,const char * str,nscd_db_option_t option,int id_num)161 _nscd_get_db_entry(
162 const nscd_db_t *db,
163 int type,
164 const char *str,
165 nscd_db_option_t option,
166 int id_num)
167 {
168 unsigned long hash;
169 const nscd_hash_t *idx_p, *hash_p;
170
171 if (db == NULL || str == NULL)
172 return (NULL);
173
174 hash = calc_hash(str);
175 idx_p = db->hash_tbl_p[hash % db->array_size];
176
177 hash_p = scan_hash(type, str, idx_p, option, id_num);
178
179 return (&hash_p->db_entry);
180 }
181
182 /*
183 * FUNCTION: _nscd_add_db_entry
184 *
185 * Add a nscd database entry to a nscd database. This function
186 * is not MT safe. The caller should lock the database to
187 * prevent concurrent updates done by other threads.
188 */
189 nscd_rc_t
_nscd_add_db_entry(nscd_db_t * db,const char * str,nscd_db_entry_t * entry,nscd_db_option_t option)190 _nscd_add_db_entry(
191 nscd_db_t *db,
192 const char *str,
193 nscd_db_entry_t *entry,
194 nscd_db_option_t option)
195 {
196 int i;
197 unsigned long hash;
198 nscd_hash_t *next_p = NULL, *prev_p = NULL;
199 nscd_hash_t *idx_p, *hash_entry;
200 nscd_db_entry_t *db_entry;
201
202 /* find the bucket */
203 hash = calc_hash(str);
204 i = hash % db->array_size;
205 idx_p = db->hash_tbl_p[i];
206
207 /* can not replace nothing */
208 if (idx_p == NULL)
209 if (option == NSCD_ADD_DB_ENTRY_REPLACE)
210 return (NSCD_DB_ENTRY_NOT_FOUND);
211
212 while (idx_p != NULL) {
213 db_entry = &idx_p->db_entry;
214 switch (option) {
215
216 case NSCD_ADD_DB_ENTRY_FIRST:
217 next_p = idx_p;
218 goto add_entry;
219
220 case NSCD_ADD_DB_ENTRY_REPLACE:
221 if (db_entry->type != entry->type)
222 goto cont;
223 if (strcasecmp(db_entry->name, str) != 0)
224 goto cont;
225
226 if (db_entry->id_num == entry->id_num) {
227 prev_p = idx_p->prev_p;
228 next_p = idx_p->next_p;
229 free(idx_p);
230 goto add_entry;
231 }
232 goto cont;
233
234 case NSCD_ADD_DB_ENTRY_IF_NONE:
235 if (db_entry->type != entry->type)
236 break;
237 if (strcasecmp(db_entry->name, str) != 0)
238 break;
239 return (NSCD_DB_ENTRY_FOUND);
240 }
241
242 if (idx_p->next_p == NULL) {
243 if (option == NSCD_ADD_DB_ENTRY_LAST ||
244 option == NSCD_ADD_DB_ENTRY_IF_NONE) {
245 prev_p = idx_p;
246 goto add_entry;
247 }
248 }
249
250 cont:
251 idx_p = idx_p->next_p;
252 }
253
254 add_entry:
255
256 /*
257 * the nscd_entry_t field should be the first field
258 * in a nscd_hash_t
259 */
260 hash_entry = (nscd_hash_t *)entry;
261
262 /* update the prev link list */
263 hash_entry->prev_p = prev_p;
264 if (prev_p == NULL)
265 db->hash_tbl_p[i] = hash_entry;
266 else
267 prev_p->next_p = hash_entry;
268
269 /* update the next link list */
270 hash_entry->next_p = next_p;
271 if (next_p != NULL)
272 next_p->prev_p = hash_entry;
273
274 return (NSCD_SUCCESS);
275 }
276
277 /*
278 * FUNCTION: _nscd_delete_db_entry
279 *
280 * Delete a nscd database entry from a nscd database. This
281 * function is not MT safe. The caller should lock the
282 * database to prevent concurrent updates done by other
283 * threads.
284 */
285 nscd_rc_t
_nscd_delete_db_entry(nscd_db_t * db,int type,const char * str,nscd_db_option_t option,int id_num)286 _nscd_delete_db_entry(
287 nscd_db_t *db,
288 int type,
289 const char *str,
290 nscd_db_option_t option,
291 int id_num)
292 {
293 int i;
294 int del_more = 0;
295 unsigned long hash;
296 nscd_hash_t *idx_p, *next_p = NULL, *prev_p = NULL;
297 nscd_db_entry_t *db_entry;
298
299 /* find the bucket */
300 hash = calc_hash(str);
301 i = hash % db->array_size;
302 idx_p = db->hash_tbl_p[i];
303
304 /* delete nothing always works */
305 if (idx_p == NULL)
306 return (NSCD_SUCCESS);
307
308 while (idx_p != NULL) {
309 db_entry = &idx_p->db_entry;
310 if (db_entry->type != type)
311 goto cont;
312 if (strcasecmp(db_entry->name, str) != 0)
313 goto cont;
314
315 switch (option) {
316
317 case NSCD_DEL_FIRST_DB_ENTRY:
318 prev_p = idx_p->prev_p;
319 next_p = idx_p->next_p;
320 del_more = 0;
321 break;
322
323 case NSCD_DEL_EXACT_DB_ENTRY:
324 if (db_entry->id_num == id_num) {
325 prev_p = idx_p->prev_p;
326 next_p = idx_p->next_p;
327 del_more = 0;
328 } else
329 goto cont;
330 break;
331
332 case NSCD_DEL_ALL_DB_ENTRY:
333 prev_p = idx_p->prev_p;
334 next_p = idx_p->next_p;
335 break;
336 }
337
338 if (prev_p == NULL)
339 db->hash_tbl_p[i] = next_p;
340 else
341 prev_p->next_p = next_p;
342
343 if (next_p != NULL)
344 next_p->prev_p = prev_p;
345
346 free(idx_p);
347
348 if (del_more == 0)
349 break;
350 /*
351 * only when option == NSCD_DEL_ALL_DB_ENTRY
352 * will we get here. next_p should be set to
353 * idx_p->next_p beforehand
354 */
355 idx_p = next_p;
356 continue;
357
358 cont:
359
360 idx_p = idx_p->next_p;
361 }
362
363 return (NSCD_SUCCESS);
364 }
365
366 /*
367 * FUNCTION: _nscd_alloc_db_entry
368 *
369 * Allocate and return the memory for a database entry
370 * so the caller can insert data and then add it to the
371 * database.
372 */
373 nscd_db_entry_t *
_nscd_alloc_db_entry(int type,const char * name,int dataSize,int num_data,int num_array)374 _nscd_alloc_db_entry(
375 int type,
376 const char *name,
377 int dataSize,
378 int num_data,
379 int num_array)
380 {
381 int size;
382 int array_o, data_o;
383 nscd_hash_t *hash;
384 void *p;
385
386 /* first part: hash data structure and name string */
387 size = sizeof (*hash) + strlen(name) + 1;
388 array_o = size = roundup(size);
389
390 /* second part: pointer array to data */
391 size += (num_data + num_array) * sizeof (void **);
392 size = roundup(size);
393
394 /* third part: actual data */
395 data_o = size;
396 size += dataSize;
397
398 /* allocate the memory */
399 hash = (nscd_hash_t *)calloc(1, size);
400
401 if (hash == NULL)
402 return (NULL);
403
404 /* init the entry based on caller's input */
405 hash->db_entry.num_data = num_data;
406 hash->db_entry.num_array = num_array;
407 hash->db_entry.type = type;
408 hash->db_entry.name = (char *)hash + sizeof (*hash);
409 p = (char *)hash + array_o;
410 hash->db_entry.data_array = (void **)p;
411 *(hash->db_entry.data_array) = (char *)hash + data_o;
412 (void) strcpy(hash->db_entry.name, name);
413
414 return (&hash->db_entry);
415 }
416
417 /*
418 * FUNCTION: _nscd_delete_db_entry_cookie
419 *
420 * Delete a database entry using the information from
421 * the input 'cookie'.
422 */
423 void
_nscd_delete_db_entry_cookie(nscd_db_t * db,void ** cookie)424 _nscd_delete_db_entry_cookie(
425 nscd_db_t *db,
426 void **cookie)
427 {
428 nscd_hash_t *hp;
429 struct cookie *c;
430
431 /* snaity check */
432 if (cookie == NULL || *cookie == NULL || db == NULL)
433 return;
434 c = *cookie;
435
436 /* more snaity check */
437 if (db != c->db || c->hash == NULL ||
438 c->idx < 0 || c->idx >= db->array_size)
439 return;
440
441 /* retrieve the hash entry from the cookie */
442 hp = c->hash;
443
444 /*
445 * Update the next/previous link list.
446 * Need to update c->hash as well, in case
447 * the cookie is also used in a walk-db
448 * loop. This is to make sure that the
449 * next _nscd_walk_db() call will
450 * find the (updated) next hash entry in line.
451 */
452 if (hp->prev_p == NULL) {
453 /*
454 * make sure the current bucket will be
455 * walked again if _nscd_walk_db is
456 * called next
457 */
458 c->hash = NULL;
459 db->hash_tbl_p[c->idx] = hp->next_p;
460 c->idx--;
461
462 } else {
463 c->hash = hp->prev_p;
464 hp->prev_p->next_p = hp->next_p;
465 }
466 if (hp->next_p != NULL)
467 hp->next_p->prev_p = hp->prev_p;
468
469 /* delete the entry */
470 free(hp);
471 }
472
473 /*
474 * FUNCTION: _nscd_alloc_db
475 *
476 * Allocate the space for a nscd database.
477 *
478 * The input argument, size, indicates the size of the database.
479 * NSCD_DB_SIZE_LARGE specifies an bucket array of size 67,
480 * NSCD_DB_SIZE_MEDIUM specifies an bucket array of size 37,
481 * NSCD_DB_SIZE_SMALL specifies an bucket array of size 13,
482 * NSCD_DB_SIZE_TINY specifies an bucket array of size 3.
483 */
484 nscd_db_t *
_nscd_alloc_db(int size)485 _nscd_alloc_db(
486 int size)
487 {
488 int sz;
489 nscd_db_t *db;
490
491 /* allocate the database */
492 db = (nscd_db_t *)calloc(1, sizeof (nscd_db_t));
493 if (db == NULL)
494 return (NULL);
495
496 /* allocate the bucket array */
497 if (size == NSCD_DB_SIZE_LARGE)
498 sz = 67;
499 else if (size == NSCD_DB_SIZE_MEDIUM)
500 sz = 37;
501 else if (size == NSCD_DB_SIZE_SMALL)
502 sz = 13;
503 else if (size == NSCD_DB_SIZE_TINY)
504 sz = 3;
505 db->hash_tbl_p = (nscd_hash_t **)calloc(sz + 1,
506 sizeof (nscd_hash_t *));
507 if (db->hash_tbl_p == NULL) {
508 free(db);
509 return (NULL);
510 }
511
512 db->array_size = sz;
513
514 return (db);
515 }
516
517 /*
518 * FUNCTION: _nscd_free_db
519 *
520 * Delete a nscd database.
521 */
522 void
_nscd_free_db(nscd_db_t * db)523 _nscd_free_db(
524 nscd_db_t *db)
525 {
526 int i;
527 nscd_hash_t *hp, *next_p;
528
529 /*
530 * find non-empty buckets and for each one of them,
531 * delete all the database entries in it's link list
532 */
533 for (i = 0; i < db->array_size; i++) {
534
535 hp = db->hash_tbl_p[i];
536
537 while (hp != NULL) {
538 next_p = hp->next_p;
539 free(hp);
540 hp = next_p;
541 }
542 }
543
544 /* free the bucket array */
545 free(db->hash_tbl_p);
546
547 free(db);
548 }
549
550 /*
551 * FUNCTION: _nscd_walk_db
552 *
553 * Iterate through the database entries contained in
554 * a nscd database and return one entry at a time.
555 * The cookie structure is used to indicate the
556 * location of the returned entry for the next call
557 * to this function. For the very first call, *cookie
558 * should be set to NULL. For subsequent calls, the one
559 * returned by the previous call sould be used.
560 */
561 nscd_db_entry_t *
_nscd_walk_db(nscd_db_t * db,void ** cookie)562 _nscd_walk_db(
563 nscd_db_t *db,
564 void **cookie)
565 {
566 struct cookie *c;
567
568 /* sanity check */
569 if (cookie == NULL || db == NULL)
570 return (NULL);
571
572 if (*cookie != NULL) {
573
574 c = *cookie;
575
576 /*
577 * More sanity check. _nscd_delete_db_entry_cookie()
578 * could change c->idx to -1.
579 */
580 if (db != c->db ||
581 c->idx < -1 || c->idx >= db->array_size)
582 return (NULL);
583
584 /* is there a next entry ? */
585 if (c->hash != NULL)
586 c->hash = c->hash->next_p;
587
588 /* yes, return it */
589 if (c->hash != NULL) {
590 return (&c->hash->db_entry);
591 }
592 } else {
593 c = (struct cookie *)calloc(1, sizeof (*c));
594 if (c == NULL)
595 return (NULL);
596 c->idx = -1;
597 c->hash = NULL;
598 c->db = db;
599 }
600
601 /* find the first non-empty bucket */
602 for (c->idx++; c->idx < db->array_size; c->idx++) {
603 c->hash = db->hash_tbl_p[c->idx];
604 if (c->hash != NULL)
605 break;
606 }
607
608 /* no (more) non-empty bucket, we are done */
609 if (c->hash == NULL) {
610 free(c);
611 *cookie = NULL;
612 return (NULL);
613 }
614
615 *cookie = c;
616 return (&c->hash->db_entry);
617 }
618