/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "nscd_db.h" /* * This file implements the database functionality used by the * switch and configuration components. The implementation is * based on hash and has table. If the need arises in the future, * the code in this file can be changed to use other algorithms. * The following lists the functions implemented: * * _nscd_add_db_entry * _nscd_delete_db_entry * _nscd_get_db_entry * _nscd_alloc_db * _nscd_free_db * _nscd_walk_db * _nscd_delete_db_entry_cookie */ /* * This structure defines an instance of the hash entry * which implements the nscd database entry. The * db_entry field should always be the first one in * the structure. */ typedef struct nscd_hash { nscd_db_entry_t db_entry; struct nscd_hash *next_p; struct nscd_hash *prev_p; } nscd_hash_t; /* * This structure defines a nscd database which * is implemented as an array of nscd_hash_t. */ struct nscd_db_s { int array_size; /* number of elements in hash_tbl_p */ nscd_hash_t **hash_tbl_p; }; /* * This cookie structure is used to iterate through the * database entries contained in a nscd database. */ struct cookie { int idx; /* the current bucket */ nscd_hash_t *hash; /* the current hash entry */ nscd_db_t *db; /* the database */ }; /* * FUNCTION: calc_hash * * Calculate a hash for a string based on the elf_hash * algorithm, hash is case insensitive. Uses tolower * instead of _tolower because of I18N. */ static unsigned long calc_hash( const char *str) { unsigned int hval = 0; char ch; while (*str != '\0') { unsigned int g; ch = (char)*str++; if (isupper(ch)) ch = _tolower(ch); hval = (hval << 4) + ch; if ((g = (hval & 0xf0000000)) != 0) hval ^= g >> 24; hval &= ~g; } return ((unsigned long)hval); } /* * FUNCTION: scan_hash * * Scan a hash table for a matching hash entry. Assume 'str' is * not NULL. If option is NSCD_GET_NEXT_DB_ENTRY and id_num * is less than zero, then treats the option as NSCD_GET_FIRST_DB_ENTRY. */ 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) { int id_matched = 0; nscd_db_entry_t *db_entry; while (idx_p != NULL) { db_entry = &((nscd_hash_t *)idx_p)->db_entry; if (db_entry->type == type) { if (strcasecmp(str, db_entry->name) == 0) { switch (option) { case NSCD_GET_FIRST_DB_ENTRY: return (idx_p); case NSCD_GET_EXACT_DB_ENTRY: if (id_num == db_entry->id_num) return (idx_p); break; case NSCD_GET_NEXT_DB_ENTRY: if (id_num < 0) return (idx_p); if (id_matched == 1) return (idx_p); if (id_num == db_entry->id_num) id_matched = 1; break; } } } idx_p = idx_p->next_p; } return (NULL); } /* * FUNCTION: _nscd_get_db_entry * * Find a nscd database entry from a nscd database. */ 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) { unsigned long hash; const nscd_hash_t *idx_p, *hash_p; if (db == NULL || str == NULL) return (NULL); hash = calc_hash(str); idx_p = db->hash_tbl_p[hash % db->array_size]; hash_p = scan_hash(type, str, idx_p, option, id_num); return (&hash_p->db_entry); } /* * FUNCTION: _nscd_add_db_entry * * Add a nscd database entry to a nscd database. This function * is not MT safe. The caller should lock the database to * prevent concurrent updates done by other threads. */ nscd_rc_t _nscd_add_db_entry( nscd_db_t *db, const char *str, nscd_db_entry_t *entry, nscd_db_option_t option) { int i; unsigned long hash; nscd_hash_t *next_p = NULL, *prev_p = NULL; nscd_hash_t *idx_p, *hash_entry; nscd_db_entry_t *db_entry; /* find the bucket */ hash = calc_hash(str); i = hash % db->array_size; idx_p = db->hash_tbl_p[i]; /* can not replace nothing */ if (idx_p == NULL) if (option == NSCD_ADD_DB_ENTRY_REPLACE) return (NSCD_DB_ENTRY_NOT_FOUND); while (idx_p != NULL) { db_entry = &idx_p->db_entry; switch (option) { case NSCD_ADD_DB_ENTRY_FIRST: next_p = idx_p; goto add_entry; case NSCD_ADD_DB_ENTRY_REPLACE: if (db_entry->type != entry->type) goto cont; if (strcasecmp(db_entry->name, str) != 0) goto cont; if (db_entry->id_num == entry->id_num) { prev_p = idx_p->prev_p; next_p = idx_p->next_p; free(idx_p); goto add_entry; } goto cont; case NSCD_ADD_DB_ENTRY_IF_NONE: if (db_entry->type != entry->type) break; if (strcasecmp(db_entry->name, str) != 0) break; return (NSCD_DB_ENTRY_FOUND); } if (idx_p->next_p == NULL) { if (option == NSCD_ADD_DB_ENTRY_LAST || option == NSCD_ADD_DB_ENTRY_IF_NONE) { prev_p = idx_p; goto add_entry; } } cont: idx_p = idx_p->next_p; } add_entry: /* * the nscd_entry_t field should be the first field * in a nscd_hash_t */ hash_entry = (nscd_hash_t *)entry; /* update the prev link list */ hash_entry->prev_p = prev_p; if (prev_p == NULL) db->hash_tbl_p[i] = hash_entry; else prev_p->next_p = hash_entry; /* update the next link list */ hash_entry->next_p = next_p; if (next_p != NULL) next_p->prev_p = hash_entry; return (NSCD_SUCCESS); } /* * FUNCTION: _nscd_delete_db_entry * * Delete a nscd database entry from a nscd database. This * function is not MT safe. The caller should lock the * database to prevent concurrent updates done by other * threads. */ nscd_rc_t _nscd_delete_db_entry( nscd_db_t *db, int type, const char *str, nscd_db_option_t option, int id_num) { int i; int del_more = 0; unsigned long hash; nscd_hash_t *idx_p, *next_p = NULL, *prev_p = NULL; nscd_db_entry_t *db_entry; /* find the bucket */ hash = calc_hash(str); i = hash % db->array_size; idx_p = db->hash_tbl_p[i]; /* delete nothing always works */ if (idx_p == NULL) return (NSCD_SUCCESS); while (idx_p != NULL) { db_entry = &idx_p->db_entry; if (db_entry->type != type) goto cont; if (strcasecmp(db_entry->name, str) != 0) goto cont; switch (option) { case NSCD_DEL_FIRST_DB_ENTRY: prev_p = idx_p->prev_p; next_p = idx_p->next_p; del_more = 0; break; case NSCD_DEL_EXACT_DB_ENTRY: if (db_entry->id_num == id_num) { prev_p = idx_p->prev_p; next_p = idx_p->next_p; del_more = 0; } else goto cont; break; case NSCD_DEL_ALL_DB_ENTRY: prev_p = idx_p->prev_p; next_p = idx_p->next_p; break; } if (prev_p == NULL) db->hash_tbl_p[i] = next_p; else prev_p->next_p = next_p; if (next_p != NULL) next_p->prev_p = prev_p; free(idx_p); if (del_more == 0) break; /* * only when option == NSCD_DEL_ALL_DB_ENTRY * will we get here. next_p should be set to * idx_p->next_p beforehand */ idx_p = next_p; continue; cont: idx_p = idx_p->next_p; } return (NSCD_SUCCESS); } /* * FUNCTION: _nscd_alloc_db_entry * * Allocate and return the memory for a database entry * so the caller can insert data and then add it to the * database. */ nscd_db_entry_t * _nscd_alloc_db_entry( int type, const char *name, int dataSize, int num_data, int num_array) { int size; int array_o, data_o; nscd_hash_t *hash; void *p; /* first part: hash data structure and name string */ size = sizeof (*hash) + strlen(name) + 1; array_o = size = roundup(size); /* second part: pointer array to data */ size += (num_data + num_array) * sizeof (void **); size = roundup(size); /* third part: actual data */ data_o = size; size += dataSize; /* allocate the memory */ hash = (nscd_hash_t *)calloc(1, size); if (hash == NULL) return (NULL); /* init the entry based on caller's input */ hash->db_entry.num_data = num_data; hash->db_entry.num_array = num_array; hash->db_entry.type = type; hash->db_entry.name = (char *)hash + sizeof (*hash); p = (char *)hash + array_o; hash->db_entry.data_array = (void **)p; *(hash->db_entry.data_array) = (char *)hash + data_o; (void) strcpy(hash->db_entry.name, name); return (&hash->db_entry); } /* * FUNCTION: _nscd_delete_db_entry_cookie * * Delete a database entry using the information from * the input 'cookie'. */ void _nscd_delete_db_entry_cookie( nscd_db_t *db, void **cookie) { nscd_hash_t *hp; struct cookie *c; /* snaity check */ if (cookie == NULL || *cookie == NULL || db == NULL) return; c = *cookie; /* more snaity check */ if (db != c->db || c->hash == NULL || c->idx < 0 || c->idx >= db->array_size) return; /* retrieve the hash entry from the cookie */ hp = c->hash; /* * Update the next/previous link list. * Need to update c->hash as well, in case * the cookie is also used in a walk-db * loop. This is to make sure that the * next _nscd_walk_db() call will * find the (updated) next hash entry in line. */ if (hp->prev_p == NULL) { /* * make sure the current bucket will be * walked again if _nscd_walk_db is * called next */ c->hash = NULL; db->hash_tbl_p[c->idx] = hp->next_p; c->idx--; } else { c->hash = hp->prev_p; hp->prev_p->next_p = hp->next_p; } if (hp->next_p != NULL) hp->next_p->prev_p = hp->prev_p; /* delete the entry */ free(hp); } /* * FUNCTION: _nscd_alloc_db * * Allocate the space for a nscd database. * * The input argument, size, indicates the size of the database. * NSCD_DB_SIZE_LARGE specifies an bucket array of size 67, * NSCD_DB_SIZE_MEDIUM specifies an bucket array of size 37, * NSCD_DB_SIZE_SMALL specifies an bucket array of size 13, * NSCD_DB_SIZE_TINY specifies an bucket array of size 3. */ nscd_db_t * _nscd_alloc_db( int size) { int sz; nscd_db_t *db; /* allocate the database */ db = (nscd_db_t *)calloc(1, sizeof (nscd_db_t)); if (db == NULL) return (NULL); /* allocate the bucket array */ if (size == NSCD_DB_SIZE_LARGE) sz = 67; else if (size == NSCD_DB_SIZE_MEDIUM) sz = 37; else if (size == NSCD_DB_SIZE_SMALL) sz = 13; else if (size == NSCD_DB_SIZE_TINY) sz = 3; db->hash_tbl_p = (nscd_hash_t **)calloc(sz + 1, sizeof (nscd_hash_t *)); if (db->hash_tbl_p == NULL) { free(db); return (NULL); } db->array_size = sz; return (db); } /* * FUNCTION: _nscd_free_db * * Delete a nscd database. */ void _nscd_free_db( nscd_db_t *db) { int i; nscd_hash_t *hp, *next_p; /* * find non-empty buckets and for each one of them, * delete all the database entries in it's link list */ for (i = 0; i < db->array_size; i++) { hp = db->hash_tbl_p[i]; while (hp != NULL) { next_p = hp->next_p; free(hp); hp = next_p; } } /* free the bucket array */ free(db->hash_tbl_p); free(db); } /* * FUNCTION: _nscd_walk_db * * Iterate through the database entries contained in * a nscd database and return one entry at a time. * The cookie structure is used to indicate the * location of the returned entry for the next call * to this function. For the very first call, *cookie * should be set to NULL. For subsequent calls, the one * returned by the previous call sould be used. */ nscd_db_entry_t * _nscd_walk_db( nscd_db_t *db, void **cookie) { struct cookie *c; /* sanity check */ if (cookie == NULL || db == NULL) return (NULL); if (*cookie != NULL) { c = *cookie; /* * More sanity check. _nscd_delete_db_entry_cookie() * could change c->idx to -1. */ if (db != c->db || c->idx < -1 || c->idx >= db->array_size) return (NULL); /* is there a next entry ? */ if (c->hash != NULL) c->hash = c->hash->next_p; /* yes, return it */ if (c->hash != NULL) { return (&c->hash->db_entry); } } else { c = (struct cookie *)calloc(1, sizeof (*c)); if (c == NULL) return (NULL); c->idx = -1; c->hash = NULL; c->db = db; } /* find the first non-empty bucket */ for (c->idx++; c->idx < db->array_size; c->idx++) { c->hash = db->hash_tbl_p[c->idx]; if (c->hash != NULL) break; } /* no (more) non-empty bucket, we are done */ if (c->hash == NULL) { free(c); *cookie = NULL; return (NULL); } *cookie = c; return (&c->hash->db_entry); }