2 Trivial Database 2: opening and closing TDBs
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/build_assert/build_assert.h>
22 /* all tdbs, to detect double-opens (fcntl file don't nest!) */
23 static struct tdb_context
*tdbs
= NULL
;
25 static struct tdb_file
*find_file(dev_t device
, ino_t ino
)
27 struct tdb_context
*i
;
29 for (i
= tdbs
; i
; i
= i
->next
) {
30 if (i
->file
->device
== device
&& i
->file
->inode
== ino
) {
38 static bool read_all(int fd
, void *buf
, size_t len
)
42 ret
= read(fd
, buf
, len
);
50 buf
= (char *)buf
+ ret
;
56 static uint64_t random_number(struct tdb_context
*tdb
)
62 fd
= open("/dev/urandom", O_RDONLY
);
64 if (read_all(fd
, &ret
, sizeof(ret
))) {
70 /* FIXME: Untested! Based on Wikipedia protocol description! */
71 fd
= open("/dev/egd-pool", O_RDWR
);
73 /* Command is 1, next byte is size we want to read. */
74 char cmd
[2] = { 1, sizeof(uint64_t) };
75 if (write(fd
, cmd
, sizeof(cmd
)) == sizeof(cmd
)) {
76 char reply
[1 + sizeof(uint64_t)];
77 int r
= read(fd
, reply
, sizeof(reply
));
79 /* Copy at least some bytes. */
80 memcpy(&ret
, reply
+1, r
- 1);
81 if (reply
[0] == sizeof(uint64_t)
82 && r
== sizeof(reply
)) {
91 /* Fallback: pid and time. */
92 gettimeofday(&now
, NULL
);
93 ret
= getpid() * 100132289ULL + now
.tv_sec
* 1000000ULL + now
.tv_usec
;
94 tdb_logerr(tdb
, TDB_SUCCESS
, TDB_LOG_WARNING
,
95 "tdb_open: random from getpid and time");
99 static void tdb2_context_init(struct tdb_context
*tdb
)
101 /* Initialize the TDB2 fields here */
103 tdb
->direct_access
= 0;
104 tdb
->transaction
= NULL
;
108 struct new_database
{
109 struct tdb_header hdr
;
110 struct tdb_freetable ftable
;
113 /* initialise a new database */
114 static enum TDB_ERROR
tdb_new_database(struct tdb_context
*tdb
,
115 struct tdb_attribute_seed
*seed
,
116 struct tdb_header
*hdr
)
118 /* We make it up in memory, then write it out if not internal */
119 struct new_database newdb
;
120 unsigned int magic_len
;
122 enum TDB_ERROR ecode
;
124 /* Fill in the header */
125 newdb
.hdr
.version
= TDB_VERSION
;
127 newdb
.hdr
.hash_seed
= seed
->seed
;
129 newdb
.hdr
.hash_seed
= random_number(tdb
);
130 newdb
.hdr
.hash_test
= TDB_HASH_MAGIC
;
131 newdb
.hdr
.hash_test
= tdb
->hash_fn(&newdb
.hdr
.hash_test
,
132 sizeof(newdb
.hdr
.hash_test
),
135 newdb
.hdr
.recovery
= 0;
136 newdb
.hdr
.features_used
= newdb
.hdr
.features_offered
= TDB_FEATURE_MASK
;
137 newdb
.hdr
.seqnum
= 0;
138 newdb
.hdr
.capabilities
= 0;
139 memset(newdb
.hdr
.reserved
, 0, sizeof(newdb
.hdr
.reserved
));
140 /* Initial hashes are empty. */
141 memset(newdb
.hdr
.hashtable
, 0, sizeof(newdb
.hdr
.hashtable
));
144 newdb
.hdr
.free_table
= offsetof(struct new_database
, ftable
);
145 memset(&newdb
.ftable
, 0, sizeof(newdb
.ftable
));
146 ecode
= set_header(NULL
, &newdb
.ftable
.hdr
, TDB_FTABLE_MAGIC
, 0,
147 sizeof(newdb
.ftable
) - sizeof(newdb
.ftable
.hdr
),
148 sizeof(newdb
.ftable
) - sizeof(newdb
.ftable
.hdr
),
150 if (ecode
!= TDB_SUCCESS
) {
155 memset(newdb
.hdr
.magic_food
, 0, sizeof(newdb
.hdr
.magic_food
));
156 strcpy(newdb
.hdr
.magic_food
, TDB_MAGIC_FOOD
);
158 /* This creates an endian-converted database, as if read from disk */
159 magic_len
= sizeof(newdb
.hdr
.magic_food
);
161 (char *)&newdb
.hdr
+ magic_len
, sizeof(newdb
) - magic_len
);
165 if (tdb
->flags
& TDB_INTERNAL
) {
166 tdb
->file
->map_size
= sizeof(newdb
);
167 tdb
->file
->map_ptr
= malloc(tdb
->file
->map_size
);
168 if (!tdb
->file
->map_ptr
) {
169 return tdb_logerr(tdb
, TDB_ERR_OOM
, TDB_LOG_ERROR
,
171 " failed to allocate");
173 memcpy(tdb
->file
->map_ptr
, &newdb
, tdb
->file
->map_size
);
176 if (lseek(tdb
->file
->fd
, 0, SEEK_SET
) == -1) {
177 return tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
179 " failed to seek: %s", strerror(errno
));
182 if (ftruncate(tdb
->file
->fd
, 0) == -1) {
183 return tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
185 " failed to truncate: %s", strerror(errno
));
188 rlen
= write(tdb
->file
->fd
, &newdb
, sizeof(newdb
));
189 if (rlen
!= sizeof(newdb
)) {
192 return tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
193 "tdb_new_database: %zi writing header: %s",
194 rlen
, strerror(errno
));
199 static enum TDB_ERROR
tdb_new_file(struct tdb_context
*tdb
)
201 tdb
->file
= malloc(sizeof(*tdb
->file
));
203 return tdb_logerr(tdb
, TDB_ERR_OOM
, TDB_LOG_ERROR
,
204 "tdb_open: cannot alloc tdb_file structure");
205 tdb
->file
->num_lockrecs
= 0;
206 tdb
->file
->lockrecs
= NULL
;
207 tdb
->file
->allrecord_lock
.count
= 0;
208 tdb
->file
->refcnt
= 1;
209 tdb
->file
->map_ptr
= NULL
;
213 _PUBLIC_
enum TDB_ERROR
tdb_set_attribute(struct tdb_context
*tdb
,
214 const union tdb_attribute
*attr
)
216 switch (attr
->base
.attr
) {
217 case TDB_ATTRIBUTE_LOG
:
218 tdb
->log_fn
= attr
->log
.fn
;
219 tdb
->log_data
= attr
->log
.data
;
221 case TDB_ATTRIBUTE_HASH
:
222 case TDB_ATTRIBUTE_SEED
:
223 case TDB_ATTRIBUTE_OPENHOOK
:
224 return tdb
->last_error
225 = tdb_logerr(tdb
, TDB_ERR_EINVAL
,
228 " cannot set %s after opening",
229 attr
->base
.attr
== TDB_ATTRIBUTE_HASH
230 ? "TDB_ATTRIBUTE_HASH"
231 : attr
->base
.attr
== TDB_ATTRIBUTE_SEED
232 ? "TDB_ATTRIBUTE_SEED"
233 : "TDB_ATTRIBUTE_OPENHOOK");
234 case TDB_ATTRIBUTE_STATS
:
235 return tdb
->last_error
236 = tdb_logerr(tdb
, TDB_ERR_EINVAL
,
239 " cannot set TDB_ATTRIBUTE_STATS");
240 case TDB_ATTRIBUTE_FLOCK
:
241 tdb
->lock_fn
= attr
->flock
.lock
;
242 tdb
->unlock_fn
= attr
->flock
.unlock
;
243 tdb
->lock_data
= attr
->flock
.data
;
246 return tdb
->last_error
247 = tdb_logerr(tdb
, TDB_ERR_EINVAL
,
250 " unknown attribute type %u",
256 _PUBLIC_
enum TDB_ERROR
tdb_get_attribute(struct tdb_context
*tdb
,
257 union tdb_attribute
*attr
)
259 switch (attr
->base
.attr
) {
260 case TDB_ATTRIBUTE_LOG
:
262 return tdb
->last_error
= TDB_ERR_NOEXIST
;
263 attr
->log
.fn
= tdb
->log_fn
;
264 attr
->log
.data
= tdb
->log_data
;
266 case TDB_ATTRIBUTE_HASH
:
267 attr
->hash
.fn
= tdb
->hash_fn
;
268 attr
->hash
.data
= tdb
->hash_data
;
270 case TDB_ATTRIBUTE_SEED
:
271 attr
->seed
.seed
= tdb
->hash_seed
;
273 case TDB_ATTRIBUTE_OPENHOOK
:
275 return tdb
->last_error
= TDB_ERR_NOEXIST
;
276 attr
->openhook
.fn
= tdb
->openhook
;
277 attr
->openhook
.data
= tdb
->openhook_data
;
279 case TDB_ATTRIBUTE_STATS
: {
280 size_t size
= attr
->stats
.size
;
281 if (size
> tdb
->stats
.size
)
282 size
= tdb
->stats
.size
;
283 memcpy(&attr
->stats
, &tdb
->stats
, size
);
286 case TDB_ATTRIBUTE_FLOCK
:
287 attr
->flock
.lock
= tdb
->lock_fn
;
288 attr
->flock
.unlock
= tdb
->unlock_fn
;
289 attr
->flock
.data
= tdb
->lock_data
;
292 return tdb
->last_error
293 = tdb_logerr(tdb
, TDB_ERR_EINVAL
,
296 " unknown attribute type %u",
299 attr
->base
.next
= NULL
;
303 _PUBLIC_
void tdb_unset_attribute(struct tdb_context
*tdb
,
304 enum tdb_attribute_type type
)
307 case TDB_ATTRIBUTE_LOG
:
310 case TDB_ATTRIBUTE_OPENHOOK
:
311 tdb
->openhook
= NULL
;
313 case TDB_ATTRIBUTE_HASH
:
314 case TDB_ATTRIBUTE_SEED
:
315 tdb_logerr(tdb
, TDB_ERR_EINVAL
, TDB_LOG_USE_ERROR
,
316 "tdb_unset_attribute: cannot unset %s after opening",
317 type
== TDB_ATTRIBUTE_HASH
318 ? "TDB_ATTRIBUTE_HASH"
319 : "TDB_ATTRIBUTE_SEED");
321 case TDB_ATTRIBUTE_STATS
:
322 tdb_logerr(tdb
, TDB_ERR_EINVAL
,
324 "tdb_unset_attribute:"
325 "cannot unset TDB_ATTRIBUTE_STATS");
327 case TDB_ATTRIBUTE_FLOCK
:
328 tdb
->lock_fn
= tdb_fcntl_lock
;
329 tdb
->unlock_fn
= tdb_fcntl_unlock
;
332 tdb_logerr(tdb
, TDB_ERR_EINVAL
,
334 "tdb_unset_attribute: unknown attribute type %u",
339 /* The top three bits of the capability tell us whether it matters. */
340 enum TDB_ERROR
unknown_capability(struct tdb_context
*tdb
, const char *caller
,
343 if (type
& TDB_CAP_NOOPEN
) {
344 return tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
345 "%s: file has unknown capability %llu",
346 caller
, type
& TDB_CAP_NOOPEN
);
349 if ((type
& TDB_CAP_NOWRITE
) && !(tdb
->flags
& TDB_RDONLY
)) {
350 return tdb_logerr(tdb
, TDB_ERR_RDONLY
, TDB_LOG_ERROR
,
351 "%s: file has unknown capability %llu"
352 " (cannot write to it)",
353 caller
, type
& TDB_CAP_NOOPEN
);
356 if (type
& TDB_CAP_NOCHECK
) {
357 tdb
->flags
|= TDB_CANT_CHECK
;
362 static enum TDB_ERROR
capabilities_ok(struct tdb_context
*tdb
,
363 tdb_off_t capabilities
)
366 enum TDB_ERROR ecode
= TDB_SUCCESS
;
367 const struct tdb_capability
*cap
;
369 /* Check capability list. */
370 for (off
= capabilities
; off
&& ecode
== TDB_SUCCESS
; off
= next
) {
371 cap
= tdb_access_read(tdb
, off
, sizeof(*cap
), true);
372 if (TDB_PTR_IS_ERR(cap
)) {
373 return TDB_PTR_ERR(cap
);
376 switch (cap
->type
& TDB_CAP_TYPE_MASK
) {
377 /* We don't understand any capabilities (yet). */
379 ecode
= unknown_capability(tdb
, "tdb_open", cap
->type
);
382 tdb_access_release(tdb
, cap
);
387 _PUBLIC_
struct tdb_context
*tdb_open(const char *name
, int tdb_flags
,
388 int open_flags
, mode_t mode
,
389 union tdb_attribute
*attr
)
391 struct tdb_context
*tdb
;
397 struct tdb_header hdr
;
398 struct tdb_attribute_seed
*seed
= NULL
;
400 enum TDB_ERROR ecode
;
403 tdb
= malloc(sizeof(*tdb
) + (name
? strlen(name
) + 1 : 0));
409 /* Set name immediately for logging functions. */
411 tdb
->name
= strcpy((char *)(tdb
+ 1), name
);
415 tdb
->flags
= tdb_flags
;
417 tdb
->open_flags
= open_flags
;
418 tdb
->last_error
= TDB_SUCCESS
;
420 tdb
->openhook
= NULL
;
421 tdb
->lock_fn
= tdb_fcntl_lock
;
422 tdb
->unlock_fn
= tdb_fcntl_unlock
;
423 tdb
->hash_fn
= tdb_jenkins_hash
;
424 memset(&tdb
->stats
, 0, sizeof(tdb
->stats
));
425 tdb
->stats
.base
.attr
= TDB_ATTRIBUTE_STATS
;
426 tdb
->stats
.size
= sizeof(tdb
->stats
);
429 switch (attr
->base
.attr
) {
430 case TDB_ATTRIBUTE_HASH
:
431 tdb
->hash_fn
= attr
->hash
.fn
;
432 tdb
->hash_data
= attr
->hash
.data
;
434 case TDB_ATTRIBUTE_SEED
:
437 case TDB_ATTRIBUTE_OPENHOOK
:
438 tdb
->openhook
= attr
->openhook
.fn
;
439 tdb
->openhook_data
= attr
->openhook
.data
;
442 /* These are set as normal. */
443 ecode
= tdb_set_attribute(tdb
, attr
);
444 if (ecode
!= TDB_SUCCESS
)
447 attr
= attr
->base
.next
;
450 if (tdb_flags
& ~(TDB_INTERNAL
| TDB_NOLOCK
| TDB_NOMMAP
| TDB_CONVERT
451 | TDB_NOSYNC
| TDB_SEQNUM
| TDB_ALLOW_NESTING
453 ecode
= tdb_logerr(tdb
, TDB_ERR_EINVAL
, TDB_LOG_USE_ERROR
,
454 "tdb_open: unknown flags %u", tdb_flags
);
459 if (!(tdb_flags
& TDB_INTERNAL
) && !(open_flags
& O_CREAT
)) {
460 ecode
= tdb_logerr(tdb
, TDB_ERR_EINVAL
,
463 " cannot set TDB_ATTRIBUTE_SEED"
464 " without O_CREAT.");
469 if ((open_flags
& O_ACCMODE
) == O_WRONLY
) {
470 ecode
= tdb_logerr(tdb
, TDB_ERR_EINVAL
, TDB_LOG_USE_ERROR
,
471 "tdb_open: can't open tdb %s write-only",
476 if ((open_flags
& O_ACCMODE
) == O_RDONLY
) {
478 tdb
->flags
|= TDB_RDONLY
;
480 if (tdb_flags
& TDB_RDONLY
) {
481 ecode
= tdb_logerr(tdb
, TDB_ERR_EINVAL
,
483 "tdb_open: can't use TDB_RDONLY"
484 " without O_RDONLY");
490 /* internal databases don't need any of the rest. */
491 if (tdb
->flags
& TDB_INTERNAL
) {
492 tdb
->flags
|= (TDB_NOLOCK
| TDB_NOMMAP
);
493 ecode
= tdb_new_file(tdb
);
494 if (ecode
!= TDB_SUCCESS
) {
498 ecode
= tdb_new_database(tdb
, seed
, &hdr
);
499 if (ecode
== TDB_SUCCESS
) {
500 tdb_convert(tdb
, &hdr
.hash_seed
,
501 sizeof(hdr
.hash_seed
));
502 tdb
->hash_seed
= hdr
.hash_seed
;
503 tdb2_context_init(tdb
);
504 tdb_ftable_init(tdb
);
506 if (ecode
!= TDB_SUCCESS
) {
512 if (stat(name
, &st
) != -1)
513 tdb
->file
= find_file(st
.st_dev
, st
.st_ino
);
518 if ((fd
= open(name
, open_flags
, mode
)) == -1) {
519 /* errno set by open(2) */
521 tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
522 "tdb_open: could not open file %s: %s",
523 name
, strerror(errno
));
527 /* on exec, don't inherit the fd */
528 v
= fcntl(fd
, F_GETFD
, 0);
529 fcntl(fd
, F_SETFD
, v
| FD_CLOEXEC
);
531 if (fstat(fd
, &st
) == -1) {
533 tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
534 "tdb_open: could not stat open %s: %s",
535 name
, strerror(errno
));
540 ecode
= tdb_new_file(tdb
);
541 if (ecode
!= TDB_SUCCESS
) {
547 tdb
->file
->device
= st
.st_dev
;
548 tdb
->file
->inode
= st
.st_ino
;
549 tdb
->file
->map_ptr
= NULL
;
550 tdb
->file
->map_size
= 0;
553 /* ensure there is only one process initialising at once */
554 ecode
= tdb_lock_open(tdb
, openlock
, TDB_LOCK_WAIT
|TDB_LOCK_NOCHECK
);
555 if (ecode
!= TDB_SUCCESS
) {
560 /* call their open hook if they gave us one. */
562 ecode
= tdb
->openhook(tdb
->file
->fd
, tdb
->openhook_data
);
563 if (ecode
!= TDB_SUCCESS
) {
564 tdb_logerr(tdb
, ecode
, TDB_LOG_ERROR
,
565 "tdb_open: open hook failed");
568 open_flags
|= O_CREAT
;
571 /* If they used O_TRUNC, read will return 0. */
572 rlen
= pread(tdb
->file
->fd
, &hdr
, sizeof(hdr
), 0);
573 if (rlen
== 0 && (open_flags
& O_CREAT
)) {
574 ecode
= tdb_new_database(tdb
, seed
, &hdr
);
575 if (ecode
!= TDB_SUCCESS
) {
578 } else if (rlen
< 0) {
579 ecode
= tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
580 "tdb_open: error %s reading %s",
581 strerror(errno
), name
);
583 } else if (rlen
< sizeof(hdr
)
584 || strcmp(hdr
.magic_food
, TDB_MAGIC_FOOD
) != 0) {
585 ecode
= tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
586 "tdb_open: %s is not a tdb2 file", name
);
590 if (hdr
.version
!= TDB_VERSION
) {
591 if (hdr
.version
== bswap_64(TDB_VERSION
))
592 tdb
->flags
|= TDB_CONVERT
;
595 ecode
= tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
597 " %s is unknown version 0x%llx",
598 name
, (long long)hdr
.version
);
601 } else if (tdb
->flags
& TDB_CONVERT
) {
602 ecode
= tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
604 " %s does not need TDB_CONVERT",
609 tdb2_context_init(tdb
);
611 tdb_convert(tdb
, &hdr
, sizeof(hdr
));
612 tdb
->hash_seed
= hdr
.hash_seed
;
613 hash_test
= TDB_HASH_MAGIC
;
614 hash_test
= tdb_hash(tdb
, &hash_test
, sizeof(hash_test
));
615 if (hdr
.hash_test
!= hash_test
) {
616 /* wrong hash variant */
617 ecode
= tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
619 " %s uses a different hash function",
624 ecode
= capabilities_ok(tdb
, hdr
.capabilities
);
625 if (ecode
!= TDB_SUCCESS
) {
629 /* Clear any features we don't understand. */
630 if ((open_flags
& O_ACCMODE
) != O_RDONLY
) {
631 hdr
.features_used
&= TDB_FEATURE_MASK
;
632 ecode
= tdb_write_convert(tdb
, offsetof(struct tdb_header
,
635 sizeof(hdr
.features_used
));
636 if (ecode
!= TDB_SUCCESS
)
640 tdb_unlock_open(tdb
, openlock
);
642 /* This makes sure we have current map_size and mmap. */
643 ecode
= tdb
->io
->oob(tdb
, tdb
->file
->map_size
, 1, true);
644 if (unlikely(ecode
!= TDB_SUCCESS
))
647 /* Now it's fully formed, recover if necessary. */
648 berr
= tdb_needs_recovery(tdb
);
649 if (unlikely(berr
!= false)) {
651 ecode
= TDB_OFF_TO_ERR(berr
);
654 ecode
= tdb_lock_and_recover(tdb
);
655 if (ecode
!= TDB_SUCCESS
) {
660 ecode
= tdb_ftable_init(tdb
);
661 if (ecode
!= TDB_SUCCESS
) {
670 /* Map ecode to some logical errno. */
671 switch (TDB_ERR_TO_OFF(ecode
)) {
672 case TDB_ERR_TO_OFF(TDB_ERR_CORRUPT
):
673 case TDB_ERR_TO_OFF(TDB_ERR_IO
):
676 case TDB_ERR_TO_OFF(TDB_ERR_LOCK
):
677 saved_errno
= EWOULDBLOCK
;
679 case TDB_ERR_TO_OFF(TDB_ERR_OOM
):
680 saved_errno
= ENOMEM
;
682 case TDB_ERR_TO_OFF(TDB_ERR_EINVAL
):
683 saved_errno
= EINVAL
;
686 saved_errno
= EINVAL
;
695 tdb_lock_cleanup(tdb
);
696 if (--tdb
->file
->refcnt
== 0) {
697 assert(tdb
->file
->num_lockrecs
== 0);
698 if (tdb
->file
->map_ptr
) {
699 if (tdb
->flags
& TDB_INTERNAL
) {
700 free(tdb
->file
->map_ptr
);
702 tdb_munmap(tdb
->file
);
704 if (close(tdb
->file
->fd
) != 0)
705 tdb_logerr(tdb
, TDB_ERR_IO
, TDB_LOG_ERROR
,
706 "tdb_open: failed to close tdb fd"
707 " on error: %s", strerror(errno
));
708 free(tdb
->file
->lockrecs
);
718 _PUBLIC_
int tdb_close(struct tdb_context
*tdb
)
721 struct tdb_context
**i
;
723 tdb_trace(tdb
, "tdb_close");
725 if (tdb
->transaction
) {
726 tdb_transaction_cancel(tdb
);
729 if (tdb
->file
->map_ptr
) {
730 if (tdb
->flags
& TDB_INTERNAL
)
731 free(tdb
->file
->map_ptr
);
733 tdb_munmap(tdb
->file
);
736 tdb_lock_cleanup(tdb
);
737 if (--tdb
->file
->refcnt
== 0) {
738 ret
= close(tdb
->file
->fd
);
739 free(tdb
->file
->lockrecs
);
744 /* Remove from tdbs list */
745 for (i
= &tdbs
; *i
; i
= &(*i
)->next
) {
760 _PUBLIC_
void tdb_foreach_(int (*fn
)(struct tdb_context
*, void *), void *p
)
762 struct tdb_context
*i
;
764 for (i
= tdbs
; i
; i
= i
->next
) {