1 /*
2 * Old binary tree database backend.
3 *
4 * Copyright 2007 Andrew Wood, distributed under the Artistic License.
5 */
6
7 #include "config.h"
8 #include "database.h"
9 #include "log.h"
10
11 #ifdef USING_OBTREE
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <signal.h>
18 #include <unistd.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #ifdef HAVE_FCNTL
22 #include <fcntl.h>
23 #endif
24
25 #define BT_RECORD_SIZE sizeof(struct bt_record)
26 #define BT_TOKEN_MAX 36
27
28 typedef unsigned long bt_ul;
29
30 struct bt_record { /* single binary tree record */
31 bt_ul lower; /* offset of "lower" token */
32 bt_ul higher; /* offset of "higher" token */
33 unsigned char token[BT_TOKEN_MAX]; /* RATS: ignore - 0-term. token */
34 long data[2]; /* token data (i.e. the counts) */
35 };
36
37 typedef struct bt_record *bt_record_t;
38
39 struct qdbint_s { /* database state */
40 int fd; /* file descriptor of database file */
41 bt_ul size; /* total size of database */
42 #ifdef HAVE_FCNTL
43 int lockcount; /* number of times lock asked for */
44 int locktype; /* type of lock to use (read or write) */
45 #endif
46 int gotheadoffs; /* flag, set once head offset read */
47 bt_ul head_offset; /* offset of head record of tree */
48 };
49
50 static char *bt_lasterror = "";
51
52
53 #ifdef HAVE_FCNTL
54 /*
55 * Obtain / release a read or write lock on the database. Returns nonzero on
56 * error, and blocks until a lock can be obtained.
57 */
dbbt_lock(qdbint_t db,int lock_type)58 static int dbbt_lock(qdbint_t db, int lock_type)
59 {
60 int ret;
61 ret = qdb_int__lock(db->fd, lock_type, &(db->lockcount));
62 if (ret != 0) {
63 bt_lasterror = strerror(errno);
64 return 1;
65 }
66 return 0;
67 }
68 #endif /* HAVE_FCNTL */
69
70
71 /*
72 * Analogue of fread().
73 */
dbbt_chunkread(void * ptr,int size,int nmemb,int fd)74 static int dbbt_chunkread(void *ptr, int size, int nmemb, int fd)
75 {
76 int numread, togo, got;
77
78 for (numread = 0; nmemb > 0; nmemb--, numread++) {
79 for (togo = size; togo > 0;) {
80 got = read(fd, ptr, togo); /* RATS: ignore (OK) */
81 if (got <= 0)
82 return numread;
83 togo -= got;
84 ptr = (void *) (((char *) ptr) + got);
85 }
86 }
87
88 return numread;
89 }
90
91
92 /*
93 * Analogue of fwrite().
94 */
dbbt_chunkwrite(void * ptr,int size,int nmemb,int fd)95 static int dbbt_chunkwrite(void *ptr, int size, int nmemb, int fd)
96 {
97 int numwritten, togo, written;
98
99 for (numwritten = 0; nmemb > 0; nmemb--, numwritten++) {
100 for (togo = size; togo > 0;) {
101 written = write(fd, ptr, togo);
102 if (written <= 0)
103 return numwritten;
104 togo -= written;
105 ptr = (void *) (((char *) ptr) + written);
106 }
107 }
108
109 return numwritten;
110 }
111
112
113 /*
114 * Read a record from the database at the given offset into the given record
115 * structure, returning nonzero on failure.
116 */
dbbt_read_record(qdbint_t db,bt_ul offset,bt_record_t record)117 static int dbbt_read_record(qdbint_t db, bt_ul offset, bt_record_t record)
118 {
119 int got;
120
121 if (lseek(db->fd, offset, SEEK_SET) == (off_t) - 1) {
122 bt_lasterror = strerror(errno);
123 return 1;
124 }
125
126 got = dbbt_chunkread(record, BT_RECORD_SIZE, 1, db->fd);
127 if (got < 1) {
128 bt_lasterror = strerror(errno);
129 return 1;
130 }
131
132 record->token[BT_TOKEN_MAX - 1] = 0;
133
134 return 0;
135 }
136
137
138 /*
139 * Write a record to the database at the given offset, returning nonzero on
140 * failure.
141 */
dbbt_write_record(qdbint_t db,bt_ul offset,bt_record_t record)142 static int dbbt_write_record(qdbint_t db, bt_ul offset, bt_record_t record)
143 {
144 if (lseek(db->fd, offset, SEEK_SET) == (off_t) - 1) {
145 bt_lasterror = strerror(errno);
146 return 1;
147 }
148
149 if (dbbt_chunkwrite(record, BT_RECORD_SIZE, 1, db->fd) < 1) {
150 bt_lasterror = strerror(errno);
151 return 1;
152 }
153
154 return 0;
155 }
156
157
158 /*
159 * Find the given token in the database and fill in the given record
160 * structure if found, also filling in the offset of the record (or 0 if not
161 * found) and the offset of the parent record (0 if none).
162 *
163 * Returns -1 if the token looked for was "lower" than its parent or +1 if
164 * "higher", or 0 if there was no parent record (i.e. this is the first
165 * record).
166 */
dbbt_find_token(qdbint_t db,qdb_datum key,bt_record_t record,bt_ul * offset,bt_ul * parent)167 static int dbbt_find_token(qdbint_t db, qdb_datum key, bt_record_t record,
168 bt_ul * offset, bt_ul * parent)
169 {
170 int hilow = 0;
171 int x;
172
173 *offset = 0;
174 *parent = 0;
175
176 if (db == NULL)
177 return 0;
178
179 if (db->size < 2 * sizeof(long))
180 return 0;
181
182 if (!db->gotheadoffs) {
183 lseek(db->fd, 0, SEEK_SET);
184 dbbt_chunkread(offset, sizeof(*offset), 1, db->fd);
185 db->head_offset = *offset;
186 db->gotheadoffs = 1;
187 } else {
188 *offset = db->head_offset;
189 }
190
191 while (*offset > 0) {
192 int len;
193
194 if (dbbt_read_record(db, *offset, record)) {
195 *offset = 0;
196 break;
197 }
198
199 x = strncmp((char *) (record->token), (char *) (key.data),
200 key.size);
201 len = strlen((char *) (record->token));
202 if (len < key.size) {
203 x = -1;
204 } else if (len > key.size) {
205 x = 1;
206 }
207
208 if (x == 0) {
209 return hilow;
210 } else if (x < 0) {
211 *parent = *offset;
212 hilow = -1;
213 *offset = record->lower;
214 } else {
215 *parent = *offset;
216 hilow = 1;
217 *offset = record->higher;
218 }
219 }
220
221 return hilow;
222 }
223
224
225 /*
226 * Return nonzero if the given file is of this database type.
227 */
qdb_obtree_identify(const char * file)228 int qdb_obtree_identify(const char *file)
229 {
230 if (file == NULL)
231 return 0;
232 if (strncasecmp(file, "obtree:", 7) == 0)
233 return 1;
234 return 0;
235 }
236
237
238 /*
239 * Open the given database in the given way (new database, read-only, or
240 * read-write); return a qdbint_t or NULL on error.
241 */
qdb_obtree_open(const char * file,qdb_open_t method)242 qdbint_t qdb_obtree_open(const char *file, qdb_open_t method)
243 {
244 qdbint_t db;
245 int fd = -1;
246 #ifdef HAVE_FCNTL
247 int locktype = F_RDLCK;
248 #endif
249 int forced_type = 0;
250
251 if (strncasecmp(file, "obtree:", 7) == 0) {
252 file += 7;
253 forced_type = 1;
254 }
255
256 switch (method) {
257 case QDB_NEW:
258 fd = open(file, /* RATS: ignore (no race) */
259 O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
260 if (fd < 0)
261 bt_lasterror = strerror(errno);
262 #ifdef HAVE_FCNTL
263 locktype = F_WRLCK;
264 #endif
265 break;
266 case QDB_READONLY:
267 fd = open(file, /* RATS: ignore (no race) */
268 O_RDONLY);
269 if (fd < 0)
270 bt_lasterror = strerror(errno);
271 break;
272 case QDB_READWRITE:
273 fd = open(file, /* RATS: ignore (no race) */
274 O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
275 if (fd < 0)
276 bt_lasterror = strerror(errno);
277 #ifdef HAVE_FCNTL
278 locktype = F_WRLCK;
279 #endif
280 break;
281 default:
282 break;
283 }
284
285 if (fd < 0)
286 return NULL;
287
288 db = calloc(1, sizeof(*db));
289 if (db == NULL) {
290 bt_lasterror = strerror(errno);
291 close(fd);
292 return NULL;
293 }
294
295 db->size = lseek(fd, 0, SEEK_END);
296 db->fd = fd;
297
298 #ifdef HAVE_FCNTL
299 db->locktype = locktype;
300
301 if (dbbt_lock(db, locktype)) {
302 close(fd);
303 free(db);
304 return NULL;
305 }
306 #endif
307
308 /*
309 * Complain at the user to stop using this backend if it wasn't
310 * specifically chosen.
311 */
312 if (!forced_type) {
313 log_add(0, "%s",
314 _("WARNING: Using deprecated obtree backend!"));
315 log_add(0, "%s",
316 _("WARNING: Dump, delete, and restore your"));
317 log_add(0, "%s",
318 _("WARNING: databases to upgrade them to the"));
319 log_add(0, "%s",
320 _("WARNING: new format and stop this warning."));
321 } else {
322 log_add(1, "%s",
323 _("warning: obtree backend is deprecated"));
324 }
325
326 /*
327 * If the database has zero size and we're writing to it, assume
328 * it's new and don't complain.
329 */
330 if ((db->size == 0) && (method != QDB_READONLY))
331 return db;
332
333 /*
334 * We now do some simple checks to make sure that the file is a
335 * database of the format we're expecting.
336 */
337
338 /*
339 * If it's shorter than 2 long ints, it's not a valid database.
340 */
341 if (db->size < 2 * sizeof(long)) {
342 bt_lasterror = _("invalid database (too small)");
343 close(fd);
344 free(db);
345 return NULL;
346 }
347
348 /*
349 * If its size, discounting the two longs at the start, isn't a
350 * multiple of our record size, it's not a valid database.
351 */
352 if (((db->size - 2 * sizeof(long)) % BT_RECORD_SIZE) != 0) {
353 bt_lasterror = _("invalid database (irregular size)");
354 close(fd);
355 free(db);
356 return NULL;
357 }
358
359 /*
360 * If the first long int (the "head offset") is larger than the size
361 * of the file, this isn't a valid database.
362 */
363 {
364 bt_ul offset = db->size + 1;
365 lseek(fd, 0, SEEK_SET);
366 dbbt_chunkread(&offset, sizeof(offset), 1, fd);
367 if (offset > db->size) {
368 bt_lasterror =
369 _("invalid database (bad head offset)");
370 close(fd);
371 free(db);
372 return NULL;
373 }
374 lseek(fd, 0, SEEK_SET);
375 }
376
377 return db;
378 }
379
380
381 /*
382 * Close the given database.
383 */
qdb_obtree_close(qdbint_t db)384 void qdb_obtree_close(qdbint_t db)
385 {
386 if (db == NULL)
387 return;
388
389 #ifdef HAVE_FCNTL
390 while (db->lockcount > 0)
391 dbbt_lock(db, F_UNLCK);
392 #endif
393
394 close(db->fd);
395
396 free(db);
397 }
398
399
400 /*
401 * Fetch a value from the database. The datum returned needs its val.data
402 * free()ing after use. If val.data is NULL, no value was found for the
403 * given key.
404 */
qdb_obtree_fetch(qdbint_t db,qdb_datum key)405 qdb_datum qdb_obtree_fetch(qdbint_t db, qdb_datum key)
406 {
407 struct bt_record record;
408 unsigned long offset, parent;
409 qdb_datum val;
410
411 val.data = NULL;
412 val.size = 0;
413
414 if (db == NULL)
415 return val;
416
417 if (key.size >= BT_TOKEN_MAX)
418 key.size = BT_TOKEN_MAX - 1;
419
420 dbbt_find_token(db, key, &record, &offset, &parent);
421
422 if (offset == 0)
423 return val;
424
425 val.size = 2 * sizeof(long);
426 val.data = calloc(1, val.size);
427 if (val.data == NULL) {
428 val.size = 0;
429 return val;
430 }
431
432 ((long *) val.data)[0] = record.data[0];
433 ((long *) val.data)[1] = record.data[1];
434
435 return val;
436 }
437
438
439 /*
440 * Return a file descriptor for the given database, or -1 on error.
441 */
qdb_obtree_fd(qdbint_t db)442 int qdb_obtree_fd(qdbint_t db)
443 {
444 if (db == NULL)
445 return -1;
446 return db->fd;
447 }
448
449
450 /*
451 * Store the given key with the given value into the database, replacing any
452 * existing value for that key. Returns nonzero on error.
453 */
qdb_obtree_store(qdbint_t db,qdb_datum key,qdb_datum val)454 int qdb_obtree_store(qdbint_t db, qdb_datum key, qdb_datum val)
455 {
456 struct bt_record record, head;
457 unsigned long offset, parent, nextfree;
458 int x;
459
460 if (db == NULL)
461 return 1;
462
463 memset(&head, 0, BT_RECORD_SIZE);
464 memset(&record, 0, BT_RECORD_SIZE);
465
466 if (key.size >= BT_TOKEN_MAX)
467 key.size = BT_TOKEN_MAX - 1;
468
469 x = dbbt_find_token(db, key, &record, &offset, &parent);
470
471 memcpy(record.token, key.data, key.size);
472 record.token[key.size] = 0;
473 record.data[0] = ((long *) val.data)[0];
474 record.data[1] = ((long *) val.data)[1];
475
476 qdb_int__sig_block();
477
478 /*
479 * Record exists - overwrite it.
480 */
481 if (offset > 0) {
482 if (dbbt_write_record(db, offset, &record)) {
483 qdb_int__sig_unblock();
484 return 1;
485 }
486 qdb_int__sig_unblock();
487 return 0;
488 }
489
490 record.lower = 0;
491 record.higher = 0;
492
493 /*
494 * Database has just been created, so fill in the header and write
495 * this record as the first one.
496 */
497 if (db->size <= sizeof(offset)) {
498
499 if (lseek(db->fd, 0, SEEK_SET) == (off_t) - 1) {
500 bt_lasterror = strerror(errno);
501 qdb_int__sig_unblock();
502 return 1;
503 }
504 offset = 2 * sizeof(offset);
505 if (dbbt_chunkwrite(&offset, sizeof(offset), 1, db->fd) <
506 1) {
507 bt_lasterror = strerror(errno);
508 qdb_int__sig_unblock();
509 return 1;
510 }
511
512 db->gotheadoffs = 0;
513
514 offset = 0;
515 if (dbbt_chunkwrite(&offset, sizeof(offset), 1, db->fd) <
516 1) {
517 bt_lasterror = strerror(errno);
518 qdb_int__sig_unblock();
519 return 1;
520 }
521
522 head.lower = (2 * sizeof(offset)) + BT_RECORD_SIZE;
523 if (dbbt_chunkwrite(&head, BT_RECORD_SIZE, 1, db->fd) < 1) {
524 bt_lasterror = strerror(errno);
525 qdb_int__sig_unblock();
526 return 1;
527 }
528
529 if (dbbt_chunkwrite(&record, BT_RECORD_SIZE, 1, db->fd) <
530 1) {
531 bt_lasterror = strerror(errno);
532 qdb_int__sig_unblock();
533 return 1;
534 }
535
536 db->size = lseek(db->fd, 0, SEEK_CUR);
537 qdb_int__sig_unblock();
538 return 0;
539 }
540
541 /*
542 * Get offset of next free space block.
543 */
544 if (lseek(db->fd, sizeof(offset), SEEK_SET) == (off_t) - 1) {
545 bt_lasterror = strerror(errno);
546 qdb_int__sig_unblock();
547 return 1;
548 }
549 if (dbbt_chunkread(&offset, sizeof(offset), 1, db->fd) < 1) {
550 bt_lasterror = strerror(errno);
551 qdb_int__sig_unblock();
552 return 1;
553 }
554
555 offset &= 0x7FFFFFFF;
556
557 /*
558 * If offset is 0 or we can't read from that offset, it's a new
559 * block at the end of the file, otherwise we take the next free
560 * offset from there and store it in the core free pointer, and then
561 * use that free offset.
562 */
563 if (lseek(db->fd, offset, SEEK_SET) == (off_t) - 1) {
564 bt_lasterror = strerror(errno);
565 qdb_int__sig_unblock();
566 return 1;
567 }
568 if ((offset == 0)
569 || (dbbt_chunkread(&nextfree, sizeof(nextfree), 1, db->fd) < 1)
570 ) {
571 offset = lseek(db->fd, 0, SEEK_END);
572 nextfree = 0x80000000;
573 }
574 if (lseek(db->fd, sizeof(offset), SEEK_SET) == (off_t) - 1) {
575 bt_lasterror = strerror(errno);
576 qdb_int__sig_unblock();
577 return 1;
578 }
579 if (dbbt_chunkwrite(&nextfree, sizeof(nextfree), 1, db->fd) < 0) {
580 bt_lasterror = strerror(errno);
581 qdb_int__sig_unblock();
582 return 1;
583 }
584
585 if (dbbt_write_record(db, offset, &record)) {
586 qdb_int__sig_unblock();
587 return 1;
588 }
589
590 /*
591 * Now attach the new record to its parent, if applicable.
592 */
593 if (parent > 0) {
594 if (dbbt_read_record(db, parent, &record)) {
595 qdb_int__sig_unblock();
596 return 1;
597 }
598 if (x < 0) {
599 record.lower = offset;
600 } else {
601 record.higher = offset;
602 }
603 if (dbbt_write_record(db, parent, &record)) {
604 qdb_int__sig_unblock();
605 return 1;
606 }
607 }
608
609 qdb_int__sig_unblock();
610
611 return 0;
612 }
613
614
615 /*
616 * Return the "first" key in the database, suitable for using with repeated
617 * calls to qdb_nextkey() to walk through every key in the database.
618 */
qdb_obtree_firstkey(qdbint_t db)619 qdb_datum qdb_obtree_firstkey(qdbint_t db)
620 {
621 struct bt_record record;
622 qdb_datum key;
623
624 key.data = NULL;
625 key.size = 0;
626
627 if (lseek(db->fd, 2 * sizeof(unsigned long), SEEK_SET) ==
628 (off_t) - 1)
629 return key;
630
631 if (dbbt_chunkread(&record, BT_RECORD_SIZE, 1, db->fd) < 1) {
632 return key;
633 }
634
635 record.token[BT_TOKEN_MAX - 1] = 0;
636
637 key.data = (unsigned char *) strdup((char *) (record.token));
638 key.size = strlen((char *) (record.token));
639
640 return key;
641 }
642
643
644 /*
645 * Return the "next" key in the database, or key.data=NULL when all keys
646 * have been returned.
647 */
qdb_obtree_nextkey(qdbint_t db,qdb_datum key)648 qdb_datum qdb_obtree_nextkey(qdbint_t db, qdb_datum key)
649 {
650 struct bt_record record;
651 unsigned long offset, parent;
652 qdb_datum newkey;
653
654 newkey.data = NULL;
655 newkey.size = 0;
656
657 if (key.data == NULL) {
658 return newkey;
659 }
660
661 dbbt_find_token(db, key, &record, &offset, &parent);
662
663 if (offset < 1) {
664 return newkey;
665 }
666
667 if (lseek(db->fd, offset + BT_RECORD_SIZE, SEEK_SET) ==
668 (off_t) - 1) {
669 return newkey;
670 }
671
672 do {
673 if (dbbt_chunkread(&record, BT_RECORD_SIZE, 1, db->fd) < 1) {
674 return newkey;
675 }
676 } while (record.lower & 0x80000000);
677
678 record.token[BT_TOKEN_MAX - 1] = 0;
679
680 newkey.data = (unsigned char *) strdup((char *) (record.token));
681 newkey.size = strlen((char *) (record.token));
682
683 return newkey;
684 }
685
686
687 /*
688 * Reposition the given record in the binary tree, by finding an existing
689 * record to link it to. Returns nonzero on error.
690 */
qdb_obtree_delete__reposition(qdbint_t db,unsigned long offset,char * token)691 static int qdb_obtree_delete__reposition(qdbint_t db, unsigned long offset,
692 char *token)
693 {
694 struct bt_record record;
695 unsigned long offs, parent;
696 qdb_datum key;
697 int x;
698
699 key.data = (unsigned char *) token;
700 key.size = strlen(token);
701
702 x = dbbt_find_token(db, key, &record, &offs, &parent);
703
704 if (parent < 1)
705 return 1;
706
707 if (dbbt_read_record(db, parent, &record))
708 return 1;
709
710 if (x < 0) {
711 record.lower = offset;
712 } else {
713 record.higher = offset;
714 }
715
716 if (dbbt_write_record(db, parent, &record))
717 return 1;
718
719 return 0;
720 }
721
722
723 /*
724 * Delete the given key from the database. Returns nonzero on error.
725 */
qdb_obtree_delete(qdbint_t db,qdb_datum key)726 int qdb_obtree_delete(qdbint_t db, qdb_datum key)
727 {
728 struct bt_record record, recparent, reclower, rechigher;
729 unsigned long offset, parent, nextfree;
730
731 int x;
732
733 if (db == NULL)
734 return 1;
735
736 if (key.size < 1)
737 return 1;
738
739 x = dbbt_find_token(db, key, &record, &offset, &parent);
740 if (offset < 1)
741 return 1;
742
743 /*
744 * Get a copy of the lower and higher records, if any.
745 */
746 if (record.lower > 0) {
747 if (dbbt_read_record(db, record.lower, &reclower))
748 return 1;
749 }
750
751 if (record.higher > 0) {
752 if (dbbt_read_record(db, record.higher, &rechigher))
753 return 1;
754 }
755
756 qdb_int__sig_block();
757
758 /*
759 * Remove the link to this record from its parent.
760 */
761 if (parent > 0) {
762 if (dbbt_read_record(db, parent, &recparent)) {
763 qdb_int__sig_unblock();
764 return 1;
765 }
766 if (x < 0) {
767 recparent.lower = 0;
768 } else {
769 recparent.higher = 0;
770 }
771 if (dbbt_write_record(db, parent, &recparent)) {
772 qdb_int__sig_unblock();
773 return 1;
774 }
775 }
776
777
778 /*
779 * Re-attach any children of this record to the database.
780 */
781 if (record.lower > 0)
782 qdb_obtree_delete__reposition(db, record.lower,
783 (char *) (reclower.token));
784 if (record.higher > 0)
785 qdb_obtree_delete__reposition(db, record.higher,
786 (char *) (rechigher.token));
787
788 /*
789 * Now we add this record's offset to the head of the "free records"
790 * linked list.
791 */
792 if (lseek(db->fd, sizeof(offset), SEEK_SET) == (off_t) - 1) {
793 bt_lasterror = strerror(errno);
794 qdb_int__sig_unblock();
795 return 1;
796 }
797
798 nextfree = 0x80000000;
799 if (dbbt_chunkread(&nextfree, sizeof(nextfree), 1, db->fd) < 0) {
800 bt_lasterror = strerror(errno);
801 qdb_int__sig_unblock();
802 return 1;
803 }
804
805 if (lseek(db->fd, sizeof(offset), SEEK_SET) == (off_t) - 1) {
806 bt_lasterror = strerror(errno);
807 qdb_int__sig_unblock();
808 return 1;
809 }
810
811 offset |= 0x80000000;
812 if (dbbt_chunkwrite(&offset, sizeof(offset), 1, db->fd) < 0) {
813 bt_lasterror = strerror(errno);
814 qdb_int__sig_unblock();
815 return 1;
816 }
817
818 offset &= 0x7FFFFFFF;
819
820 if (lseek(db->fd, offset, SEEK_SET) == (off_t) - 1) {
821 bt_lasterror = strerror(errno);
822 qdb_int__sig_unblock();
823 return 1;
824 }
825
826 if (dbbt_chunkwrite(&nextfree, sizeof(nextfree), 1, db->fd) < 1) {
827 bt_lasterror = strerror(errno);
828 qdb_int__sig_unblock();
829 return 1;
830 }
831
832 qdb_int__sig_unblock();
833
834 return 0;
835 }
836
837
838 /*
839 * Temporarily release the lock on the database.
840 */
qdb_obtree_unlock(qdbint_t db)841 void qdb_obtree_unlock(qdbint_t db)
842 {
843 #ifdef HAVE_FCNTL
844 dbbt_lock(db, F_UNLCK);
845 #endif
846 }
847
848
849 /*
850 * Reassert the lock on the database.
851 */
qdb_obtree_relock(qdbint_t db)852 void qdb_obtree_relock(qdbint_t db)
853 {
854 #ifdef HAVE_FCNTL
855 dbbt_lock(db, db->locktype);
856 #endif
857 db->gotheadoffs = 0;
858 }
859
860
861 /*
862 * Return a string describing the last database error to occur.
863 */
qdb_obtree_error(void)864 char *qdb_obtree_error(void)
865 {
866 return bt_lasterror;
867 }
868
869 #endif /* USING_OBTREE */
870
871 /* EOF */
872