gitify the tree, especially the web makefile.
[ccan] / ccan / tdb2 / tdb.c
1 #include "private.h"
2 #include <ccan/tdb2/tdb2.h>
3 #include <ccan/build_assert/build_assert.h>
4 #include <ccan/likely/likely.h>
5 #include <assert.h>
6
7 /* The null return. */
8 struct tdb_data tdb_null = { .dptr = NULL, .dsize = 0 };
9
10 /* all contexts, to ensure no double-opens (fcntl locks don't nest!) */
11 static struct tdb_context *tdbs = NULL;
12
13 PRINTF_ATTRIBUTE(4, 5) static void
14 null_log_fn(struct tdb_context *tdb,
15             enum tdb_debug_level level, void *priv,
16             const char *fmt, ...)
17 {
18 }
19
20 static bool tdb_already_open(dev_t device, ino_t ino)
21 {
22         struct tdb_context *i;
23         
24         for (i = tdbs; i; i = i->next) {
25                 if (i->device == device && i->inode == ino) {
26                         return true;
27                 }
28         }
29
30         return false;
31 }
32
33 static uint64_t random_number(struct tdb_context *tdb)
34 {
35         int fd;
36         uint64_t ret = 0;
37         struct timeval now;
38
39         fd = open("/dev/urandom", O_RDONLY);
40         if (fd >= 0) {
41                 if (tdb_read_all(fd, &ret, sizeof(ret))) {
42                         tdb->log(tdb, TDB_DEBUG_TRACE, tdb->log_priv,
43                                  "tdb_open: random from /dev/urandom\n");
44                         close(fd);
45                         return ret;
46                 }
47                 close(fd);
48         }
49         /* FIXME: Untested!  Based on Wikipedia protocol description! */
50         fd = open("/dev/egd-pool", O_RDWR);
51         if (fd >= 0) {
52                 /* Command is 1, next byte is size we want to read. */
53                 char cmd[2] = { 1, sizeof(uint64_t) };
54                 if (write(fd, cmd, sizeof(cmd)) == sizeof(cmd)) {
55                         char reply[1 + sizeof(uint64_t)];
56                         int r = read(fd, reply, sizeof(reply));
57                         if (r > 1) {
58                                 tdb->log(tdb, TDB_DEBUG_TRACE, tdb->log_priv,
59                                          "tdb_open: %u random bytes from"
60                                          " /dev/egd-pool\n", r-1);
61                                 /* Copy at least some bytes. */
62                                 memcpy(&ret, reply+1, r - 1);
63                                 if (reply[0] == sizeof(uint64_t)
64                                     && r == sizeof(reply)) {
65                                         close(fd);
66                                         return ret;
67                                 }
68                         }
69                 }
70                 close(fd);
71         }
72
73         /* Fallback: pid and time. */
74         gettimeofday(&now, NULL);
75         ret = getpid() * 100132289ULL + now.tv_sec * 1000000ULL + now.tv_usec;
76         tdb->log(tdb, TDB_DEBUG_TRACE, tdb->log_priv,
77                  "tdb_open: random from getpid and time\n");
78         return ret;
79 }
80
81 struct new_database {
82         struct tdb_header hdr;
83         /* Initial free zone. */
84         struct free_zone_header zhdr;
85         tdb_off_t free[BUCKETS_FOR_ZONE(INITIAL_ZONE_BITS) + 1];
86         struct tdb_free_record frec;
87         /* Rest up to 1 << INITIAL_ZONE_BITS is empty. */
88         char space[(1 << INITIAL_ZONE_BITS)
89                    - sizeof(struct free_zone_header)
90                    - sizeof(tdb_off_t) * (BUCKETS_FOR_ZONE(INITIAL_ZONE_BITS)+1)
91                    - sizeof(struct tdb_free_record)];
92         uint8_t tailer;
93         /* Don't count final padding! */
94 };
95
96 /* initialise a new database */
97 static int tdb_new_database(struct tdb_context *tdb,
98                             struct tdb_attribute_seed *seed,
99                             struct tdb_header *hdr)
100 {
101         /* We make it up in memory, then write it out if not internal */
102         struct new_database newdb;
103         unsigned int bucket, magic_len, dbsize;
104
105         /* Don't want any extra padding! */
106         dbsize = offsetof(struct new_database, tailer) + sizeof(newdb.tailer);
107
108         /* Fill in the header */
109         newdb.hdr.version = TDB_VERSION;
110         if (seed)
111                 newdb.hdr.hash_seed = seed->seed;
112         else
113                 newdb.hdr.hash_seed = random_number(tdb);
114         newdb.hdr.hash_test = TDB_HASH_MAGIC;
115         newdb.hdr.hash_test = tdb->khash(&newdb.hdr.hash_test,
116                                          sizeof(newdb.hdr.hash_test),
117                                          newdb.hdr.hash_seed,
118                                          tdb->hash_priv);
119         memset(newdb.hdr.reserved, 0, sizeof(newdb.hdr.reserved));
120         /* Initial hashes are empty. */
121         memset(newdb.hdr.hashtable, 0, sizeof(newdb.hdr.hashtable));
122
123         /* Free is mostly empty... */
124         newdb.zhdr.zone_bits = INITIAL_ZONE_BITS;
125         memset(newdb.free, 0, sizeof(newdb.free));
126
127         /* Create the single free entry. */
128         newdb.frec.magic_and_meta = TDB_FREE_MAGIC | INITIAL_ZONE_BITS;
129         newdb.frec.data_len = (sizeof(newdb.frec)
130                                  - sizeof(struct tdb_used_record)
131                                  + sizeof(newdb.space));
132
133         /* Add it to the correct bucket. */
134         bucket = size_to_bucket(INITIAL_ZONE_BITS, newdb.frec.data_len);
135         newdb.free[bucket] = offsetof(struct new_database, frec);
136         newdb.frec.next = newdb.frec.prev = 0;
137
138         /* Clear free space to keep valgrind happy, and avoid leaking stack. */
139         memset(newdb.space, 0, sizeof(newdb.space));
140
141         /* Tailer contains maximum number of free_zone bits. */
142         newdb.tailer = INITIAL_ZONE_BITS;
143
144         /* Magic food */
145         memset(newdb.hdr.magic_food, 0, sizeof(newdb.hdr.magic_food));
146         strcpy(newdb.hdr.magic_food, TDB_MAGIC_FOOD);
147
148         /* This creates an endian-converted database, as if read from disk */
149         magic_len = sizeof(newdb.hdr.magic_food);
150         tdb_convert(tdb,
151                     (char *)&newdb.hdr + magic_len,
152                     offsetof(struct new_database, space) - magic_len);
153
154         *hdr = newdb.hdr;
155
156         if (tdb->flags & TDB_INTERNAL) {
157                 tdb->map_size = dbsize;
158                 tdb->map_ptr = malloc(tdb->map_size);
159                 if (!tdb->map_ptr) {
160                         tdb->ecode = TDB_ERR_OOM;
161                         return -1;
162                 }
163                 memcpy(tdb->map_ptr, &newdb, tdb->map_size);
164                 return 0;
165         }
166         if (lseek(tdb->fd, 0, SEEK_SET) == -1)
167                 return -1;
168
169         if (ftruncate(tdb->fd, 0) == -1)
170                 return -1;
171
172         if (!tdb_pwrite_all(tdb->fd, &newdb, dbsize, 0)) {
173                 tdb->ecode = TDB_ERR_IO;
174                 return -1;
175         }
176         return 0;
177 }
178
179 struct tdb_context *tdb_open(const char *name, int tdb_flags,
180                              int open_flags, mode_t mode,
181                              union tdb_attribute *attr)
182 {
183         struct tdb_context *tdb;
184         struct stat st;
185         int save_errno;
186         uint64_t hash_test;
187         unsigned v;
188         struct tdb_header hdr;
189         struct tdb_attribute_seed *seed = NULL;
190
191         tdb = malloc(sizeof(*tdb));
192         if (!tdb) {
193                 /* Can't log this */
194                 errno = ENOMEM;
195                 goto fail;
196         }
197         tdb->name = NULL;
198         tdb->map_ptr = NULL;
199         tdb->direct_access = 0;
200         tdb->fd = -1;
201         tdb->map_size = sizeof(struct tdb_header);
202         tdb->ecode = TDB_SUCCESS;
203         tdb->flags = tdb_flags;
204         tdb->log = null_log_fn;
205         tdb->log_priv = NULL;
206         tdb->transaction = NULL;
207         tdb_hash_init(tdb);
208         /* last_zone will be set below. */
209         tdb_io_init(tdb);
210         tdb_lock_init(tdb);
211
212         while (attr) {
213                 switch (attr->base.attr) {
214                 case TDB_ATTRIBUTE_LOG:
215                         tdb->log = attr->log.log_fn;
216                         tdb->log_priv = attr->log.log_private;
217                         break;
218                 case TDB_ATTRIBUTE_HASH:
219                         tdb->khash = attr->hash.hash_fn;
220                         tdb->hash_priv = attr->hash.hash_private;
221                         break;
222                 case TDB_ATTRIBUTE_SEED:
223                         seed = &attr->seed;
224                         break;
225                 default:
226                         tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
227                                  "tdb_open: unknown attribute type %u\n",
228                                  attr->base.attr);
229                         errno = EINVAL;
230                         goto fail;
231                 }
232                 attr = attr->base.next;
233         }
234
235         if ((open_flags & O_ACCMODE) == O_WRONLY) {
236                 tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
237                          "tdb_open: can't open tdb %s write-only\n", name);
238                 errno = EINVAL;
239                 goto fail;
240         }
241
242         if ((open_flags & O_ACCMODE) == O_RDONLY) {
243                 tdb->read_only = true;
244                 /* read only databases don't do locking */
245                 tdb->flags |= TDB_NOLOCK;
246                 tdb->mmap_flags = PROT_READ;
247         } else {
248                 tdb->read_only = false;
249                 tdb->mmap_flags = PROT_READ | PROT_WRITE;
250         }
251
252         /* internal databases don't need any of the rest. */
253         if (tdb->flags & TDB_INTERNAL) {
254                 tdb->flags |= (TDB_NOLOCK | TDB_NOMMAP);
255                 if (tdb_new_database(tdb, seed, &hdr) != 0) {
256                         tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
257                                  "tdb_open: tdb_new_database failed!");
258                         goto fail;
259                 }
260                 tdb_convert(tdb, &hdr.hash_seed, sizeof(hdr.hash_seed));
261                 tdb->hash_seed = hdr.hash_seed;
262                 tdb_zone_init(tdb);
263                 return tdb;
264         }
265
266         if ((tdb->fd = open(name, open_flags, mode)) == -1) {
267                 tdb->log(tdb, TDB_DEBUG_WARNING, tdb->log_priv,
268                          "tdb_open: could not open file %s: %s\n",
269                          name, strerror(errno));
270                 goto fail;      /* errno set by open(2) */
271         }
272
273         /* on exec, don't inherit the fd */
274         v = fcntl(tdb->fd, F_GETFD, 0);
275         fcntl(tdb->fd, F_SETFD, v | FD_CLOEXEC);
276
277         /* ensure there is only one process initialising at once */
278         if (tdb_lock_open(tdb) == -1) {
279                 tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
280                          "tdb_open: failed to get open lock on %s: %s\n",
281                          name, strerror(errno));
282                 goto fail;      /* errno set by tdb_brlock */
283         }
284
285         if (!tdb_pread_all(tdb->fd, &hdr, sizeof(hdr), 0)
286             || strcmp(hdr.magic_food, TDB_MAGIC_FOOD) != 0) {
287                 if (!(open_flags & O_CREAT)
288                     || tdb_new_database(tdb, seed, &hdr) == -1) {
289                         if (errno == 0) {
290                                 errno = EIO; /* ie bad format or something */
291                         }
292                         goto fail;
293                 }
294         } else if (hdr.version != TDB_VERSION) {
295                 if (hdr.version == bswap_64(TDB_VERSION))
296                         tdb->flags |= TDB_CONVERT;
297                 else {
298                         /* wrong version */
299                         tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
300                                  "tdb_open: %s is unknown version 0x%llx\n",
301                                  name, (long long)hdr.version);
302                         errno = EIO;
303                         goto fail;
304                 }
305         }
306
307         tdb_convert(tdb, &hdr, sizeof(hdr));
308         tdb->hash_seed = hdr.hash_seed;
309         hash_test = TDB_HASH_MAGIC;
310         hash_test = tdb_hash(tdb, &hash_test, sizeof(hash_test));
311         if (hdr.hash_test != hash_test) {
312                 /* wrong hash variant */
313                 tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
314                          "tdb_open: %s uses a different hash function\n",
315                          name);
316                 errno = EIO;
317                 goto fail;
318         }
319
320         if (fstat(tdb->fd, &st) == -1)
321                 goto fail;
322
323         /* Is it already in the open list?  If so, fail. */
324         if (tdb_already_open(st.st_dev, st.st_ino)) {
325                 /* FIXME */
326                 tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
327                          "tdb_open: %s (%d,%d) is already open in this process\n",
328                          name, (int)st.st_dev, (int)st.st_ino);
329                 errno = EBUSY;
330                 goto fail;
331         }
332
333         tdb->name = strdup(name);
334         if (!tdb->name) {
335                 errno = ENOMEM;
336                 goto fail;
337         }
338
339         tdb->device = st.st_dev;
340         tdb->inode = st.st_ino;
341         tdb_unlock_open(tdb);
342
343         /* This make sure we have current map_size and mmap. */
344         tdb->methods->oob(tdb, tdb->map_size + 1, true);
345
346         /* Now we can pick a random free zone to start from. */
347         if (tdb_zone_init(tdb) == -1)
348                 goto fail;
349
350         tdb->next = tdbs;
351         tdbs = tdb;
352         return tdb;
353
354  fail:
355         save_errno = errno;
356
357         if (!tdb)
358                 return NULL;
359
360 #ifdef TDB_TRACE
361         close(tdb->tracefd);
362 #endif
363         if (tdb->map_ptr) {
364                 if (tdb->flags & TDB_INTERNAL) {
365                         free(tdb->map_ptr);
366                 } else
367                         tdb_munmap(tdb);
368         }
369         free((char *)tdb->name);
370         if (tdb->fd != -1)
371                 if (close(tdb->fd) != 0)
372                         tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
373                                  "tdb_open: failed to close tdb->fd"
374                                  " on error!\n");
375         free(tdb);
376         errno = save_errno;
377         return NULL;
378 }
379
380 /* FIXME: modify, don't rewrite! */
381 static int update_rec_hdr(struct tdb_context *tdb,
382                           tdb_off_t off,
383                           tdb_len_t keylen,
384                           tdb_len_t datalen,
385                           struct tdb_used_record *rec,
386                           uint64_t h)
387 {
388         uint64_t dataroom = rec_data_length(rec) + rec_extra_padding(rec);
389
390         if (set_header(tdb, rec, keylen, datalen, keylen + dataroom, h,
391                        rec_zone_bits(rec)))
392                 return -1;
393
394         return tdb_write_convert(tdb, off, rec, sizeof(*rec));
395 }
396
397 /* Returns -1 on error, 0 on OK */
398 static int replace_data(struct tdb_context *tdb,
399                         struct hash_info *h,
400                         struct tdb_data key, struct tdb_data dbuf,
401                         tdb_off_t old_off, tdb_len_t old_room,
402                         unsigned old_zone,
403                         bool growing)
404 {
405         tdb_off_t new_off;
406
407         /* Allocate a new record. */
408         new_off = alloc(tdb, key.dsize, dbuf.dsize, h->h, growing);
409         if (unlikely(new_off == TDB_OFF_ERR))
410                 return -1;
411
412         /* We didn't like the existing one: remove it. */
413         if (old_off) {
414                 add_free_record(tdb, old_zone, old_off,
415                                 sizeof(struct tdb_used_record)
416                                 + key.dsize + old_room);
417                 if (replace_in_hash(tdb, h, new_off) == -1)
418                         return -1;
419         } else {
420                 if (add_to_hash(tdb, h, new_off) == -1)
421                         return -1;
422         }
423
424         new_off += sizeof(struct tdb_used_record);
425         if (tdb->methods->write(tdb, new_off, key.dptr, key.dsize) == -1)
426                 return -1;
427
428         new_off += key.dsize;
429         if (tdb->methods->write(tdb, new_off, dbuf.dptr, dbuf.dsize) == -1)
430                 return -1;
431
432         /* FIXME: tdb_increment_seqnum(tdb); */
433         return 0;
434 }
435
436 int tdb_store(struct tdb_context *tdb,
437               struct tdb_data key, struct tdb_data dbuf, int flag)
438 {
439         struct hash_info h;
440         tdb_off_t off;
441         tdb_len_t old_room = 0;
442         struct tdb_used_record rec;
443         int ret;
444
445         off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL);
446         if (unlikely(off == TDB_OFF_ERR))
447                 return -1;
448
449         /* Now we have lock on this hash bucket. */
450         if (flag == TDB_INSERT) {
451                 if (off) {
452                         tdb->ecode = TDB_ERR_EXISTS;
453                         goto fail;
454                 }
455         } else {
456                 if (off) {
457                         old_room = rec_data_length(&rec)
458                                 + rec_extra_padding(&rec);
459                         if (old_room >= dbuf.dsize) {
460                                 /* Can modify in-place.  Easy! */
461                                 if (update_rec_hdr(tdb, off,
462                                                    key.dsize, dbuf.dsize,
463                                                    &rec, h.h))
464                                         goto fail;
465                                 if (tdb->methods->write(tdb, off + sizeof(rec)
466                                                         + key.dsize,
467                                                         dbuf.dptr, dbuf.dsize))
468                                         goto fail;
469                                 tdb_unlock_hashes(tdb, h.hlock_start,
470                                                   h.hlock_range, F_WRLCK);
471                                 return 0;
472                         }
473                         /* FIXME: See if right record is free? */
474                 } else {
475                         if (flag == TDB_MODIFY) {
476                                 /* if the record doesn't exist and we
477                                    are in TDB_MODIFY mode then we should fail
478                                    the store */
479                                 tdb->ecode = TDB_ERR_NOEXIST;
480                                 goto fail;
481                         }
482                 }
483         }
484
485         /* If we didn't use the old record, this implies we're growing. */
486         ret = replace_data(tdb, &h, key, dbuf, off, old_room,
487                            rec_zone_bits(&rec), off != 0);
488         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
489         return ret;
490
491 fail:
492         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
493         return -1;
494 }
495
496 int tdb_append(struct tdb_context *tdb,
497                struct tdb_data key, struct tdb_data dbuf)
498 {
499         struct hash_info h;
500         tdb_off_t off;
501         struct tdb_used_record rec;
502         tdb_len_t old_room = 0, old_dlen;
503         unsigned char *newdata;
504         struct tdb_data new_dbuf;
505         int ret;
506
507         off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL);
508         if (unlikely(off == TDB_OFF_ERR))
509                 return -1;
510
511         if (off) {
512                 old_dlen = rec_data_length(&rec);
513                 old_room = old_dlen + rec_extra_padding(&rec);
514
515                 /* Fast path: can append in place. */
516                 if (rec_extra_padding(&rec) >= dbuf.dsize) {
517                         if (update_rec_hdr(tdb, off, key.dsize,
518                                            old_dlen + dbuf.dsize, &rec, h.h))
519                                 goto fail;
520
521                         off += sizeof(rec) + key.dsize + old_dlen;
522                         if (tdb->methods->write(tdb, off, dbuf.dptr,
523                                                 dbuf.dsize) == -1)
524                                 goto fail;
525
526                         /* FIXME: tdb_increment_seqnum(tdb); */
527                         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
528                                           F_WRLCK);
529                         return 0;
530                 }
531                 /* FIXME: Check right record free? */
532
533                 /* Slow path. */
534                 newdata = malloc(key.dsize + old_dlen + dbuf.dsize);
535                 if (!newdata) {
536                         tdb->ecode = TDB_ERR_OOM;
537                         tdb->log(tdb, TDB_DEBUG_FATAL, tdb->log_priv,
538                                  "tdb_append: cannot allocate %llu bytes!\n",
539                                  (long long)key.dsize + old_dlen + dbuf.dsize);
540                         goto fail;
541                 }
542                 if (tdb->methods->read(tdb, off + sizeof(rec) + key.dsize,
543                                        newdata, old_dlen) != 0) {
544                         free(newdata);
545                         goto fail;
546                 }
547                 memcpy(newdata + old_dlen, dbuf.dptr, dbuf.dsize);
548                 new_dbuf.dptr = newdata;
549                 new_dbuf.dsize = old_dlen + dbuf.dsize;
550         } else {
551                 newdata = NULL;
552                 new_dbuf = dbuf;
553         }
554
555         /* If they're using tdb_append(), it implies they're growing record. */
556         ret = replace_data(tdb, &h, key, new_dbuf, off,
557                            old_room, rec_zone_bits(&rec), true);
558         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
559         free(newdata);
560
561         return ret;
562
563 fail:
564         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
565         return -1;
566 }
567
568 struct tdb_data tdb_fetch(struct tdb_context *tdb, struct tdb_data key)
569 {
570         tdb_off_t off;
571         struct tdb_used_record rec;
572         struct hash_info h;
573         struct tdb_data ret;
574
575         off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL);
576         if (unlikely(off == TDB_OFF_ERR))
577                 return tdb_null;
578
579         if (!off) {
580                 tdb->ecode = TDB_ERR_NOEXIST;
581                 ret = tdb_null;
582         } else {
583                 ret.dsize = rec_data_length(&rec);
584                 ret.dptr = tdb_alloc_read(tdb, off + sizeof(rec) + key.dsize,
585                                           ret.dsize);
586         }
587
588         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK);
589         return ret;
590 }
591
592 int tdb_delete(struct tdb_context *tdb, struct tdb_data key)
593 {
594         tdb_off_t off;
595         struct tdb_used_record rec;
596         struct hash_info h;
597
598         off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL);
599         if (unlikely(off == TDB_OFF_ERR))
600                 return -1;
601
602         if (!off) {
603                 tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
604                 tdb->ecode = TDB_ERR_NOEXIST;
605                 return -1;
606         }
607
608         if (delete_from_hash(tdb, &h) == -1)
609                 goto unlock_err;
610
611         /* Free the deleted entry. */
612         if (add_free_record(tdb, rec_zone_bits(&rec), off,
613                             sizeof(struct tdb_used_record)
614                             + rec_key_length(&rec)
615                             + rec_data_length(&rec)
616                             + rec_extra_padding(&rec)) != 0)
617                 goto unlock_err;
618
619         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
620         return 0;
621
622 unlock_err:
623         tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK);
624         return -1;
625 }
626
627 int tdb_close(struct tdb_context *tdb)
628 {
629         struct tdb_context **i;
630         int ret = 0;
631
632         /* FIXME:
633         if (tdb->transaction) {
634                 tdb_transaction_cancel(tdb);
635         }
636         */
637         tdb_trace(tdb, "tdb_close");
638
639         if (tdb->map_ptr) {
640                 if (tdb->flags & TDB_INTERNAL)
641                         free(tdb->map_ptr);
642                 else
643                         tdb_munmap(tdb);
644         }
645         free((char *)tdb->name);
646         if (tdb->fd != -1) {
647                 ret = close(tdb->fd);
648                 tdb->fd = -1;
649         }
650         free(tdb->lockrecs);
651
652         /* Remove from contexts list */
653         for (i = &tdbs; *i; i = &(*i)->next) {
654                 if (*i == tdb) {
655                         *i = tdb->next;
656                         break;
657                 }
658         }
659
660 #ifdef TDB_TRACE
661         close(tdb->tracefd);
662 #endif
663         free(tdb);
664
665         return ret;
666 }
667
668 enum TDB_ERROR tdb_error(struct tdb_context *tdb)
669 {
670         return tdb->ecode;
671 }
672
673 const char *tdb_errorstr(struct tdb_context *tdb)
674 {
675         /* Gcc warns if you miss a case in the switch, so use that. */
676         switch (tdb->ecode) {
677         case TDB_SUCCESS: return "Success";
678         case TDB_ERR_CORRUPT: return "Corrupt database";
679         case TDB_ERR_IO: return "IO Error";
680         case TDB_ERR_LOCK: return "Locking error";
681         case TDB_ERR_OOM: return "Out of memory";
682         case TDB_ERR_EXISTS: return "Record exists";
683         case TDB_ERR_NESTING: return "Transaction already started";
684         case TDB_ERR_EINVAL: return "Invalid parameter";
685         case TDB_ERR_NOEXIST: return "Record does not exist";
686         case TDB_ERR_RDONLY: return "write not permitted";
687         }
688         return "Invalid error code";
689 }