hash.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /*
  2. Trivial Database 2: hash handling
  3. Copyright (C) Rusty Russell 2010
  4. This library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Lesser General Public
  6. License as published by the Free Software Foundation; either
  7. version 3 of the License, or (at your option) any later version.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public
  13. License along with this library; if not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include "private.h"
  16. #include <ccan/hash/hash.h>
  17. /* Default hash function. */
  18. uint32_t ntdb_jenkins_hash(const void *key, size_t length, uint32_t seed,
  19. void *unused)
  20. {
  21. return hash_stable((const unsigned char *)key, length, seed);
  22. }
  23. uint32_t ntdb_hash(struct ntdb_context *ntdb, const void *ptr, size_t len)
  24. {
  25. return ntdb->hash_fn(ptr, len, ntdb->hash_seed, ntdb->hash_data);
  26. }
  27. static ntdb_bool_err key_matches(struct ntdb_context *ntdb,
  28. const struct ntdb_used_record *rec,
  29. ntdb_off_t off,
  30. const NTDB_DATA *key,
  31. const char **rptr)
  32. {
  33. ntdb_bool_err ret = false;
  34. const char *rkey;
  35. if (rec_key_length(rec) != key->dsize) {
  36. ntdb->stats.compare_wrong_keylen++;
  37. return ret;
  38. }
  39. rkey = ntdb_access_read(ntdb, off + sizeof(*rec),
  40. key->dsize + rec_data_length(rec), false);
  41. if (NTDB_PTR_IS_ERR(rkey)) {
  42. return (ntdb_bool_err)NTDB_PTR_ERR(rkey);
  43. }
  44. if (memcmp(rkey, key->dptr, key->dsize) == 0) {
  45. if (rptr) {
  46. *rptr = rkey;
  47. } else {
  48. ntdb_access_release(ntdb, rkey);
  49. }
  50. return true;
  51. }
  52. ntdb->stats.compare_wrong_keycmp++;
  53. ntdb_access_release(ntdb, rkey);
  54. return ret;
  55. }
  56. /* Does entry match? */
  57. static ntdb_bool_err match(struct ntdb_context *ntdb,
  58. uint32_t hash,
  59. const NTDB_DATA *key,
  60. ntdb_off_t val,
  61. struct ntdb_used_record *rec,
  62. const char **rptr)
  63. {
  64. ntdb_off_t off;
  65. enum NTDB_ERROR ecode;
  66. ntdb->stats.compares++;
  67. /* Top bits of offset == next bits of hash. */
  68. if (bits_from(hash, ntdb->hash_bits, NTDB_OFF_UPPER_STEAL)
  69. != bits_from(val, 64-NTDB_OFF_UPPER_STEAL, NTDB_OFF_UPPER_STEAL)) {
  70. ntdb->stats.compare_wrong_offsetbits++;
  71. return false;
  72. }
  73. off = val & NTDB_OFF_MASK;
  74. ecode = ntdb_read_convert(ntdb, off, rec, sizeof(*rec));
  75. if (ecode != NTDB_SUCCESS) {
  76. return (ntdb_bool_err)ecode;
  77. }
  78. return key_matches(ntdb, rec, off, key, rptr);
  79. }
  80. static bool is_chain(ntdb_off_t val)
  81. {
  82. return val & (1ULL << NTDB_OFF_CHAIN_BIT);
  83. }
  84. static ntdb_off_t hbucket_off(ntdb_off_t base, ntdb_len_t idx)
  85. {
  86. return base + sizeof(struct ntdb_used_record)
  87. + idx * sizeof(ntdb_off_t);
  88. }
  89. /* This is the core routine which searches the hashtable for an entry.
  90. * On error, no locks are held and -ve is returned.
  91. * Otherwise, hinfo is filled in.
  92. * If not found, the return value is 0.
  93. * If found, the return value is the offset, and *rec is the record. */
  94. ntdb_off_t find_and_lock(struct ntdb_context *ntdb,
  95. NTDB_DATA key,
  96. int ltype,
  97. struct hash_info *h,
  98. struct ntdb_used_record *rec,
  99. const char **rptr)
  100. {
  101. ntdb_off_t off, val;
  102. const ntdb_off_t *arr = NULL;
  103. ntdb_len_t i;
  104. bool found_empty;
  105. enum NTDB_ERROR ecode;
  106. struct ntdb_used_record chdr;
  107. ntdb_bool_err berr;
  108. h->h = ntdb_hash(ntdb, key.dptr, key.dsize);
  109. h->table = NTDB_HASH_OFFSET;
  110. h->table_size = 1 << ntdb->hash_bits;
  111. h->bucket = bits_from(h->h, 0, ntdb->hash_bits);
  112. h->old_val = 0;
  113. ecode = ntdb_lock_hash(ntdb, h->bucket, ltype);
  114. if (ecode != NTDB_SUCCESS) {
  115. return NTDB_ERR_TO_OFF(ecode);
  116. }
  117. off = hbucket_off(h->table, h->bucket);
  118. val = ntdb_read_off(ntdb, off);
  119. if (NTDB_OFF_IS_ERR(val)) {
  120. ecode = NTDB_OFF_TO_ERR(val);
  121. goto fail;
  122. }
  123. /* Directly in hash table? */
  124. if (!likely(is_chain(val))) {
  125. if (val) {
  126. berr = match(ntdb, h->h, &key, val, rec, rptr);
  127. if (berr < 0) {
  128. ecode = NTDB_OFF_TO_ERR(berr);
  129. goto fail;
  130. }
  131. if (berr) {
  132. return val & NTDB_OFF_MASK;
  133. }
  134. /* If you want to insert here, make a chain. */
  135. h->old_val = val;
  136. }
  137. return 0;
  138. }
  139. /* Nope? Iterate through chain. */
  140. h->table = val & NTDB_OFF_MASK;
  141. ecode = ntdb_read_convert(ntdb, h->table, &chdr, sizeof(chdr));
  142. if (ecode != NTDB_SUCCESS) {
  143. goto fail;
  144. }
  145. if (rec_magic(&chdr) != NTDB_CHAIN_MAGIC) {
  146. ecode = ntdb_logerr(ntdb, NTDB_ERR_CORRUPT,
  147. NTDB_LOG_ERROR,
  148. "find_and_lock:"
  149. " corrupt record %#x at %llu",
  150. rec_magic(&chdr), (long long)off);
  151. goto fail;
  152. }
  153. h->table_size = rec_data_length(&chdr) / sizeof(ntdb_off_t);
  154. arr = ntdb_access_read(ntdb, hbucket_off(h->table, 0),
  155. rec_data_length(&chdr), true);
  156. if (NTDB_PTR_IS_ERR(arr)) {
  157. ecode = NTDB_PTR_ERR(arr);
  158. goto fail;
  159. }
  160. found_empty = false;
  161. for (i = 0; i < h->table_size; i++) {
  162. if (arr[i] == 0) {
  163. if (!found_empty) {
  164. h->bucket = i;
  165. found_empty = true;
  166. }
  167. } else {
  168. berr = match(ntdb, h->h, &key, arr[i], rec, rptr);
  169. if (berr < 0) {
  170. ecode = NTDB_OFF_TO_ERR(berr);
  171. ntdb_access_release(ntdb, arr);
  172. goto fail;
  173. }
  174. if (berr) {
  175. /* We found it! */
  176. h->bucket = i;
  177. off = arr[i] & NTDB_OFF_MASK;
  178. ntdb_access_release(ntdb, arr);
  179. return off;
  180. }
  181. }
  182. }
  183. if (!found_empty) {
  184. /* Set to any non-zero value */
  185. h->old_val = 1;
  186. h->bucket = i;
  187. }
  188. ntdb_access_release(ntdb, arr);
  189. return 0;
  190. fail:
  191. ntdb_unlock_hash(ntdb, h->bucket, ltype);
  192. return NTDB_ERR_TO_OFF(ecode);
  193. }
  194. static ntdb_off_t encode_offset(const struct ntdb_context *ntdb,
  195. ntdb_off_t new_off, uint32_t hash)
  196. {
  197. ntdb_off_t extra;
  198. assert((new_off & (1ULL << NTDB_OFF_CHAIN_BIT)) == 0);
  199. assert((new_off >> (64 - NTDB_OFF_UPPER_STEAL)) == 0);
  200. /* We pack extra hash bits into the upper bits of the offset. */
  201. extra = bits_from(hash, ntdb->hash_bits, NTDB_OFF_UPPER_STEAL);
  202. extra <<= (64 - NTDB_OFF_UPPER_STEAL);
  203. return new_off | extra;
  204. }
  205. /* Simply overwrite the hash entry we found before. */
  206. enum NTDB_ERROR replace_in_hash(struct ntdb_context *ntdb,
  207. const struct hash_info *h,
  208. ntdb_off_t new_off)
  209. {
  210. return ntdb_write_off(ntdb, hbucket_off(h->table, h->bucket),
  211. encode_offset(ntdb, new_off, h->h));
  212. }
  213. enum NTDB_ERROR delete_from_hash(struct ntdb_context *ntdb,
  214. const struct hash_info *h)
  215. {
  216. return ntdb_write_off(ntdb, hbucket_off(h->table, h->bucket), 0);
  217. }
  218. enum NTDB_ERROR add_to_hash(struct ntdb_context *ntdb,
  219. const struct hash_info *h,
  220. ntdb_off_t new_off)
  221. {
  222. enum NTDB_ERROR ecode;
  223. ntdb_off_t chain;
  224. struct ntdb_used_record chdr;
  225. const ntdb_off_t *old;
  226. ntdb_off_t *new;
  227. /* We hit an empty bucket during search? That's where it goes. */
  228. if (!h->old_val) {
  229. return replace_in_hash(ntdb, h, new_off);
  230. }
  231. /* Full at top-level? Create a 2-element chain. */
  232. if (h->table == NTDB_HASH_OFFSET) {
  233. ntdb_off_t pair[2];
  234. /* One element is old value, the other is the new value. */
  235. pair[0] = h->old_val;
  236. pair[1] = encode_offset(ntdb, new_off, h->h);
  237. chain = alloc(ntdb, 0, sizeof(pair), NTDB_CHAIN_MAGIC, true);
  238. if (NTDB_OFF_IS_ERR(chain)) {
  239. return NTDB_OFF_TO_ERR(chain);
  240. }
  241. ecode = ntdb_write_convert(ntdb,
  242. chain
  243. + sizeof(struct ntdb_used_record),
  244. pair, sizeof(pair));
  245. if (ecode == NTDB_SUCCESS) {
  246. ecode = ntdb_write_off(ntdb,
  247. hbucket_off(h->table, h->bucket),
  248. chain
  249. | (1ULL << NTDB_OFF_CHAIN_BIT));
  250. }
  251. return ecode;
  252. }
  253. /* Full bucket. Expand. */
  254. ecode = ntdb_read_convert(ntdb, h->table, &chdr, sizeof(chdr));
  255. if (ecode != NTDB_SUCCESS) {
  256. return ecode;
  257. }
  258. if (rec_extra_padding(&chdr) >= sizeof(new_off)) {
  259. /* Expand in place. */
  260. uint64_t dlen = rec_data_length(&chdr);
  261. ecode = set_header(ntdb, &chdr, NTDB_CHAIN_MAGIC, 0,
  262. dlen + sizeof(new_off),
  263. dlen + rec_extra_padding(&chdr));
  264. if (ecode != NTDB_SUCCESS) {
  265. return ecode;
  266. }
  267. /* find_and_lock set up h to point to last bucket. */
  268. ecode = replace_in_hash(ntdb, h, new_off);
  269. if (ecode != NTDB_SUCCESS) {
  270. return ecode;
  271. }
  272. ecode = ntdb_write_convert(ntdb, h->table, &chdr, sizeof(chdr));
  273. if (ecode != NTDB_SUCCESS) {
  274. return ecode;
  275. }
  276. /* For futureproofing, we always make the first byte of padding
  277. * a zero. */
  278. if (rec_extra_padding(&chdr)) {
  279. ecode = ntdb->io->twrite(ntdb, h->table + sizeof(chdr)
  280. + dlen + sizeof(new_off),
  281. "", 1);
  282. }
  283. return ecode;
  284. }
  285. /* We need to reallocate the chain. */
  286. chain = alloc(ntdb, 0, (h->table_size + 1) * sizeof(ntdb_off_t),
  287. NTDB_CHAIN_MAGIC, true);
  288. if (NTDB_OFF_IS_ERR(chain)) {
  289. return NTDB_OFF_TO_ERR(chain);
  290. }
  291. /* Map both and copy across old buckets. */
  292. old = ntdb_access_read(ntdb, hbucket_off(h->table, 0),
  293. h->table_size*sizeof(ntdb_off_t), true);
  294. if (NTDB_PTR_IS_ERR(old)) {
  295. return NTDB_PTR_ERR(old);
  296. }
  297. new = ntdb_access_write(ntdb, hbucket_off(chain, 0),
  298. (h->table_size + 1)*sizeof(ntdb_off_t), true);
  299. if (NTDB_PTR_IS_ERR(new)) {
  300. ntdb_access_release(ntdb, old);
  301. return NTDB_PTR_ERR(new);
  302. }
  303. memcpy(new, old, h->bucket * sizeof(ntdb_off_t));
  304. new[h->bucket] = encode_offset(ntdb, new_off, h->h);
  305. ntdb_access_release(ntdb, old);
  306. ecode = ntdb_access_commit(ntdb, new);
  307. if (ecode != NTDB_SUCCESS) {
  308. return ecode;
  309. }
  310. /* Free the old chain. */
  311. ecode = add_free_record(ntdb, h->table,
  312. sizeof(struct ntdb_used_record)
  313. + rec_data_length(&chdr)
  314. + rec_extra_padding(&chdr),
  315. NTDB_LOCK_WAIT, true);
  316. /* Replace top-level to point to new chain */
  317. return ntdb_write_off(ntdb,
  318. hbucket_off(NTDB_HASH_OFFSET,
  319. bits_from(h->h, 0, ntdb->hash_bits)),
  320. chain | (1ULL << NTDB_OFF_CHAIN_BIT));
  321. }
  322. /* Traverse support: returns offset of record, or 0 or -ve error. */
  323. static ntdb_off_t iterate_chain(struct ntdb_context *ntdb,
  324. ntdb_off_t val,
  325. struct hash_info *h)
  326. {
  327. ntdb_off_t i;
  328. enum NTDB_ERROR ecode;
  329. struct ntdb_used_record chdr;
  330. /* First load up chain header. */
  331. h->table = val & NTDB_OFF_MASK;
  332. ecode = ntdb_read_convert(ntdb, h->table, &chdr, sizeof(chdr));
  333. if (ecode != NTDB_SUCCESS) {
  334. return ecode;
  335. }
  336. if (rec_magic(&chdr) != NTDB_CHAIN_MAGIC) {
  337. return ntdb_logerr(ntdb, NTDB_ERR_CORRUPT,
  338. NTDB_LOG_ERROR,
  339. "get_table:"
  340. " corrupt record %#x at %llu",
  341. rec_magic(&chdr),
  342. (long long)h->table);
  343. }
  344. /* Chain length is implied by data length. */
  345. h->table_size = rec_data_length(&chdr) / sizeof(ntdb_off_t);
  346. i = ntdb_find_nonzero_off(ntdb, hbucket_off(h->table, 0), h->bucket,
  347. h->table_size);
  348. if (NTDB_OFF_IS_ERR(i)) {
  349. return i;
  350. }
  351. if (i != h->table_size) {
  352. /* Return to next bucket. */
  353. h->bucket = i + 1;
  354. val = ntdb_read_off(ntdb, hbucket_off(h->table, i));
  355. if (NTDB_OFF_IS_ERR(val)) {
  356. return val;
  357. }
  358. return val & NTDB_OFF_MASK;
  359. }
  360. /* Go back up to hash table. */
  361. h->table = NTDB_HASH_OFFSET;
  362. h->table_size = 1 << ntdb->hash_bits;
  363. h->bucket = bits_from(h->h, 0, ntdb->hash_bits) + 1;
  364. return 0;
  365. }
  366. /* Keeps hash locked unless returns 0 or error. */
  367. static ntdb_off_t lock_and_iterate_hash(struct ntdb_context *ntdb,
  368. struct hash_info *h)
  369. {
  370. ntdb_off_t val, i;
  371. enum NTDB_ERROR ecode;
  372. if (h->table != NTDB_HASH_OFFSET) {
  373. /* We're in a chain. */
  374. i = bits_from(h->h, 0, ntdb->hash_bits);
  375. ecode = ntdb_lock_hash(ntdb, i, F_RDLCK);
  376. if (ecode != NTDB_SUCCESS) {
  377. return NTDB_ERR_TO_OFF(ecode);
  378. }
  379. /* We dropped lock, bucket might have moved! */
  380. val = ntdb_read_off(ntdb, hbucket_off(NTDB_HASH_OFFSET, i));
  381. if (NTDB_OFF_IS_ERR(val)) {
  382. goto unlock;
  383. }
  384. /* We don't remove chains: there should still be one there! */
  385. if (!val || !is_chain(val)) {
  386. ecode = ntdb_logerr(ntdb, NTDB_ERR_CORRUPT,
  387. NTDB_LOG_ERROR,
  388. "iterate_hash:"
  389. " vanished hchain %llu at %llu",
  390. (long long)val,
  391. (long long)i);
  392. val = NTDB_ERR_TO_OFF(ecode);
  393. goto unlock;
  394. }
  395. /* Find next bucket in the chain. */
  396. val = iterate_chain(ntdb, val, h);
  397. if (NTDB_OFF_IS_ERR(val)) {
  398. goto unlock;
  399. }
  400. if (val != 0) {
  401. return val;
  402. }
  403. ntdb_unlock_hash(ntdb, i, F_RDLCK);
  404. /* OK, we've reset h back to top level. */
  405. }
  406. /* We do this unlocked, then re-check. */
  407. for (i = ntdb_find_nonzero_off(ntdb, hbucket_off(h->table, 0),
  408. h->bucket, h->table_size);
  409. i != h->table_size;
  410. i = ntdb_find_nonzero_off(ntdb, hbucket_off(h->table, 0),
  411. i+1, h->table_size)) {
  412. ecode = ntdb_lock_hash(ntdb, i, F_RDLCK);
  413. if (ecode != NTDB_SUCCESS) {
  414. return NTDB_ERR_TO_OFF(ecode);
  415. }
  416. val = ntdb_read_off(ntdb, hbucket_off(h->table, i));
  417. if (NTDB_OFF_IS_ERR(val)) {
  418. goto unlock;
  419. }
  420. /* Lost race, and it's empty? */
  421. if (!val) {
  422. ntdb->stats.traverse_val_vanished++;
  423. ntdb_unlock_hash(ntdb, i, F_RDLCK);
  424. continue;
  425. }
  426. if (!is_chain(val)) {
  427. /* So caller knows what lock to free. */
  428. h->h = i;
  429. /* Return to next bucket. */
  430. h->bucket = i + 1;
  431. val &= NTDB_OFF_MASK;
  432. return val;
  433. }
  434. /* Start at beginning of chain */
  435. h->bucket = 0;
  436. h->h = i;
  437. val = iterate_chain(ntdb, val, h);
  438. if (NTDB_OFF_IS_ERR(val)) {
  439. goto unlock;
  440. }
  441. if (val != 0) {
  442. return val;
  443. }
  444. /* Otherwise, bucket has been set to i+1 */
  445. ntdb_unlock_hash(ntdb, i, F_RDLCK);
  446. }
  447. return 0;
  448. unlock:
  449. ntdb_unlock_hash(ntdb, i, F_RDLCK);
  450. return val;
  451. }
  452. /* Return success if we find something, NTDB_ERR_NOEXIST if none. */
  453. enum NTDB_ERROR next_in_hash(struct ntdb_context *ntdb,
  454. struct hash_info *h,
  455. NTDB_DATA *kbuf, size_t *dlen)
  456. {
  457. ntdb_off_t off;
  458. struct ntdb_used_record rec;
  459. enum NTDB_ERROR ecode;
  460. off = lock_and_iterate_hash(ntdb, h);
  461. if (NTDB_OFF_IS_ERR(off)) {
  462. return NTDB_OFF_TO_ERR(off);
  463. } else if (off == 0) {
  464. return NTDB_ERR_NOEXIST;
  465. }
  466. /* The hash for this key is still locked. */
  467. ecode = ntdb_read_convert(ntdb, off, &rec, sizeof(rec));
  468. if (ecode != NTDB_SUCCESS) {
  469. goto unlock;
  470. }
  471. if (rec_magic(&rec) != NTDB_USED_MAGIC) {
  472. ecode = ntdb_logerr(ntdb, NTDB_ERR_CORRUPT,
  473. NTDB_LOG_ERROR,
  474. "next_in_hash:"
  475. " corrupt record at %llu",
  476. (long long)off);
  477. goto unlock;
  478. }
  479. kbuf->dsize = rec_key_length(&rec);
  480. /* They want data as well? */
  481. if (dlen) {
  482. *dlen = rec_data_length(&rec);
  483. kbuf->dptr = ntdb_alloc_read(ntdb, off + sizeof(rec),
  484. kbuf->dsize + *dlen);
  485. } else {
  486. kbuf->dptr = ntdb_alloc_read(ntdb, off + sizeof(rec),
  487. kbuf->dsize);
  488. }
  489. if (NTDB_PTR_IS_ERR(kbuf->dptr)) {
  490. ecode = NTDB_PTR_ERR(kbuf->dptr);
  491. goto unlock;
  492. }
  493. ecode = NTDB_SUCCESS;
  494. unlock:
  495. ntdb_unlock_hash(ntdb, bits_from(h->h, 0, ntdb->hash_bits), F_RDLCK);
  496. return ecode;
  497. }
  498. enum NTDB_ERROR first_in_hash(struct ntdb_context *ntdb,
  499. struct hash_info *h,
  500. NTDB_DATA *kbuf, size_t *dlen)
  501. {
  502. h->table = NTDB_HASH_OFFSET;
  503. h->table_size = 1 << ntdb->hash_bits;
  504. h->bucket = 0;
  505. return next_in_hash(ntdb, h, kbuf, dlen);
  506. }
  507. /* Even if the entry isn't in this hash bucket, you'd have to lock this
  508. * bucket to find it. */
  509. static enum NTDB_ERROR chainlock(struct ntdb_context *ntdb,
  510. const NTDB_DATA *key, int ltype)
  511. {
  512. uint32_t h = ntdb_hash(ntdb, key->dptr, key->dsize);
  513. return ntdb_lock_hash(ntdb, bits_from(h, 0, ntdb->hash_bits), ltype);
  514. }
  515. /* lock/unlock one hash chain. This is meant to be used to reduce
  516. contention - it cannot guarantee how many records will be locked */
  517. _PUBLIC_ enum NTDB_ERROR ntdb_chainlock(struct ntdb_context *ntdb, NTDB_DATA key)
  518. {
  519. return chainlock(ntdb, &key, F_WRLCK);
  520. }
  521. _PUBLIC_ void ntdb_chainunlock(struct ntdb_context *ntdb, NTDB_DATA key)
  522. {
  523. uint32_t h = ntdb_hash(ntdb, key.dptr, key.dsize);
  524. ntdb_unlock_hash(ntdb, bits_from(h, 0, ntdb->hash_bits), F_WRLCK);
  525. }
  526. _PUBLIC_ enum NTDB_ERROR ntdb_chainlock_read(struct ntdb_context *ntdb,
  527. NTDB_DATA key)
  528. {
  529. return chainlock(ntdb, &key, F_RDLCK);
  530. }
  531. _PUBLIC_ void ntdb_chainunlock_read(struct ntdb_context *ntdb, NTDB_DATA key)
  532. {
  533. uint32_t h = ntdb_hash(ntdb, key.dptr, key.dsize);
  534. ntdb_unlock_hash(ntdb, bits_from(h, 0, ntdb->hash_bits), F_RDLCK);
  535. }