2 Trivial Database 2: free list/block handling
3 Copyright (C) Rusty Russell 2010
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 3 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 #include <ccan/likely/likely.h>
20 #include <ccan/asearch/asearch.h>
22 /* We keep an ordered array of offsets. */
23 static bool append(struct ntdb_context
*ntdb
,
24 ntdb_off_t
**arr
, size_t *num
, ntdb_off_t off
)
29 new = ntdb
->alloc_fn(ntdb
, sizeof(ntdb_off_t
), ntdb
->alloc_data
);
31 new = ntdb
->expand_fn(*arr
, (*num
+ 1) * sizeof(ntdb_off_t
),
41 static enum NTDB_ERROR
check_header(struct ntdb_context
*ntdb
,
44 size_t *num_capabilities
)
47 struct ntdb_header hdr
;
48 enum NTDB_ERROR ecode
;
51 ecode
= ntdb_read_convert(ntdb
, 0, &hdr
, sizeof(hdr
));
52 if (ecode
!= NTDB_SUCCESS
) {
55 /* magic food should not be converted, so convert back. */
56 ntdb_convert(ntdb
, hdr
.magic_food
, sizeof(hdr
.magic_food
));
58 hash_test
= NTDB_HASH_MAGIC
;
59 hash_test
= ntdb_hash(ntdb
, &hash_test
, sizeof(hash_test
));
60 if (hdr
.hash_test
!= hash_test
) {
61 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
62 "check: hash test %llu should be %llu",
63 (long long)hdr
.hash_test
,
64 (long long)hash_test
);
67 if (strcmp(hdr
.magic_food
, NTDB_MAGIC_FOOD
) != 0) {
68 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
69 "check: bad magic '%.*s'",
70 (unsigned)sizeof(hdr
.magic_food
),
74 /* Features which are used must be a subset of features offered. */
75 if (hdr
.features_used
& ~hdr
.features_offered
) {
76 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
77 "check: features used (0x%llx) which"
78 " are not offered (0x%llx)",
79 (long long)hdr
.features_used
,
80 (long long)hdr
.features_offered
);
83 *features
= hdr
.features_offered
;
84 *recovery
= hdr
.recovery
;
86 if (*recovery
< sizeof(hdr
)
87 || *recovery
> ntdb
->file
->map_size
) {
88 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
90 " invalid recovery offset %zu",
95 for (off
= hdr
.capabilities
; off
&& ecode
== NTDB_SUCCESS
; off
= next
) {
96 const struct ntdb_capability
*cap
;
99 cap
= ntdb_access_read(ntdb
, off
, sizeof(*cap
), true);
100 if (NTDB_PTR_IS_ERR(cap
)) {
101 return NTDB_PTR_ERR(cap
);
104 /* All capabilities are unknown. */
105 e
= unknown_capability(ntdb
, "ntdb_check", cap
->type
);
107 ntdb_access_release(ntdb
, cap
);
110 (*num_capabilities
)++;
113 /* Don't check reserved: they *can* be used later. */
117 static int off_cmp(const ntdb_off_t
*a
, const ntdb_off_t
*b
)
119 /* Can overflow an int. */
125 static enum NTDB_ERROR
check_entry(struct ntdb_context
*ntdb
,
126 ntdb_off_t off_and_hash
,
131 enum NTDB_ERROR (*check
)(NTDB_DATA
,
136 enum NTDB_ERROR ecode
;
137 const struct ntdb_used_record
*r
;
138 const unsigned char *kptr
;
139 ntdb_len_t klen
, dlen
;
141 ntdb_off_t off
= off_and_hash
& NTDB_OFF_MASK
;
144 /* Empty bucket is fine. */
149 /* This can't point to a chain, we handled those at toplevel. */
150 if (off_and_hash
& (1ULL << NTDB_OFF_CHAIN_BIT
)) {
151 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
152 "ntdb_check: Invalid chain bit in offset "
153 " %llu", (long long)off_and_hash
);
156 p
= asearch(&off
, used
, num_used
, off_cmp
);
158 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
159 "ntdb_check: Invalid offset"
160 " %llu in hash", (long long)off
);
162 /* Mark it invalid. */
166 r
= ntdb_access_read(ntdb
, off
, sizeof(*r
), true);
167 if (NTDB_PTR_IS_ERR(r
)) {
168 return NTDB_PTR_ERR(r
);
170 klen
= rec_key_length(r
);
171 dlen
= rec_data_length(r
);
172 ntdb_access_release(ntdb
, r
);
174 kptr
= ntdb_access_read(ntdb
, off
+ sizeof(*r
), klen
+ dlen
, false);
175 if (NTDB_PTR_IS_ERR(kptr
)) {
176 return NTDB_PTR_ERR(kptr
);
179 hash
= ntdb_hash(ntdb
, kptr
, klen
);
181 /* Are we in the right chain? */
182 if (bits_from(hash
, 0, ntdb
->hash_bits
) != bucket
) {
183 ecode
= ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
185 "ntdb_check: Bad bucket %u vs %llu",
186 bits_from(hash
, 0, ntdb
->hash_bits
),
188 /* Next 8 bits should be the same as top bits of bucket. */
189 } else if (bits_from(hash
, ntdb
->hash_bits
, NTDB_OFF_UPPER_STEAL
)
190 != bits_from(off_and_hash
, 64-NTDB_OFF_UPPER_STEAL
,
191 NTDB_OFF_UPPER_STEAL
)) {
192 ecode
= ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
194 "ntdb_check: Bad hash bits %llu vs %llu",
195 (long long)off_and_hash
,
200 k
= ntdb_mkdata(kptr
, klen
);
201 d
= ntdb_mkdata(kptr
+ klen
, dlen
);
202 ecode
= check(k
, d
, data
);
204 ecode
= NTDB_SUCCESS
;
206 ntdb_access_release(ntdb
, kptr
);
211 static enum NTDB_ERROR
check_hash_chain(struct ntdb_context
*ntdb
,
217 enum NTDB_ERROR (*check
)(NTDB_DATA
,
222 struct ntdb_used_record rec
;
223 enum NTDB_ERROR ecode
;
224 const ntdb_off_t
*entries
;
227 /* This is a used entry. */
230 ecode
= ntdb_read_convert(ntdb
, off
, &rec
, sizeof(rec
));
231 if (ecode
!= NTDB_SUCCESS
) {
235 if (rec_magic(&rec
) != NTDB_CHAIN_MAGIC
) {
236 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
237 "ntdb_check: Bad hash chain magic %llu",
238 (long long)rec_magic(&rec
));
241 if (rec_data_length(&rec
) % sizeof(ntdb_off_t
)) {
242 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
243 "ntdb_check: Bad hash chain data length %llu",
244 (long long)rec_data_length(&rec
));
247 if (rec_key_length(&rec
) != 0) {
248 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
249 "ntdb_check: Bad hash chain key length %llu",
250 (long long)rec_key_length(&rec
));
254 num
= rec_data_length(&rec
) / sizeof(ntdb_off_t
);
255 entries
= ntdb_access_read(ntdb
, off
, rec_data_length(&rec
), true);
256 if (NTDB_PTR_IS_ERR(entries
)) {
257 return NTDB_PTR_ERR(entries
);
260 /* Check each non-deleted entry in chain. */
261 for (i
= 0; i
< num
; i
++) {
262 ecode
= check_entry(ntdb
, entries
[i
], bucket
,
263 used
, num_used
, num_found
, check
, data
);
269 ntdb_access_release(ntdb
, entries
);
273 static enum NTDB_ERROR
check_hash(struct ntdb_context
*ntdb
,
276 size_t num_other_used
,
277 enum NTDB_ERROR (*check
)(NTDB_DATA
,
282 enum NTDB_ERROR ecode
;
283 struct ntdb_used_record rec
;
284 const ntdb_off_t
*entries
;
286 /* Free tables and capabilities also show up as used, as do we. */
287 size_t num_found
= num_other_used
+ 1;
289 ecode
= ntdb_read_convert(ntdb
, NTDB_HASH_OFFSET
, &rec
, sizeof(rec
));
290 if (ecode
!= NTDB_SUCCESS
) {
294 if (rec_magic(&rec
) != NTDB_HTABLE_MAGIC
) {
295 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
296 "ntdb_check: Bad hash table magic %llu",
297 (long long)rec_magic(&rec
));
300 if (rec_data_length(&rec
) != (sizeof(ntdb_off_t
) << ntdb
->hash_bits
)) {
301 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
302 "ntdb_check: Bad hash table data length %llu",
303 (long long)rec_data_length(&rec
));
306 if (rec_key_length(&rec
) != 0) {
307 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
308 "ntdb_check: Bad hash table key length %llu",
309 (long long)rec_key_length(&rec
));
312 entries
= ntdb_access_read(ntdb
, NTDB_HASH_OFFSET
+ sizeof(rec
),
313 rec_data_length(&rec
), true);
314 if (NTDB_PTR_IS_ERR(entries
)) {
315 return NTDB_PTR_ERR(entries
);
318 for (i
= 0; i
< (1 << ntdb
->hash_bits
); i
++) {
319 ntdb_off_t off
= entries
[i
] & NTDB_OFF_MASK
;
320 if (entries
[i
] & (1ULL << NTDB_OFF_CHAIN_BIT
)) {
321 ecode
= check_hash_chain(ntdb
, off
, i
,
322 used
, num_used
, &num_found
,
325 ecode
= check_entry(ntdb
, entries
[i
], i
,
326 used
, num_used
, &num_found
,
333 ntdb_access_release(ntdb
, entries
);
335 if (ecode
== NTDB_SUCCESS
&& num_found
!= num_used
) {
336 ecode
= ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
337 "ntdb_check: Not all entries are in hash");
342 static enum NTDB_ERROR
check_free(struct ntdb_context
*ntdb
,
344 const struct ntdb_free_record
*frec
,
345 ntdb_off_t prev
, unsigned int ftable
,
348 enum NTDB_ERROR ecode
;
350 if (frec_magic(frec
) != NTDB_FREE_MAGIC
) {
351 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
352 "ntdb_check: offset %llu bad magic 0x%llx",
354 (long long)frec
->magic_and_prev
);
356 if (frec_ftable(frec
) != ftable
) {
357 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
358 "ntdb_check: offset %llu bad freetable %u",
359 (long long)off
, frec_ftable(frec
));
363 ecode
= ntdb_oob(ntdb
, off
,
364 frec_len(frec
) + sizeof(struct ntdb_used_record
),
366 if (ecode
!= NTDB_SUCCESS
) {
369 if (size_to_bucket(frec_len(frec
)) != bucket
) {
370 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
371 "ntdb_check: offset %llu in wrong bucket"
374 bucket
, size_to_bucket(frec_len(frec
)));
376 if (prev
&& prev
!= frec_prev(frec
)) {
377 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
378 "ntdb_check: offset %llu bad prev"
381 (long long)prev
, (long long)frec_len(frec
));
386 static enum NTDB_ERROR
check_free_table(struct ntdb_context
*ntdb
,
387 ntdb_off_t ftable_off
,
393 struct ntdb_freetable ft
;
396 enum NTDB_ERROR ecode
;
398 ecode
= ntdb_read_convert(ntdb
, ftable_off
, &ft
, sizeof(ft
));
399 if (ecode
!= NTDB_SUCCESS
) {
403 if (rec_magic(&ft
.hdr
) != NTDB_FTABLE_MAGIC
404 || rec_key_length(&ft
.hdr
) != 0
405 || rec_data_length(&ft
.hdr
) != sizeof(ft
) - sizeof(ft
.hdr
)) {
406 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
407 "ntdb_check: Invalid header on free table");
410 for (i
= 0; i
< NTDB_FREE_BUCKETS
; i
++) {
411 ntdb_off_t off
, prev
= 0, *p
, first
= 0;
412 struct ntdb_free_record f
;
414 h
= bucket_off(ftable_off
, i
);
415 for (off
= ntdb_read_off(ntdb
, h
); off
; off
= f
.next
) {
416 if (NTDB_OFF_IS_ERR(off
)) {
417 return NTDB_OFF_TO_ERR(off
);
420 off
&= NTDB_OFF_MASK
;
423 ecode
= ntdb_read_convert(ntdb
, off
, &f
, sizeof(f
));
424 if (ecode
!= NTDB_SUCCESS
) {
427 ecode
= check_free(ntdb
, off
, &f
, prev
, ftable_num
, i
);
428 if (ecode
!= NTDB_SUCCESS
) {
432 /* FIXME: Check hash bits */
433 p
= asearch(&off
, fr
, num_free
, off_cmp
);
435 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
437 "ntdb_check: Invalid offset"
438 " %llu in free table",
441 /* Mark it invalid. */
448 /* Now we can check first back pointer. */
449 ecode
= ntdb_read_convert(ntdb
, first
, &f
, sizeof(f
));
450 if (ecode
!= NTDB_SUCCESS
) {
453 ecode
= check_free(ntdb
, first
, &f
, prev
, ftable_num
, i
);
454 if (ecode
!= NTDB_SUCCESS
) {
462 /* Slow, but should be very rare. */
463 ntdb_off_t
dead_space(struct ntdb_context
*ntdb
, ntdb_off_t off
)
466 enum NTDB_ERROR ecode
;
468 for (len
= 0; off
+ len
< ntdb
->file
->map_size
; len
++) {
470 ecode
= ntdb
->io
->tread(ntdb
, off
, &c
, 1);
471 if (ecode
!= NTDB_SUCCESS
) {
472 return NTDB_ERR_TO_OFF(ecode
);
474 if (c
!= 0 && c
!= 0x43)
480 static enum NTDB_ERROR
check_linear(struct ntdb_context
*ntdb
,
481 ntdb_off_t
**used
, size_t *num_used
,
482 ntdb_off_t
**fr
, size_t *num_free
,
483 uint64_t features
, ntdb_off_t recovery
)
487 enum NTDB_ERROR ecode
;
488 bool found_recovery
= false;
490 for (off
= sizeof(struct ntdb_header
);
491 off
< ntdb
->file
->map_size
;
494 struct ntdb_used_record u
;
495 struct ntdb_free_record f
;
496 struct ntdb_recovery_record r
;
498 /* r is larger: only get that if we need to. */
499 ecode
= ntdb_read_convert(ntdb
, off
, &rec
, sizeof(rec
.f
));
500 if (ecode
!= NTDB_SUCCESS
) {
504 /* If we crash after ftruncate, we can get zeroes or fill. */
505 if (rec
.r
.magic
== NTDB_RECOVERY_INVALID_MAGIC
506 || rec
.r
.magic
== 0x4343434343434343ULL
) {
507 ecode
= ntdb_read_convert(ntdb
, off
, &rec
, sizeof(rec
.r
));
508 if (ecode
!= NTDB_SUCCESS
) {
511 if (recovery
== off
) {
512 found_recovery
= true;
513 len
= sizeof(rec
.r
) + rec
.r
.max_len
;
515 len
= dead_space(ntdb
, off
);
516 if (NTDB_OFF_IS_ERR(len
)) {
517 return NTDB_OFF_TO_ERR(len
);
519 if (len
< sizeof(rec
.r
)) {
520 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
522 "ntdb_check: invalid"
523 " dead space at %zu",
527 ntdb_logerr(ntdb
, NTDB_SUCCESS
, NTDB_LOG_WARNING
,
528 "Dead space at %zu-%zu (of %zu)",
529 (size_t)off
, (size_t)(off
+ len
),
530 (size_t)ntdb
->file
->map_size
);
532 } else if (rec
.r
.magic
== NTDB_RECOVERY_MAGIC
) {
533 ecode
= ntdb_read_convert(ntdb
, off
, &rec
, sizeof(rec
.r
));
534 if (ecode
!= NTDB_SUCCESS
) {
537 if (recovery
!= off
) {
538 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
540 "ntdb_check: unexpected"
541 " recovery record at offset"
545 if (rec
.r
.len
> rec
.r
.max_len
) {
546 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
548 "ntdb_check: invalid recovery"
552 if (rec
.r
.eof
> ntdb
->file
->map_size
) {
553 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
555 "ntdb_check: invalid old EOF"
556 " %zu", (size_t)rec
.r
.eof
);
558 found_recovery
= true;
559 len
= sizeof(rec
.r
) + rec
.r
.max_len
;
560 } else if (frec_magic(&rec
.f
) == NTDB_FREE_MAGIC
) {
561 len
= sizeof(rec
.u
) + frec_len(&rec
.f
);
562 if (off
+ len
> ntdb
->file
->map_size
) {
563 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
565 "ntdb_check: free overlength"
566 " %llu at offset %llu",
570 /* This record should be in free lists. */
571 if (frec_ftable(&rec
.f
) != NTDB_FTABLE_NONE
572 && !append(ntdb
, fr
, num_free
, off
)) {
573 return ntdb_logerr(ntdb
, NTDB_ERR_OOM
,
575 "ntdb_check: tracking %zu'th"
576 " free record.", *num_free
);
578 } else if (rec_magic(&rec
.u
) == NTDB_USED_MAGIC
579 || rec_magic(&rec
.u
) == NTDB_CHAIN_MAGIC
580 || rec_magic(&rec
.u
) == NTDB_HTABLE_MAGIC
581 || rec_magic(&rec
.u
) == NTDB_FTABLE_MAGIC
582 || rec_magic(&rec
.u
) == NTDB_CAP_MAGIC
) {
583 uint64_t klen
, dlen
, extra
;
585 /* This record is used! */
586 if (!append(ntdb
, used
, num_used
, off
)) {
587 return ntdb_logerr(ntdb
, NTDB_ERR_OOM
,
589 "ntdb_check: tracking %zu'th"
590 " used record.", *num_used
);
593 klen
= rec_key_length(&rec
.u
);
594 dlen
= rec_data_length(&rec
.u
);
595 extra
= rec_extra_padding(&rec
.u
);
597 len
= sizeof(rec
.u
) + klen
+ dlen
+ extra
;
598 if (off
+ len
> ntdb
->file
->map_size
) {
599 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
601 "ntdb_check: used overlength"
602 " %llu at offset %llu",
607 if (len
< sizeof(rec
.f
)) {
608 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
610 "ntdb_check: too short record"
616 /* Check that records have correct 0 at end (but may
618 if (extra
&& !features
619 && rec_magic(&rec
.u
) != NTDB_CAP_MAGIC
) {
622 p
= ntdb_access_read(ntdb
, off
+ sizeof(rec
.u
)
623 + klen
+ dlen
, 1, false);
624 if (NTDB_PTR_IS_ERR(p
))
625 return NTDB_PTR_ERR(p
);
627 ntdb_access_release(ntdb
, p
);
630 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
639 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
,
641 "ntdb_check: Bad magic 0x%llx"
643 (long long)rec_magic(&rec
.u
),
648 /* We must have found recovery area if there was one. */
649 if (recovery
!= 0 && !found_recovery
) {
650 return ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
651 "ntdb_check: expected a recovery area at %zu",
658 _PUBLIC_
enum NTDB_ERROR
ntdb_check_(struct ntdb_context
*ntdb
,
659 enum NTDB_ERROR (*check
)(NTDB_DATA
, NTDB_DATA
, void *),
662 ntdb_off_t
*fr
= NULL
, *used
= NULL
;
663 ntdb_off_t ft
= 0, recovery
= 0;
664 size_t num_free
= 0, num_used
= 0, num_found
= 0, num_ftables
= 0,
665 num_capabilities
= 0;
666 uint64_t features
= 0;
667 enum NTDB_ERROR ecode
;
669 if (ntdb
->flags
& NTDB_CANT_CHECK
) {
670 return ntdb_logerr(ntdb
, NTDB_SUCCESS
, NTDB_LOG_WARNING
,
671 "ntdb_check: database has unknown capability,"
675 ecode
= ntdb_allrecord_lock(ntdb
, F_RDLCK
, NTDB_LOCK_WAIT
, false);
676 if (ecode
!= NTDB_SUCCESS
) {
680 ecode
= ntdb_lock_expand(ntdb
, F_RDLCK
);
681 if (ecode
!= NTDB_SUCCESS
) {
682 ntdb_allrecord_unlock(ntdb
, F_RDLCK
);
686 ecode
= check_header(ntdb
, &recovery
, &features
, &num_capabilities
);
687 if (ecode
!= NTDB_SUCCESS
)
690 /* First we do a linear scan, checking all records. */
691 ecode
= check_linear(ntdb
, &used
, &num_used
, &fr
, &num_free
, features
,
693 if (ecode
!= NTDB_SUCCESS
)
696 for (ft
= first_ftable(ntdb
); ft
; ft
= next_ftable(ntdb
, ft
)) {
697 if (NTDB_OFF_IS_ERR(ft
)) {
698 ecode
= NTDB_OFF_TO_ERR(ft
);
701 ecode
= check_free_table(ntdb
, ft
, num_ftables
, fr
, num_free
,
703 if (ecode
!= NTDB_SUCCESS
)
708 /* FIXME: Check key uniqueness? */
709 ecode
= check_hash(ntdb
, used
, num_used
, num_ftables
+ num_capabilities
,
711 if (ecode
!= NTDB_SUCCESS
)
714 if (num_found
!= num_free
) {
715 ecode
= ntdb_logerr(ntdb
, NTDB_ERR_CORRUPT
, NTDB_LOG_ERROR
,
716 "ntdb_check: Not all entries are in"
721 ntdb_allrecord_unlock(ntdb
, F_RDLCK
);
722 ntdb_unlock_expand(ntdb
, F_RDLCK
);
723 ntdb
->free_fn(fr
, ntdb
->alloc_data
);
724 ntdb
->free_fn(used
, ntdb
->alloc_data
);