Fix DSN Final-Recipient: field
[exim.git] / src / src / dbfn.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
059ec3d9
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8
9#include "exim.h"
10
11
12/* Functions for accessing Exim's hints database, which consists of a number of
13different DBM files. This module does not contain code for reading DBM files
14for (e.g.) alias expansion. That is all contained within the general search
15functions. As Exim now has support for several DBM interfaces, all the relevant
16functions are called as macros.
17
18All the data in Exim's database is in the nature of *hints*. Therefore it
19doesn't matter if it gets destroyed by accident. These functions are not
20supposed to implement a "safe" database.
21
22Keys are passed in as C strings, and the terminating zero *is* used when
23building the dbm files. This just makes life easier when scanning the files
24sequentially.
25
26Synchronization is required on the database files, and this is achieved by
27means of locking on independent lock files. (Earlier attempts to lock on the
28DBM files themselves were never completely successful.) Since callers may in
29general want to do more than one read or write while holding the lock, there
30are separate open and close functions. However, the calling modules should
31arrange to hold the locks for the bare minimum of time. */
32
33
34
35/*************************************************
36* Berkeley DB error callback *
37*************************************************/
38
39/* For Berkeley DB >= 2, we can define a function to be called in case of DB
40errors. This should help with debugging strange DB problems, e.g. getting "File
1f922db1
PH
41exists" when you try to open a db file. The API for this function was changed
42at DB release 4.3. */
059ec3d9
PH
43
44#if defined(USE_DB) && defined(DB_VERSION_STRING)
45void
1f922db1
PH
46#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
47dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
48{
49dbenv = dbenv;
50#else
059ec3d9
PH
51dbfn_bdb_error_callback(const char *pfx, char *msg)
52{
1f922db1 53#endif
059ec3d9
PH
54pfx = pfx;
55log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg);
56}
57#endif
58
59
60
61
62/*************************************************
63* Open and lock a database file *
64*************************************************/
65
66/* Used for accessing Exim's hints databases.
67
68Arguments:
69 name The single-component name of one of Exim's database files.
70 flags Either O_RDONLY or O_RDWR, indicating the type of open required;
71 O_RDWR implies "create if necessary"
72 dbblock Points to an open_db block to be filled in.
73 lof If TRUE, write to the log for actual open failures (locking failures
74 are always logged).
b10c87b3 75 panic If TRUE, panic on failure to create the db directory
059ec3d9
PH
76
77Returns: NULL if the open failed, or the locking failed. After locking
78 failures, errno is zero.
79
80 On success, dbblock is returned. This contains the dbm pointer and
81 the fd of the locked lock file.
eff37e47
PH
82
83There are some calls that use O_RDWR|O_CREAT for the flags. Having discovered
84this in December 2005, I'm not sure if this is correct or not, but for the
85moment I haven't changed them.
059ec3d9
PH
86*/
87
88open_db *
b10c87b3 89dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
059ec3d9
PH
90{
91int rc, save_errno;
92BOOL read_only = flags == O_RDONLY;
93BOOL created = FALSE;
94flock_t lock_data;
cfb9cf20 95uschar dirname[256], filename[256];
059ec3d9 96
966e829c
JH
97DEBUG(D_hints_lookup) acl_level++;
98
059ec3d9
PH
99/* The first thing to do is to open a separate file on which to lock. This
100ensures that Exim has exclusive use of the database before it even tries to
101open it. Early versions tried to lock on the open database itself, but that
102gave rise to mysterious problems from time to time - it was suspected that some
103DB libraries "do things" on their open() calls which break the interlocking.
104The lock file is never written to, but we open it for writing so we can get a
105write lock if required. If it does not exist, we create it. This is done
106separately so we know when we have done it, because when running as root we
107need to change the ownership - see the bottom of this function. We also try to
108make the directory as well, just in case. We won't be doing this many times
109unnecessarily, because usually the lock file will be there. If the directory
110exists, there is no error. */
111
cfb9cf20
JH
112snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
113snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
059ec3d9 114
cfb9cf20 115if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
059ec3d9
PH
116 {
117 created = TRUE;
b10c87b3 118 (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, panic);
cfb9cf20 119 dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
059ec3d9
PH
120 }
121
122if (dbblock->lockfd < 0)
123 {
124 log_write(0, LOG_MAIN, "%s",
cfb9cf20 125 string_open_failed(errno, "database lock file %s", filename));
059ec3d9 126 errno = 0; /* Indicates locking failure */
966e829c 127 DEBUG(D_hints_lookup) acl_level--;
059ec3d9
PH
128 return NULL;
129 }
130
131/* Now we must get a lock on the opened lock file; do this with a blocking
132lock that times out. */
133
134lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
135lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
136
137DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
966e829c 138 debug_printf_indent("locking %s\n", filename);
059ec3d9
PH
139
140sigalrm_seen = FALSE;
c2a1bba0 141ALARM(EXIMDB_LOCK_TIMEOUT);
059ec3d9 142rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
c2a1bba0 143ALARM_CLR(0);
059ec3d9
PH
144
145if (sigalrm_seen) errno = ETIMEDOUT;
146if (rc < 0)
147 {
8c07b69f 148 log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
cfb9cf20 149 read_only ? "read" : "write", filename,
7b4c8c1f 150 errno == ETIMEDOUT ? "timed out" : strerror(errno));
f1e894f3 151 (void)close(dbblock->lockfd);
059ec3d9 152 errno = 0; /* Indicates locking failure */
966e829c 153 DEBUG(D_hints_lookup) acl_level--;
059ec3d9
PH
154 return NULL;
155 }
156
966e829c 157DEBUG(D_hints_lookup) debug_printf_indent("locked %s\n", filename);
059ec3d9
PH
158
159/* At this point we have an opened and locked separate lock file, that is,
160exclusive access to the database, so we can go ahead and open it. If we are
161expected to create it, don't do so at first, again so that we can detect
162whether we need to change its ownership (see comments about the lock file
8187c3f3
PH
163above.) There have been regular reports of crashes while opening hints
164databases - often this is caused by non-matching db.h and the library. To make
165it easy to pin this down, there are now debug statements on either side of the
166open call. */
059ec3d9 167
cfb9cf20 168snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
cfb9cf20 169EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
059ec3d9 170
7b4c8c1f 171if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
059ec3d9
PH
172 {
173 DEBUG(D_hints_lookup)
966e829c 174 debug_printf_indent("%s appears not to exist: trying to create\n", filename);
059ec3d9 175 created = TRUE;
cfb9cf20 176 EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
059ec3d9
PH
177 }
178
179save_errno = errno;
180
181/* If we are running as root and this is the first access to the database, its
182files will be owned by root. We want them to be owned by exim. We detect this
183situation by noting above when we had to create the lock file or the database
184itself. Because the different dbm libraries use different extensions for their
185files, I don't know of any easier way of arranging this than scanning the
186directory for files with the appropriate base name. At least this deals with
187the lock file at the same time. Also, the directory will typically have only
188half a dozen files, so the scan will be quick.
189
190This code is placed here, before the test for successful opening, because there
191was a case when a file was created, but the DBM library still returned NULL
192because of some problem. It also sorts out the lock file if that was created
193but creation of the database file failed. */
194
195if (created && geteuid() == root_uid)
196 {
197 DIR *dd;
198 struct dirent *ent;
cfb9cf20 199 uschar *lastname = Ustrrchr(filename, '/') + 1;
059ec3d9
PH
200 int namelen = Ustrlen(name);
201
202 *lastname = 0;
cfb9cf20 203 dd = opendir(CS filename);
059ec3d9 204
7b4c8c1f 205 while ((ent = readdir(dd)))
059ec3d9
PH
206 if (Ustrncmp(ent->d_name, name, namelen) == 0)
207 {
208 struct stat statbuf;
209 Ustrcpy(lastname, ent->d_name);
cfb9cf20 210 if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
059ec3d9 211 {
966e829c 212 DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
b66fecb4 213 if (exim_chown(filename, exim_uid, exim_gid))
966e829c 214 DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename);
059ec3d9
PH
215 }
216 }
059ec3d9
PH
217
218 closedir(dd);
219 }
220
221/* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
0a6c178c
JH
222log the event - also for debugging - but debug only if the file just doesn't
223exist. */
059ec3d9 224
7b4c8c1f 225if (!dbblock->dbptr)
059ec3d9 226 {
0a6c178c
JH
227 if (lof && save_errno != ENOENT)
228 log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
cfb9cf20 229 filename));
0a6c178c
JH
230 else
231 DEBUG(D_hints_lookup)
966e829c 232 debug_printf_indent("%s\n", CS string_open_failed(save_errno, "DB file %s",
cfb9cf20 233 filename));
f1e894f3 234 (void)close(dbblock->lockfd);
059ec3d9 235 errno = save_errno;
966e829c 236 DEBUG(D_hints_lookup) acl_level--;
059ec3d9
PH
237 return NULL;
238 }
239
240DEBUG(D_hints_lookup)
966e829c 241 debug_printf_indent("opened hints database %s: flags=%s\n", filename,
7b4c8c1f
JH
242 flags == O_RDONLY ? "O_RDONLY"
243 : flags == O_RDWR ? "O_RDWR"
244 : flags == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT"
245 : "??");
059ec3d9
PH
246
247/* Pass back the block containing the opened database handle and the open fd
248for the lock. */
249
250return dbblock;
251}
252
253
254
255
256/*************************************************
257* Unlock and close a database file *
258*************************************************/
259
260/* Closing a file automatically unlocks it, so after closing the database, just
261close the lock file.
262
263Argument: a pointer to an open database block
264Returns: nothing
265*/
266
267void
268dbfn_close(open_db *dbblock)
269{
270EXIM_DBCLOSE(dbblock->dbptr);
f1e894f3 271(void)close(dbblock->lockfd);
966e829c
JH
272DEBUG(D_hints_lookup)
273 { debug_printf_indent("closed hints database and lockfile\n"); acl_level--; }
059ec3d9
PH
274}
275
276
277
278
279/*************************************************
280* Read from database file *
281*************************************************/
282
283/* Passing back the pointer unchanged is useless, because there is
284no guarantee of alignment. Since all the records used by Exim need
285to be properly aligned to pick out the timestamps, etc., we might as
286well do the copying centrally here.
287
288Most calls don't need the length, so there is a macro called dbfn_read which
289has two arguments; it calls this function adding NULL as the third.
290
291Arguments:
292 dbblock a pointer to an open database block
293 key the key of the record to be read
294 length a pointer to an int into which to return the length, if not NULL
295
296Returns: a pointer to the retrieved record, or
297 NULL if the record is not found
298*/
299
300void *
55414b25 301dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
059ec3d9
PH
302{
303void *yield;
304EXIM_DATUM key_datum, result_datum;
55414b25
JH
305int klen = Ustrlen(key) + 1;
306uschar * key_copy = store_get(klen);
307
308memcpy(key_copy, key, klen);
059ec3d9 309
966e829c 310DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%s\n", key);
059ec3d9
PH
311
312EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
313EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
55414b25
JH
314EXIM_DATUM_DATA(key_datum) = CS key_copy;
315EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
316
317if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
318
319yield = store_get(EXIM_DATUM_SIZE(result_datum));
320memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
321if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
322
323EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
324return yield;
325}
326
327
328
329/*************************************************
330* Write to database file *
331*************************************************/
332
333/*
334Arguments:
335 dbblock a pointer to an open database block
336 key the key of the record to be written
337 ptr a pointer to the record to be written
338 length the length of the record to be written
339
340Returns: the yield of the underlying dbm or db "write" function. If this
341 is dbm, the value is zero for OK.
342*/
343
344int
55414b25 345dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
059ec3d9
PH
346{
347EXIM_DATUM key_datum, value_datum;
348dbdata_generic *gptr = (dbdata_generic *)ptr;
55414b25
JH
349int klen = Ustrlen(key) + 1;
350uschar * key_copy = store_get(klen);
351
352memcpy(key_copy, key, klen);
059ec3d9
PH
353gptr->time_stamp = time(NULL);
354
966e829c 355DEBUG(D_hints_lookup) debug_printf_indent("dbfn_write: key=%s\n", key);
059ec3d9
PH
356
357EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
358EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
55414b25
JH
359EXIM_DATUM_DATA(key_datum) = CS key_copy;
360EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
361EXIM_DATUM_DATA(value_datum) = CS ptr;
362EXIM_DATUM_SIZE(value_datum) = length;
363return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
364}
365
366
367
368/*************************************************
369* Delete record from database file *
370*************************************************/
371
372/*
373Arguments:
374 dbblock a pointer to an open database block
375 key the key of the record to be deleted
376
377Returns: the yield of the underlying dbm or db "delete" function.
378*/
379
380int
55414b25 381dbfn_delete(open_db *dbblock, const uschar *key)
059ec3d9 382{
55414b25
JH
383int klen = Ustrlen(key) + 1;
384uschar * key_copy = store_get(klen);
385
6b1bf31e
JH
386DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
387
55414b25 388memcpy(key_copy, key, klen);
059ec3d9
PH
389EXIM_DATUM key_datum;
390EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
55414b25
JH
391EXIM_DATUM_DATA(key_datum) = CS key_copy;
392EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
393return EXIM_DBDEL(dbblock->dbptr, key_datum);
394}
395
396
397
398/*************************************************
399* Scan the keys of a database file *
400*************************************************/
401
402/*
403Arguments:
404 dbblock a pointer to an open database block
405 start TRUE if starting a new scan
406 FALSE if continuing with the current scan
407 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
408 that use the notion of a cursor
409
410Returns: the next record from the file, or
411 NULL if there are no more
412*/
413
414uschar *
415dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
416{
417EXIM_DATUM key_datum, value_datum;
418uschar *yield;
419value_datum = value_datum; /* dummy; not all db libraries use this */
420
6b1bf31e
JH
421DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");
422
059ec3d9
PH
423/* Some dbm require an initialization */
424
425if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
426
427EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
428EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
429
430yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
431 US EXIM_DATUM_DATA(key_datum) : NULL;
432
433/* Some dbm require a termination */
434
435if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
436return yield;
437}
438
439
440
441/*************************************************
442**************************************************
443* Stand-alone test program *
444**************************************************
445*************************************************/
446
447#ifdef STAND_ALONE
448
449int
450main(int argc, char **cargv)
451{
452open_db dbblock[8];
453int max_db = sizeof(dbblock)/sizeof(open_db);
454int current = -1;
455int showtime = 0;
456int i;
457dbdata_wait *dbwait = NULL;
458uschar **argv = USS cargv;
459uschar buffer[256];
460uschar structbuffer[1024];
461
462if (argc != 2)
463 {
464 printf("Usage: test_dbfn directory\n");
465 printf("The subdirectory called \"db\" in the given directory is used for\n");
466 printf("the files used in this test program.\n");
467 return 1;
468 }
469
470/* Initialize */
471
472spool_directory = argv[1];
473debug_selector = D_all - D_memory;
474debug_file = stderr;
475big_buffer = malloc(big_buffer_size);
476
477for (i = 0; i < max_db; i++) dbblock[i].dbptr = NULL;
478
479printf("\nExim's db functions tester: interface type is %s\n", EXIM_DBTYPE);
480printf("DBM library: ");
481
482#ifdef DB_VERSION_STRING
483printf("Berkeley DB: %s\n", DB_VERSION_STRING);
484#elif defined(BTREEVERSION) && defined(HASHVERSION)
485 #ifdef USE_DB
486 printf("probably Berkeley DB version 1.8x (native mode)\n");
487 #else
488 printf("probably Berkeley DB version 1.8x (compatibility mode)\n");
489 #endif
490#elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
491printf("probably ndbm\n");
492#elif defined(USE_TDB)
493printf("using tdb\n");
494#else
495 #ifdef USE_GDBM
496 printf("probably GDBM (native mode)\n");
497 #else
498 printf("probably GDBM (compatibility mode)\n");
499 #endif
500#endif
501
502/* Test the functions */
503
504printf("\nTest the functions\n> ");
505
506while (Ufgets(buffer, 256, stdin) != NULL)
507 {
508 int len = Ustrlen(buffer);
509 int count = 1;
510 clock_t start = 1;
511 clock_t stop = 0;
512 uschar *cmd = buffer;
513 while (len > 0 && isspace((uschar)buffer[len-1])) len--;
514 buffer[len] = 0;
515
516 if (isdigit((uschar)*cmd))
517 {
518 count = Uatoi(cmd);
519 while (isdigit((uschar)*cmd)) cmd++;
520 while (isspace((uschar)*cmd)) cmd++;
521 }
522
523 if (Ustrncmp(cmd, "open", 4) == 0)
524 {
525 int i;
526 open_db *odb;
527 uschar *s = cmd + 4;
528 while (isspace((uschar)*s)) s++;
529
530 for (i = 0; i < max_db; i++)
531 if (dbblock[i].dbptr == NULL) break;
532
533 if (i >= max_db)
534 {
535 printf("Too many open databases\n> ");
536 continue;
537 }
538
539 start = clock();
b10c87b3 540 odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE, TRUE);
059ec3d9
PH
541 stop = clock();
542
0a6c178c 543 if (odb)
059ec3d9
PH
544 {
545 current = i;
546 printf("opened %d\n", current);
547 }
548 /* Other error cases will have written messages */
549 else if (errno == ENOENT)
550 {
551 printf("open failed: %s%s\n", strerror(errno),
552 #ifdef USE_DB
553 " (or other Berkeley DB error)"
554 #else
555 ""
556 #endif
557 );
558 }
559 }
560
561 else if (Ustrncmp(cmd, "write", 5) == 0)
562 {
563 int rc = 0;
564 uschar *key = cmd + 5;
565 uschar *data;
566
567 if (current < 0)
568 {
569 printf("No current database\n");
570 continue;
571 }
572
573 while (isspace((uschar)*key)) key++;
574 data = key;
575 while (*data != 0 && !isspace((uschar)*data)) data++;
576 *data++ = 0;
577 while (isspace((uschar)*data)) data++;
578
579 dbwait = (dbdata_wait *)(&structbuffer);
580 Ustrcpy(dbwait->text, data);
581
582 start = clock();
583 while (count-- > 0)
584 rc = dbfn_write(dbblock + current, key, dbwait,
585 Ustrlen(data) + sizeof(dbdata_wait));
586 stop = clock();
587 if (rc != 0) printf("Failed: %s\n", strerror(errno));
588 }
589
590 else if (Ustrncmp(cmd, "read", 4) == 0)
591 {
592 uschar *key = cmd + 4;
593 if (current < 0)
594 {
595 printf("No current database\n");
596 continue;
597 }
598 while (isspace((uschar)*key)) key++;
599 start = clock();
600 while (count-- > 0)
601 dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock+ current, key, NULL);
602 stop = clock();
603 printf("%s\n", (dbwait == NULL)? "<not found>" : CS dbwait->text);
604 }
605
606 else if (Ustrncmp(cmd, "delete", 6) == 0)
607 {
608 uschar *key = cmd + 6;
609 if (current < 0)
610 {
611 printf("No current database\n");
612 continue;
613 }
614 while (isspace((uschar)*key)) key++;
615 dbfn_delete(dbblock + current, key);
616 }
617
618 else if (Ustrncmp(cmd, "scan", 4) == 0)
619 {
620 EXIM_CURSOR *cursor;
621 BOOL startflag = TRUE;
622 uschar *key;
623 uschar keybuffer[256];
624 if (current < 0)
625 {
626 printf("No current database\n");
627 continue;
628 }
629 start = clock();
630 while ((key = dbfn_scan(dbblock + current, startflag, &cursor)) != NULL)
631 {
632 startflag = FALSE;
633 Ustrcpy(keybuffer, key);
634 dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock + current,
635 keybuffer, NULL);
636 printf("%s: %s\n", keybuffer, dbwait->text);
637 }
638 stop = clock();
639 printf("End of scan\n");
640 }
641
642 else if (Ustrncmp(cmd, "close", 5) == 0)
643 {
644 uschar *s = cmd + 5;
645 while (isspace((uschar)*s)) s++;
646 i = Uatoi(s);
647 if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else
648 {
649 start = clock();
650 dbfn_close(dbblock + i);
651 stop = clock();
652 dbblock[i].dbptr = NULL;
653 if (i == current) current = -1;
654 }
655 }
656
657 else if (Ustrncmp(cmd, "file", 4) == 0)
658 {
659 uschar *s = cmd + 4;
660 while (isspace((uschar)*s)) s++;
661 i = Uatoi(s);
662 if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n");
663 else current = i;
664 }
665
666 else if (Ustrncmp(cmd, "time", 4) == 0)
667 {
668 showtime = ~showtime;
669 printf("Timing %s\n", showtime? "on" : "off");
670 }
671
672 else if (Ustrcmp(cmd, "q") == 0 || Ustrncmp(cmd, "quit", 4) == 0) break;
673
674 else if (Ustrncmp(cmd, "help", 4) == 0)
675 {
676 printf("close [<number>] close file [<number>]\n");
677 printf("delete <key> remove record from current file\n");
678 printf("file <number> make file <number> current\n");
679 printf("open <name> open db file\n");
680 printf("q[uit] exit program\n");
681 printf("read <key> read record from current file\n");
682 printf("scan scan current file\n");
683 printf("time time display on/off\n");
684 printf("write <key> <rest-of-line> write record to current file\n");
685 }
686
687 else printf("Eh?\n");
688
689 if (showtime && stop >= start)
690 printf("start=%d stop=%d difference=%d\n", (int)start, (int)stop,
691 (int)(stop - start));
692
693 printf("> ");
694 }
695
696for (i = 0; i < max_db; i++)
697 {
698 if (dbblock[i].dbptr != NULL)
699 {
700 printf("\nClosing %d", i);
701 dbfn_close(dbblock + i);
702 }
703 }
704
705printf("\n");
706return 0;
707}
708
709#endif
710
711/* End of dbfn.c */