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