dapl_hash.c revision 9517:b4839b0aa7a4
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/* 23 * Copyright (c) 2002-2003, Network Appliance, Inc. All rights reserved. 24 */ 25 26/* 27 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 28 * Use is subject to license terms. 29 */ 30 31/* 32 * 33 * MODULE: dapl_hash.c 34 * 35 * PURPOSE: Hash Table 36 * Description: 37 * 38 * Provides a generic hash table with chaining. 39 * 40 * $Id: dapl_hash.c,v 1.10 2003/07/11 18:42:17 hobie16 Exp $ 41 */ 42 43#include "dapl_hash.h" 44 45/* 46 * 47 * Structures 48 * 49 */ 50 51/* 52 * A hash table element 53 */ 54typedef struct DAPL_HASH_ELEM 55{ 56 struct DAPL_HASH_ELEM *next_element; 57 DAPL_HASH_KEY key; 58 void *datum; 59} DAPL_HASH_ELEM; 60 61/* 62 * The hash table 63 */ 64struct dapl_hash_table 65{ 66 unsigned long num_entries; 67 unsigned long tbl_size; 68 DAPL_HASH_ELEM *table; 69 DAT_BOOLEAN locking_required; /* internal locking reqd */ 70 DAPL_OS_LOCK lock; 71 unsigned long iterator_bucket; 72 DAPL_HASH_ELEM *iterator; 73 /* 74 * statistics - we tally on insert operations, counting 75 * the number of entries in the whole hash table, as 76 * well as the length of chains we walk to insert. This 77 * ignores empty buckets, giving us data on overall table 78 * occupancy, as well as max/average chain length for 79 * the buckets used. If our hash function results in 80 * hot buckets, this will show it. 81 */ 82 uint64_t hash_tbl_inserts; /* total inserts ops */ 83 uint64_t hash_tbl_max; /* max in entire table */ 84 uint64_t hash_tbl_total; /* total in table */ 85 uint64_t hash_chn_max; /* longest chain */ 86 uint64_t hash_chn_total; /* total non-0 lenghts */ 87}; 88 89 90/* 91 * 92 * Defines 93 * 94 */ 95 96/* The hash function */ 97#define DAPL_DOHASH(key, hashsize) ((uint64_t)((key) % (hashsize))) 98 99/* datum value in empty table slots (use 0UL or ~0UL as appropriate) */ 100#define NO_DATUM_VALUE ((void *) 0UL) 101#define NO_DATUM(value) ((value) == NO_DATUM_VALUE) 102 103/* Lookup macro (which falls back to function to rehash) */ 104#define DAPL_HASHLOOKUP(p_table, in_key, out_datum, bucket_head) \ 105 { \ 106 DAPL_HASH_ELEM *element = \ 107 &((p_table)->table)[DAPL_DOHASH(in_key, (p_table)->tbl_size)]; \ 108 if (NO_DATUM(element->datum)) { \ 109 (bucket_head) = (void *)0; \ 110 } else if (element->key == (DAPL_HASH_KEY) (in_key)) { \ 111 (out_datum) = element->datum; \ 112 (bucket_head) = (void *)element; \ 113 } else if (element->next_element) { \ 114 dapli_hash_rehash(element, \ 115 (in_key), \ 116 (void **)&(out_datum), \ 117 (DAPL_HASH_ELEM **)&(bucket_head)); \ 118 } else { \ 119 (bucket_head) = (void *)0; \ 120 }\ 121 } 122 123 124/* 125 * 126 * Internal Functions 127 * 128 */ 129 130/* 131 * Rehash the key (used by add and lookup functions) 132 * 133 * Inputs: element element to rehash key 134 * key, datum datum for key head 135 * head head for list 136 */ 137static void 138dapli_hash_rehash( 139 DAPL_HASH_ELEM *element, 140 DAPL_HASH_KEY key, 141 void **datum, 142 DAPL_HASH_ELEM ** head) 143{ 144 /* 145 * assume we looked at the contents of element already, 146 * and start with the next element. 147 */ 148 dapl_os_assert(element->next_element); 149 dapl_os_assert(!NO_DATUM(element->datum)); 150 151 *head = element; 152 /*CONSTCOND*/ 153 while (1) { 154 element = element->next_element; 155 if (!element) { 156 break; 157 } 158 if (element->key == key) { 159 *datum = element->datum; 160 return; 161 } 162 } 163 *head = 0; 164} 165 166/* 167 * Add a new key to the hash table 168 * 169 * Inputs: 170 * table, key and datum to be added 171 * allow_dup - DAT_TRUE if dups are allowed 172 * Outputs: 173 * report_dup - should you care to know 174 * Returns: 175 * DAT_TRUE on success 176 */ 177static DAT_BOOLEAN 178dapli_hash_add( 179 DAPL_HASH_TABLEP p_table, 180 DAPL_HASH_KEY key, 181 void *datum, 182 DAT_BOOLEAN allow_dup, 183 DAT_BOOLEAN *report_dup) 184{ 185 void *olddatum; 186 DAPL_HASH_KEY hashValue; 187 DAPL_HASH_ELEM *found; 188 DAT_BOOLEAN status = DAT_FALSE; 189 unsigned int chain_len = 0; 190 191 if (report_dup) { 192 (*report_dup) = DAT_FALSE; 193 } 194 195 if (NO_DATUM(datum)) { 196 /* 197 * Reserved value used for datum 198 */ 199 dapl_dbg_log( 200 DAPL_DBG_TYPE_ERR, 201 "dapli_hash_add () called with magic NO_DATA value " 202 "(%p) used as datum!\n", datum); 203 return (DAT_FALSE); 204 } 205 206 DAPL_HASHLOOKUP(p_table, key, olddatum, found); 207 208 if (found) { 209 /* 210 * key exists already 211 */ 212 if (report_dup) { 213 *report_dup = DAT_TRUE; 214 } 215 216 if (!allow_dup) { 217 dapl_dbg_log(DAPL_DBG_TYPE_ERR, 218 "dapli_hash_add () called with duplicate key " 219 "(" F64x ")\n", key); 220 return (DAT_FALSE); 221 } 222 } 223 224 hashValue = DAPL_DOHASH(key, p_table->tbl_size); 225 if (NO_DATUM(p_table->table[hashValue].datum)) { 226 /* 227 * Empty head - just fill it in 228 */ 229 p_table->table[hashValue].key = key; 230 p_table->table[hashValue].datum = datum; 231 p_table->table[hashValue].next_element = 0; 232 p_table->num_entries++; 233 status = DAT_TRUE; 234 } else { 235 DAPL_HASH_ELEM *newelement = (DAPL_HASH_ELEM *) 236 dapl_os_alloc(sizeof (DAPL_HASH_ELEM)); 237 /* 238 * Add an element to the end of the chain 239 */ 240 if (newelement) { 241 DAPL_HASH_ELEM *lastelement; 242 newelement->key = key; 243 newelement->datum = datum; 244 newelement->next_element = 0; 245 for (lastelement = &p_table->table[hashValue]; 246 lastelement->next_element; 247 lastelement = lastelement->next_element) { 248 /* Walk to the end of the chain */ 249 chain_len++; 250 } 251 lastelement->next_element = newelement; 252 p_table->num_entries++; 253 status = DAT_TRUE; 254 } else { 255 /* allocation failed - should not happen */ 256 status = DAT_FALSE; 257 } 258 } 259 260 /* 261 * Tally up our counters. chain_len is one less than current chain 262 * length. 263 */ 264 chain_len++; 265 p_table->hash_tbl_inserts++; 266 p_table->hash_tbl_total += p_table->num_entries; 267 p_table->hash_chn_total += chain_len; 268 if (p_table->num_entries > p_table->hash_tbl_max) { 269 p_table->hash_tbl_max = p_table->num_entries; 270 } 271 if (chain_len > p_table->hash_chn_max) { 272 p_table->hash_chn_max = chain_len; 273 } 274 275 return (status); 276} 277 278 279/* 280 * Remove element from hash bucket 281 * 282 * Inputs: 283 * element, key to be deleted 284 * Returns: 285 * DAT_TRUE on success 286 */ 287static DAT_BOOLEAN 288dapl_hash_delete_element(DAPL_HASH_ELEM * element, 289 DAPL_HASH_KEY key, 290 DAPL_HASH_DATA *p_datum) 291{ 292 DAPL_HASH_ELEM *curelement; 293 DAPL_HASH_ELEM *lastelement; 294 295 lastelement = NULL; 296 for (curelement = element; curelement; 297 lastelement = curelement, 298 curelement = curelement->next_element) { 299 if (curelement->key == key) { 300 if (p_datum) { 301 *p_datum = curelement->datum; 302 } 303 if (lastelement) { 304 /* 305 * curelement was malloc'd; free it 306 */ 307 lastelement->next_element = 308 curelement->next_element; 309 dapl_os_free((void *) curelement, 310 sizeof (DAPL_HASH_ELEM)); 311 } else { 312 /* 313 * curelement is static list head 314 */ 315 DAPL_HASH_ELEM *n = curelement->next_element; 316 if (n) { 317 /* 318 * If there is a next element, copy its 319 * contents into the head and free the 320 * original next element. 321 */ 322 curelement->key = n->key; 323 curelement->datum = n->datum; 324 curelement->next_element = 325 n->next_element; 326 dapl_os_free((void *) n, 327 sizeof (DAPL_HASH_ELEM)); 328 } else { 329 curelement->datum = NO_DATUM_VALUE; 330 } 331 } 332 break; 333 } 334 } 335 336 return (curelement != NULL ? DAT_TRUE : DAT_FALSE); 337} 338 339 340/* 341 * 342 * External Functions 343 * 344 */ 345 346 347/* 348 * Create a new hash table with at least 'table_size' hash buckets. 349 */ 350DAT_RETURN 351dapls_hash_create( 352 IN DAT_COUNT table_size, 353 IN DAT_BOOLEAN locking_required, 354 OUT DAPL_HASH_TABLE **pp_table) 355{ 356 DAPL_HASH_TABLE *p_table; 357 DAT_COUNT table_length = table_size * sizeof (DAPL_HASH_ELEM); 358 DAT_RETURN dat_status; 359 DAT_COUNT i; 360 361 dapl_os_assert(pp_table); 362 dat_status = DAT_SUCCESS; 363 364 /* Allocate hash table */ 365 p_table = dapl_os_alloc(sizeof (DAPL_HASH_TABLE)); 366 if (NULL == p_table) { 367 dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES, 368 DAT_RESOURCE_MEMORY); 369 goto bail; 370 } 371 372 /* Init hash table, allocate and init and buckets */ 373 (void) dapl_os_memzero(p_table, sizeof (DAPL_HASH_TABLE)); 374 p_table->tbl_size = table_size; 375 p_table->table = (DAPL_HASH_ELEM *)dapl_os_alloc(table_length); 376 if (NULL == p_table->table) { 377 dapl_os_free(p_table, sizeof (DAPL_HASH_TABLE)); 378 dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES, 379 DAT_RESOURCE_MEMORY); 380 goto bail; 381 } 382 /* Init the lock anyways */ 383 dapl_os_lock_init(&p_table->lock); 384 p_table->locking_required = locking_required; 385 386 for (i = 0; i < table_size; i++) { 387 p_table->table[i].datum = NO_DATUM_VALUE; 388 p_table->table[i].key = 0; 389 p_table->table[i].next_element = 0; 390 } 391 392 *pp_table = p_table; 393 394bail: 395 return (dat_status); 396} 397 398 399/* 400 * Destroy a hash table 401 */ 402DAT_RETURN 403dapls_hash_free( 404 IN DAPL_HASH_TABLE *p_table) 405{ 406 dapl_os_assert(p_table && p_table->table); 407 408 dapl_os_lock_destroy(&p_table->lock); 409 dapl_os_free(p_table->table, 410 sizeof (DAPL_HASH_ELEM) * p_table->tbl_size); 411 dapl_os_free(p_table, sizeof (DAPL_HASH_TABLE)); 412 413 return (DAT_SUCCESS); 414} 415 416 417/* 418 * Returns the number of elements stored in the table 419 */ 420 421DAT_RETURN 422dapls_hash_size( 423 IN DAPL_HASH_TABLE *p_table, 424 OUT DAT_COUNT *p_size) 425{ 426 dapl_os_assert(p_table && p_size); 427 428 *p_size = p_table->num_entries; 429 430 return (DAT_SUCCESS); 431} 432 433 434/* 435 * Inserts the specified data into the table with the given key. 436 * Duplicates are not expected, and return in error, having done nothing. 437 */ 438 439DAT_RETURN 440dapls_hash_insert( 441 IN DAPL_HASH_TABLE *p_table, 442 IN DAPL_HASH_KEY key, 443 IN DAPL_HASH_DATA data) 444{ 445 DAT_RETURN dat_status; 446 447 dapl_os_assert(p_table); 448 dat_status = DAT_SUCCESS; 449 450 if (p_table->locking_required) { 451 dapl_os_lock(&p_table->lock); 452 } 453 454 if (!dapli_hash_add(p_table, key, data, DAT_FALSE, NULL)) { 455 dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES, 456 DAT_RESOURCE_MEMORY); 457 } 458 459 if (p_table->locking_required) { 460 dapl_os_unlock(&p_table->lock); 461 } 462 463 return (dat_status); 464} 465 466 467/* 468 * Searches for the given key. If found, 469 * DAT_SUCCESS is returned and the associated 470 * data is returned in the DAPL_HASH_DATA 471 * pointer if that pointer is not NULL. 472 */ 473DAT_RETURN 474dapls_hash_search( 475 IN DAPL_HASH_TABLE *p_table, 476 IN DAPL_HASH_KEY key, 477 OUT DAPL_HASH_DATA *p_data) 478{ 479 DAT_RETURN dat_status; 480 void *olddatum; 481 DAPL_HASH_ELEM *found; 482 483 dapl_os_assert(p_table); 484 dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, 0); 485 486 if (p_table->locking_required) { 487 dapl_os_lock(&p_table->lock); 488 DAPL_HASHLOOKUP(p_table, key, olddatum, found); 489 dapl_os_unlock(&p_table->lock); 490 } else { 491 DAPL_HASHLOOKUP(p_table, key, olddatum, found); 492 } 493 494 if (found) { 495 if (p_data) { 496 *p_data = olddatum; 497 } 498 dat_status = DAT_SUCCESS; 499 } 500 501 return (dat_status); 502} 503 504 505DAT_RETURN 506dapls_hash_remove( 507 IN DAPL_HASH_TABLE *p_table, 508 IN DAPL_HASH_KEY key, 509 OUT DAPL_HASH_DATA *p_data) 510{ 511 DAT_RETURN dat_status; 512 DAPL_HASH_KEY hashValue; 513 514 dapl_os_assert(p_table); 515 dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, 0); 516 517 if (p_table->num_entries == 0) { 518 dapl_dbg_log(DAPL_DBG_TYPE_ERR, 519 "dapls_hash_remove () called on empty hash table!\n"); 520 return (dat_status); 521 } 522 523 hashValue = DAPL_DOHASH(key, p_table->tbl_size); 524 if (p_table->locking_required) { 525 dapl_os_lock(&p_table->lock); 526 } 527 if (dapl_hash_delete_element(&p_table->table[hashValue], key, p_data)) { 528 p_table->num_entries--; 529 dat_status = DAT_SUCCESS; 530 } 531 if (p_table->locking_required) { 532 dapl_os_unlock(&p_table->lock); 533 } 534 return (dat_status); 535} 536/* 537 * Iterates through the entire hash table return one element at a time. 538 * Note: this is not a threadsafe routine and hence consumers that 539 * rely on the hash-tables internal locking are not allowed to use this. 540 */ 541DAT_RETURN 542dapls_hash_iterate( 543 IN DAPL_HASH_TABLE *p_table, 544 IN DAPL_HASH_ITERATOR op, 545 OUT DAPL_HASH_DATA *p_data) 546{ 547 DAPL_HASH_ELEM *curr; 548 549 dapl_os_assert(p_table); 550 /* 551 * sorry iterate is supported only for consumers that do their 552 * own locking 553 */ 554 if (p_table->locking_required) { 555 return (DAT_ERROR(DAT_INVALID_PARAMETER, 0)); 556 } 557 if (op == DAPL_HASH_ITERATE_INIT) { 558 /* the hash table is empty */ 559 if (p_table->num_entries == 0) { 560 *p_data = NULL; 561 return (DAT_SUCCESS); 562 } 563 /* find the first bucket with valid data */ 564 p_table->iterator_bucket = 0; 565 while (p_table->iterator_bucket < p_table->tbl_size) { 566 curr = &p_table->table[p_table->iterator_bucket]; 567 if (NO_DATUM(curr->datum)) { 568 /* empty bucket - move on */ 569 p_table->iterator_bucket++; 570 } else { 571 break; 572 } 573 } 574 /* should not be empty if num_entries is non-zero */ 575 dapl_os_assert(!NO_DATUM(curr->datum)); 576 if (p_table->iterator_bucket == p_table->tbl_size) { 577 p_table->iterator = NULL; 578 } else { 579 p_table->iterator = curr; 580 } 581 } else { 582 dapl_os_assert(op == DAPL_HASH_ITERATE_NEXT); 583 } 584 /* iterator points to the element to be returned */ 585 if (p_table->iterator == NULL) { 586 /* nothing more left in the hashtable */ 587 *p_data = NULL; 588 return (DAT_SUCCESS); 589 } 590 591 dapl_dbg_log(DAPL_DBG_TYPE_EP, 592 "dapls_hash_iterate: entry found=(%p), bucket(%d)\n", 593 p_table->iterator, p_table->iterator_bucket); 594 *p_data = p_table->iterator->datum; 595 curr = p_table->iterator; 596 597 /* re-position iterator to point to the next valid element */ 598 if (curr->next_element != NULL) { /* found the next element */ 599 p_table->iterator = curr->next_element; 600 dapl_os_assert(!NO_DATUM(p_table->iterator->datum)); 601 } else { 602 p_table->iterator = NULL; 603 /* 604 * We are here means we've hit the end of the current bucket, 605 * so start searching for next bucket with a valid entry - 606 * we only need to look at the head of the bucket 607 */ 608 p_table->iterator_bucket++; 609 while (p_table->iterator_bucket < p_table->tbl_size) { 610 curr = &p_table->table[p_table->iterator_bucket]; 611 if (NO_DATUM(curr->datum)) { 612 p_table->iterator_bucket++; 613 } else { 614 p_table->iterator = curr; 615 break; 616 } 617 } 618 } 619 return (DAT_SUCCESS); 620} 621