16b300180b17e0ae6cb30f9fc0cd3cc42b6b49d2
[exim.git] / src / src / dbfn.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10
11 #include "exim.h"
12
13 /* We have buffers holding path names for database files.
14 PATH_MAX could be used here, but would be wasting memory, as we deal
15 with database files like $spooldirectory/db/<name> */
16 #define PATHLEN 256
17
18
19 /* Functions for accessing Exim's hints database, which consists of a number of
20 different DBM files. This module does not contain code for reading DBM files
21 for (e.g.) alias expansion. That is all contained within the general search
22 functions. As Exim now has support for several DBM interfaces, all the relevant
23 functions are called as inlinable functions from an included file.
24
25 All the data in Exim's database is in the nature of *hints*. Therefore it
26 doesn't matter if it gets destroyed by accident. These functions are not
27 supposed to implement a "safe" database.
28
29 Keys are passed in as C strings, and the terminating zero *is* used when
30 building the dbm files. This just makes life easier when scanning the files
31 sequentially.
32
33 Synchronization is required on the database files, and this is achieved by
34 means of locking on independent lock files. (Earlier attempts to lock on the
35 DBM files themselves were never completely successful.) Since callers may in
36 general want to do more than one read or write while holding the lock, there
37 are separate open and close functions. However, the calling modules should
38 arrange to hold the locks for the bare minimum of time.
39
40 API:
41   exim_lockfile_needed                  facilities predicate
42   dbfn_open_path                        full pathname; no lock taken, readonly
43   dbfn_open                             takes lockfile or opens transaction
44   dbfn_open_multi                       only if transactions supported;
45                                         no lock or transaction taken
46   dbfn_close                            release lockfile or transaction
47   dbfn_close_multi
48   dbfn_transaction_start                only if transactions supported
49   dbfn_transaction_commit
50   dbfn_read_klen                        explicit key length; embedded NUL ok
51   dbfn_read_with_length
52   dbfn_read_enforce_length
53   dbfn_write
54   dbfn_delete
55   dbfn_scan                             unused; ifdeffout out
56
57 Users:
58   ACL ratelimit & seen conditions
59   delivery retry handling
60   delivery serialization
61   TLS session resumption
62   peer capability cache
63   callout & quota cache
64   DBM lookup type
65 */
66
67
68
69 /*************************************************
70 *          Open and lock a database file         *
71 *************************************************/
72
73 /* Used by DBM lookups:
74 full pathname for DB file rather than hintsdb name, readonly, no locking. */
75
76 open_db *
77 dbfn_open_path(const uschar * path, open_db * dbblock)
78 {
79 uschar * dirname = string_copy(path);
80
81 dbblock->readonly = TRUE;
82 dbblock->lockfd = -1;
83 dbblock->dbptr = !exim_lockfile_needed()
84   ? exim_dbopen_multi(path, dirname, O_RDONLY, 0)
85   : exim_dbopen(path, dirname, O_RDONLY, 0);
86 return dbblock->dbptr ? dbblock : NULL;;
87 }
88
89 /* Ensure the directory for the DB is present */
90
91 static inline void
92 db_dir_make(BOOL panic)
93 {
94 (void) directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, panic);
95 }
96
97
98 /* Lock a file to protect the DB.  Return TRUE for success */
99
100 static inline BOOL
101 lockfile_take(open_db * dbblock, const uschar * filename, BOOL rdonly, BOOL panic)
102 {
103 flock_t lock_data;
104 int rc, * fdp = &dbblock->lockfd;
105
106 priv_drop_temp(exim_uid, exim_gid);
107 if ((*fdp = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
108   {
109   db_dir_make(panic);
110   *fdp = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
111   }
112 priv_restore();
113
114 if (*fdp < 0)
115   {
116   log_write(0, LOG_MAIN, "%s",
117     string_open_failed("database lock file %s", filename));
118   errno = 0;      /* Indicates locking failure */
119   return FALSE;
120   }
121
122 /* Now we must get a lock on the opened lock file; do this with a blocking
123 lock that times out. */
124
125 lock_data.l_type = rdonly ? F_RDLCK : F_WRLCK;
126 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
127
128 DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
129   debug_printf_indent("locking %s\n", filename);
130
131 sigalrm_seen = FALSE;
132 ALARM(EXIMDB_LOCK_TIMEOUT);
133 rc = fcntl(*fdp, F_SETLKW, &lock_data);
134 ALARM_CLR(0);
135
136 if (sigalrm_seen) errno = ETIMEDOUT;
137 if (rc < 0)
138   {
139   log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
140     rdonly ? "read" : "write", filename,
141     errno == ETIMEDOUT ? "timed out" : strerror(errno));
142   (void)close(*fdp); *fdp = -1;
143   errno = 0;       /* Indicates locking failure */
144   return FALSE;
145   }
146
147 DEBUG(D_hints_lookup) debug_printf_indent("locked  %s\n", filename);
148 return TRUE;
149 }
150
151 /* Used for accessing Exim's hints databases.
152
153 Arguments:
154   name     The single-component name of one of Exim's database files.
155   flags    Either O_RDONLY or O_RDWR, indicating the type of open required;
156              optionally O_CREAT
157   dbblock  Points to an open_db block to be filled in.
158   lof      If TRUE, write to the log for actual open failures (locking failures
159            are always logged).
160   panic    If TRUE, panic on failure to create the db directory
161
162 Returns:   NULL if the open failed, or the locking failed. After locking
163            failures, errno is zero.
164
165            On success, dbblock is returned. This contains the dbm pointer and
166            the fd of the locked lock file.
167 */
168
169 open_db *
170 dbfn_open(const uschar * name, int flags, open_db * dbblock,
171   BOOL lof, BOOL panic)
172 {
173 int rc, save_errno, dlen, flen;
174 flock_t lock_data;
175 uschar dirname[PATHLEN], filename[PATHLEN];
176
177 DEBUG(D_hints_lookup) acl_level++;
178
179 /* The first thing to do is to open a separate file on which to lock. This
180 ensures that Exim has exclusive use of the database before it even tries to
181 open it. Early versions tried to lock on the open database itself, but that
182 gave rise to mysterious problems from time to time - it was suspected that some
183 DB libraries "do things" on their open() calls which break the interlocking.
184 The lock file is never written to, but we open it for writing so we can get a
185 write lock if required. If it does not exist, we create it. This is done
186 separately so we know when we have done it, because when running as root we
187 need to change the ownership - see the bottom of this function. We also try to
188 make the directory as well, just in case. We won't be doing this many times
189 unnecessarily, because usually the lock file will be there. If the directory
190 exists, there is no error. */
191
192 dlen = snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
193
194 dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY;
195 dbblock->lockfd = -1;
196 if (!exim_lockfile_needed())
197   db_dir_make(panic);
198 else
199   {
200   flen = Ustrlen(name);
201   snprintf(CS filename, sizeof(filename), "%.*s/%.*s.lockfile",
202             (int)sizeof(filename) - dlen - flen - 11, dirname,
203             flen, name);
204   if (!lockfile_take(dbblock, filename, flags == O_RDONLY, panic))
205     {
206     DEBUG(D_hints_lookup) acl_level--;
207     return NULL;
208     }
209   }
210
211 /* At this point we have an opened and locked separate lock file, that is,
212 exclusive access to the database, so we can go ahead and open it. If we are
213 expected to create it, don't do so at first, again so that we can detect
214 whether we need to change its ownership (see comments about the lock file
215 above.) There have been regular reports of crashes while opening hints
216 databases - often this is caused by non-matching db.h and the library. To make
217 it easy to pin this down, there are now debug statements on either side of the
218 open call. */
219
220 snprintf(CS filename, sizeof(filename), "%.*s/%s", dlen, dirname, name);
221
222 priv_drop_temp(exim_uid, exim_gid);
223 dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed()
224   ? exim_dbopen_multi(filename, dirname, flags & O_ACCMODE, EXIMDB_MODE)
225   : exim_dbopen(filename, dirname, flags & O_ACCMODE, EXIMDB_MODE);
226
227 if (!dbblock->dbptr && errno == ENOENT && flags & O_CREAT)
228   {
229   DEBUG(D_hints_lookup)
230     debug_printf_indent("%s appears not to exist: trying to create\n", filename);
231   dbblock->dbptr = exim_dbopen(filename, dirname, flags, EXIMDB_MODE);
232   }
233 save_errno = errno;
234 priv_restore();
235
236 /* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
237 log the event - also for debugging - but debug only if the file just doesn't
238 exist. */
239
240 if (!dbblock->dbptr)
241   {
242   errno = save_errno;
243   if (lof && save_errno != ENOENT)
244     log_write(0, LOG_MAIN, "%s", string_open_failed("DB file %s",
245         filename));
246   else
247     DEBUG(D_hints_lookup)
248       debug_printf_indent("%s\n", CS string_open_failed("DB file %s",
249           filename));
250   (void)close(dbblock->lockfd);
251   dbblock->lockfd = -1;
252   dbblock = NULL;
253   }
254
255 /* Pass back the block containing the opened database handle and the open fd
256 for the lock. */
257
258 DEBUG(D_hints_lookup) acl_level--;
259 return dbblock;
260 }
261
262
263
264 /* Only for transaction-capable DB types.  Open without locking or
265 starting a transaction.  "lof" and "panic" always true; read/write mode.
266 */
267
268 open_db *
269 dbfn_open_multi(const uschar * name, int flags, open_db * dbblock)
270 {
271 int rc, save_errno, dlen;
272 flock_t lock_data;
273 uschar dirname[PATHLEN], filename[PATHLEN];
274
275 DEBUG(D_hints_lookup) acl_level++;
276
277 dbblock->lockfd = -1;
278 dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY;
279 db_dir_make(TRUE);
280
281 dlen = snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
282 snprintf(CS filename, sizeof(filename), "%.*s/%s", dlen, dirname, name);
283
284 priv_drop_temp(exim_uid, exim_gid);
285 dbblock->dbptr = exim_dbopen_multi(filename, dirname, flags & O_ACCMODE, EXIMDB_MODE);
286 if (!dbblock->dbptr && errno == ENOENT && flags & O_CREAT)
287   {
288   DEBUG(D_hints_lookup)
289     debug_printf_indent("%s appears not to exist: trying to create\n", filename);
290   dbblock->dbptr = exim_dbopen_multi(filename, dirname, flags, EXIMDB_MODE);
291   }
292 save_errno = errno;
293 priv_restore();
294
295 /* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
296 log the event - also for debugging - but debug only if the file just doesn't
297 exist. */
298
299 if (!dbblock->dbptr)
300   {
301   errno = save_errno;
302   if (save_errno != ENOENT)
303     log_write(0, LOG_MAIN, "%s", string_open_failed("DB file %s",
304         filename));
305   else
306     DEBUG(D_hints_lookup)
307       debug_printf_indent("%s\n", CS string_open_failed("DB file %s",
308           filename));
309   dbblock =  NULL;
310   }
311
312 /* Pass back the block containing the opened database handle */
313 DEBUG(D_hints_lookup) acl_level--;
314 return dbblock;
315 }
316
317
318 /* Return: boolean success */
319 BOOL
320 dbfn_transaction_start(open_db * dbp)
321 {
322 DEBUG(D_hints_lookup) debug_printf_indent("dbfn_transaction_start\n");
323 if (!dbp->readonly) return exim_dbtransaction_start(dbp->dbptr);
324 return FALSE;
325 }
326 void
327 dbfn_transaction_commit(open_db * dbp)
328 {
329 DEBUG(D_hints_lookup) debug_printf_indent("dbfn_transaction_commit\n");
330 if (!dbp->readonly) exim_dbtransaction_commit(dbp->dbptr);
331 }
332
333
334
335 /*************************************************
336 *         Unlock and close a database file       *
337 *************************************************/
338
339 /* Closing a file automatically unlocks it, so after closing the database, just
340 close the lock file if there was one.
341
342 Argument: a pointer to an open database block
343 Returns:  nothing
344 */
345
346 void
347 dbfn_close(open_db * dbp)
348 {
349 int * fdp = &dbp->lockfd;
350
351 if (dbp->readonly && !exim_lockfile_needed())
352   exim_dbclose_multi(dbp->dbptr);
353 else
354   exim_dbclose(dbp->dbptr);
355
356 if (*fdp >= 0) (void)close(*fdp);
357 DEBUG(D_hints_lookup)
358   debug_printf_indent("closed hints database%s\n",
359                       *fdp < 0 ? "" : " and lockfile");
360 *fdp = -1;
361 }
362
363
364 void
365 dbfn_close_multi(open_db * dbp)
366 {
367 exim_dbclose_multi(dbp->dbptr);
368 DEBUG(D_hints_lookup)
369   debug_printf_indent("closed hints database\n");
370 }
371
372
373
374
375 /*************************************************
376 *             Read from database file            *
377 *************************************************/
378
379 /* Passing back the pointer unchanged is useless, because there is
380 no guarantee of alignment. Since all the records used by Exim need
381 to be properly aligned to pick out the timestamps, etc., we might as
382 well do the copying centrally here.
383
384 Most calls don't need the length, so there is a macro called dbfn_read which
385 has two arguments; it calls this function adding NULL as the third.
386
387 Arguments:
388   dbblock   a pointer to an open database block
389   key       the key of the record to be read
390   klen      length of key including a terminating NUL (if present)
391   length    a pointer to an int into which to return the length, if not NULL
392
393 Returns: a pointer to the retrieved record, or
394          NULL if the record is not found
395 */
396
397 void *
398 dbfn_read_klen(open_db * dbblock, const uschar * key, int klen, int * length)
399 {
400 void * yield;
401 EXIM_DATUM key_datum, result_datum;
402 uschar * key_copy = store_get(klen, key);
403 unsigned dlen;
404
405 memcpy(key_copy, key, klen);
406
407 DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%.*s\n", klen, key);
408
409 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
410 exim_datum_init(&result_datum);      /* to be cleared before use. */
411 exim_datum_data_set(&key_datum, key_copy);
412 exim_datum_size_set(&key_datum, klen);
413
414 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum))
415   {
416   DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: null return\n");
417   return NULL;
418   }
419
420 /* Assume the data store could have been tainted.  Properly, we should
421 store the taint status with the data. */
422
423 dlen = exim_datum_size_get(&result_datum);
424 yield = store_get(dlen, GET_TAINTED);
425 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
426 DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen);
427 if (length) *length = dlen;
428
429 exim_datum_free(&result_datum);    /* Some DBM libs require freeing */
430 return yield;
431 }
432
433
434 /*
435 Arguments:
436   dbblock   a pointer to an open database block
437   key       the key of the record to be read (NUL-terminated)
438   lenp      a pointer to an int into which to return the data length,
439             if not NULL
440
441 Returns: a pointer to the retrieved record, or
442          NULL if the record is not found
443 */
444
445 void *
446 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * lenp)
447 {
448 return dbfn_read_klen(dbblock, key, Ustrlen(key)+1, lenp);
449 }
450
451
452
453 /* Read a record.  If the length is not as expected then delete it, write
454 an error log line, delete the record and return NULL.
455 Use this for fixed-size records (so not retry or wait records).
456
457 Arguments:
458   dbblock   a pointer to an open database block
459   key       the key of the record to be read
460   length    the expected record length
461
462 Returns: a pointer to the retrieved record, or
463          NULL if the record is not found/bad
464 */
465
466 void *
467 dbfn_read_enforce_length(open_db * dbblock, const uschar * key, size_t length)
468 {
469 int rlen;
470 void * yield = dbfn_read_with_length(dbblock, key, &rlen);
471
472 if (yield)
473   {
474   if (rlen == length) return yield;
475   log_write(0, LOG_MAIN|LOG_PANIC, "Bad db record size for '%s'", key);
476   dbfn_delete(dbblock, key);
477   }
478 return NULL;
479 }
480
481 /*************************************************
482 *             Write to database file             *
483 *************************************************/
484
485 /*
486 Arguments:
487   dbblock   a pointer to an open database block
488   key       the key of the record to be written
489   ptr       a pointer to the record to be written
490   length    the length of the record to be written
491
492 Returns:    the yield of the underlying dbm or db "write" function. If this
493             is dbm, the value is zero for OK.
494 */
495
496 int
497 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
498 {
499 EXIM_DATUM key_datum, value_datum;
500 dbdata_generic *gptr = (dbdata_generic *)ptr;
501 int klen = Ustrlen(key) + 1;
502 uschar * key_copy = store_get(klen, key);
503
504 memcpy(key_copy, key, klen);
505 gptr->time_stamp = time(NULL);
506
507 DEBUG(D_hints_lookup)
508   debug_printf_indent("dbfn_write: key=%s datalen %d\n", key, length);
509
510 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
511 exim_datum_init(&value_datum);       /* to be cleared before use. */
512 exim_datum_data_set(&key_datum, key_copy);
513 exim_datum_size_set(&key_datum, klen);
514 exim_datum_data_set(&value_datum, ptr);
515 exim_datum_size_set(&value_datum, length);
516 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
517 }
518
519
520
521 /*************************************************
522 *           Delete record from database file     *
523 *************************************************/
524
525 /*
526 Arguments:
527   dbblock    a pointer to an open database block
528   key        the key of the record to be deleted
529
530 Returns: the yield of the underlying dbm or db "delete" function.
531 */
532
533 int
534 dbfn_delete(open_db *dbblock, const uschar *key)
535 {
536 int klen = Ustrlen(key) + 1;
537 uschar * key_copy = store_get(klen, key);
538 EXIM_DATUM key_datum;
539
540 DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
541
542 memcpy(key_copy, key, klen);
543 exim_datum_init(&key_datum);         /* Some DBM libraries require clearing */
544 exim_datum_data_set(&key_datum, key_copy);
545 exim_datum_size_set(&key_datum, klen);
546 return exim_dbdel(dbblock->dbptr, &key_datum);
547 }
548
549
550
551 #ifdef notdef
552 /* XXX This appears to be unused.  There's a separate implementation
553 in dbutils.c for dumpdb and fixdb, using the same underlying support.
554 */
555
556 /*************************************************
557 *         Scan the keys of a database file       *
558 *************************************************/
559
560 /*
561 Arguments:
562   dbblock  a pointer to an open database block
563   start    TRUE if starting a new scan
564            FALSE if continuing with the current scan
565   cursor   a pointer to a pointer to a cursor anchor, for those dbm libraries
566            that use the notion of a cursor
567
568 Returns:   the next record from the file, or
569            NULL if there are no more
570 */
571
572 uschar *
573 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
574 {
575 EXIM_DATUM key_datum, value_datum;
576 uschar *yield;
577
578 DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");
579
580 /* Some dbm require an initialization */
581
582 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
583
584 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
585 exim_datum_init(&value_datum);       /* to be cleared before use. */
586
587 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
588   ? US exim_datum_data_get(&key_datum) : NULL;
589
590 /* Some dbm require a termination */
591
592 if (!yield) exim_dbdelete_cursor(*cursor);
593 return yield;
594 }
595 #endif
596
597
598
599 /*************************************************
600 **************************************************
601 *             Stand-alone test program           *
602 **************************************************
603 *************************************************/
604
605 #ifdef STAND_ALONE
606
607 int
608 main(int argc, char **cargv)
609 {
610 open_db dbblock[8];
611 int max_db = sizeof(dbblock)/sizeof(open_db);
612 int current = -1;
613 int showtime = 0;
614 int i;
615 dbdata_wait *dbwait = NULL;
616 uschar **argv = USS cargv;
617 uschar buffer[256];
618 uschar structbuffer[1024];
619
620 if (argc != 2)
621   {
622   printf("Usage: test_dbfn directory\n");
623   printf("The subdirectory called \"db\" in the given directory is used for\n");
624   printf("the files used in this test program.\n");
625   return 1;
626   }
627
628 /* Initialize */
629
630 spool_directory = argv[1];
631 debug_selector = D_all - D_memory;
632 debug_file = stderr;
633 big_buffer = malloc(big_buffer_size);
634
635 for (i = 0; i < max_db; i++) dbblock[i].dbptr = NULL;
636
637 printf("\nExim's db functions tester: interface type is %s\n", EXIM_DBTYPE);
638 printf("DBM library: ");
639
640 #ifdef DB_VERSION_STRING
641 printf("Berkeley DB: %s\n", DB_VERSION_STRING);
642 #elif defined(BTREEVERSION) && defined(HASHVERSION)
643   #ifdef USE_DB
644   printf("probably Berkeley DB version 1.8x (native mode)\n");
645   #else
646   printf("probably Berkeley DB version 1.8x (compatibility mode)\n");
647   #endif
648 #elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
649 printf("probably ndbm\n");
650 #elif defined(USE_TDB)
651 printf("using tdb\n");
652 #else
653   #ifdef USE_GDBM
654   printf("probably GDBM (native mode)\n");
655   #else
656   printf("probably GDBM (compatibility mode)\n");
657   #endif
658 #endif
659
660 /* Test the functions */
661
662 printf("\nTest the functions\n> ");
663
664 while (Ufgets(buffer, 256, stdin) != NULL)
665   {
666   int len = Ustrlen(buffer);
667   int count = 1;
668   clock_t start = 1;
669   clock_t stop = 0;
670   uschar *cmd = buffer;
671   while (len > 0 && isspace((uschar)buffer[len-1])) len--;
672   buffer[len] = 0;
673
674   if (isdigit((uschar)*cmd))
675     {
676     count = Uatoi(cmd);
677     while (isdigit((uschar)*cmd)) cmd++;
678     Uskip_whitespace(&cmd);
679     }
680
681   if (Ustrncmp(cmd, "open", 4) == 0)
682     {
683     int i;
684     open_db *odb;
685     uschar *s = cmd + 4;
686     Uskip_whitespace(&s);
687
688     for (i = 0; i < max_db; i++)
689       if (dbblock[i].dbptr == NULL) break;
690
691     if (i >= max_db)
692       {
693       printf("Too many open databases\n> ");
694       continue;
695       }
696
697     start = clock();
698     odb = dbfn_open(s, O_RDWR|O_CREAT, dbblock + i, TRUE, TRUE);
699     stop = clock();
700
701     if (odb)
702       {
703       current = i;
704       printf("opened %d\n", current);
705       }
706     /* Other error cases will have written messages */
707     else if (errno == ENOENT)
708       {
709       printf("open failed: %s%s\n", strerror(errno),
710         #ifdef USE_DB
711         " (or other Berkeley DB error)"
712         #else
713         ""
714         #endif
715         );
716       }
717     }
718
719   else if (Ustrncmp(cmd, "write", 5) == 0)
720     {
721     int rc = 0;
722     uschar * key = cmd + 5, * data;
723
724     if (current < 0)
725       {
726       printf("No current database\n");
727       continue;
728       }
729
730     Uskip_whitespace(&key);
731     data = key;
732     Uskip_nonwhite(&data);
733     *data++ = '\0';
734     Uskip_whitespace(&data);
735
736     dbwait = (dbdata_wait *)(&structbuffer);
737     Ustrcpy(dbwait->text, data);
738
739     start = clock();
740     while (count-- > 0)
741       rc = dbfn_write(dbblock + current, key, dbwait,
742         Ustrlen(data) + sizeof(dbdata_wait));
743     stop = clock();
744     if (rc != 0) printf("Failed: %s\n", strerror(errno));
745     }
746
747   else if (Ustrncmp(cmd, "read", 4) == 0)
748     {
749     uschar * key = cmd + 4;
750     if (current < 0)
751       {
752       printf("No current database\n");
753       continue;
754       }
755     Uskip_whitespace(&key);
756     start = clock();
757     while (count-- > 0)
758       dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock+ current, key, NULL);
759     stop = clock();
760     printf("%s\n", (dbwait == NULL)? "<not found>" : CS dbwait->text);
761     }
762
763   else if (Ustrncmp(cmd, "delete", 6) == 0)
764     {
765     uschar * key = cmd + 6;
766     if (current < 0)
767       {
768       printf("No current database\n");
769       continue;
770       }
771     Uskip_whitespace(&key);
772     dbfn_delete(dbblock + current, key);
773     }
774
775   else if (Ustrncmp(cmd, "scan", 4) == 0)
776     {
777     EXIM_CURSOR *cursor;
778     BOOL startflag = TRUE;
779     uschar *key;
780     uschar keybuffer[256];
781     if (current < 0)
782       {
783       printf("No current database\n");
784       continue;
785       }
786     start = clock();
787     while ((key = dbfn_scan(dbblock + current, startflag, &cursor)) != NULL)
788       {
789       startflag = FALSE;
790       Ustrcpy(keybuffer, key);
791       dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock + current,
792         keybuffer, NULL);
793       printf("%s: %s\n", keybuffer, dbwait->text);
794       }
795     stop = clock();
796     printf("End of scan\n");
797     }
798
799   else if (Ustrncmp(cmd, "close", 5) == 0)
800     {
801     uschar * s = cmd + 5;
802     Uskip_whitespace(&s);
803     i = Uatoi(s);
804     if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else
805       {
806       start = clock();
807       dbfn_close(dbblock + i);
808       stop = clock();
809       dbblock[i].dbptr = NULL;
810       if (i == current) current = -1;
811       }
812     }
813
814   else if (Ustrncmp(cmd, "file", 4) == 0)
815     {
816     uschar * s = cmd + 4;
817     Uskip_whitespace(&s);
818     i = Uatoi(s);
819     if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n");
820       else current = i;
821     }
822
823   else if (Ustrncmp(cmd, "time", 4) == 0)
824     {
825     showtime = ~showtime;
826     printf("Timing %s\n", showtime? "on" : "off");
827     }
828
829   else if (Ustrcmp(cmd, "q") == 0 || Ustrncmp(cmd, "quit", 4) == 0) break;
830
831   else if (Ustrncmp(cmd, "help", 4) == 0)
832     {
833     printf("close  [<number>]              close file [<number>]\n");
834     printf("delete <key>                   remove record from current file\n");
835     printf("file   <number>                make file <number> current\n");
836     printf("open   <name>                  open db file\n");
837     printf("q[uit]                         exit program\n");
838     printf("read   <key>                   read record from current file\n");
839     printf("scan                           scan current file\n");
840     printf("time                           time display on/off\n");
841     printf("write  <key> <rest-of-line>    write record to current file\n");
842     }
843
844   else printf("Eh?\n");
845
846   if (showtime && stop >= start)
847     printf("start=%d stop=%d difference=%d\n", (int)start, (int)stop,
848      (int)(stop - start));
849
850   printf("> ");
851   }
852
853 for (i = 0; i < max_db; i++)
854   {
855   if (dbblock[i].dbptr != NULL)
856     {
857     printf("\nClosing %d", i);
858     dbfn_close(dbblock + i);
859     }
860   }
861
862 printf("\n");
863 return 0;
864 }
865
866 #endif
867
868 /* End of dbfn.c */
869 /* vi: aw ai sw=2
870 */