Testsuite: strip trailing / from "pwd" output
[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;
f3ebb786 209 Ustrcpy(lastname, US 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 305int klen = Ustrlen(key) + 1;
f3ebb786 306uschar * key_copy = store_get(klen, is_tainted(key));
55414b25
JH
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
f3ebb786
JH
319/* Assume the data store could have been tainted. Properly, we should
320store the taint status with the data. */
321
322yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
059ec3d9
PH
323memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
324if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
325
326EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
327return yield;
328}
329
330
331
332/*************************************************
333* Write to database file *
334*************************************************/
335
336/*
337Arguments:
338 dbblock a pointer to an open database block
339 key the key of the record to be written
340 ptr a pointer to the record to be written
341 length the length of the record to be written
342
343Returns: the yield of the underlying dbm or db "write" function. If this
344 is dbm, the value is zero for OK.
345*/
346
347int
55414b25 348dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
059ec3d9
PH
349{
350EXIM_DATUM key_datum, value_datum;
351dbdata_generic *gptr = (dbdata_generic *)ptr;
55414b25 352int klen = Ustrlen(key) + 1;
f3ebb786 353uschar * key_copy = store_get(klen, is_tainted(key));
55414b25
JH
354
355memcpy(key_copy, key, klen);
059ec3d9
PH
356gptr->time_stamp = time(NULL);
357
966e829c 358DEBUG(D_hints_lookup) debug_printf_indent("dbfn_write: key=%s\n", key);
059ec3d9
PH
359
360EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
361EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
55414b25
JH
362EXIM_DATUM_DATA(key_datum) = CS key_copy;
363EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
364EXIM_DATUM_DATA(value_datum) = CS ptr;
365EXIM_DATUM_SIZE(value_datum) = length;
366return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
367}
368
369
370
371/*************************************************
372* Delete record from database file *
373*************************************************/
374
375/*
376Arguments:
377 dbblock a pointer to an open database block
378 key the key of the record to be deleted
379
380Returns: the yield of the underlying dbm or db "delete" function.
381*/
382
383int
55414b25 384dbfn_delete(open_db *dbblock, const uschar *key)
059ec3d9 385{
55414b25 386int klen = Ustrlen(key) + 1;
f3ebb786 387uschar * key_copy = store_get(klen, is_tainted(key));
55414b25 388
6b1bf31e
JH
389DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
390
55414b25 391memcpy(key_copy, key, klen);
059ec3d9
PH
392EXIM_DATUM key_datum;
393EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
55414b25
JH
394EXIM_DATUM_DATA(key_datum) = CS key_copy;
395EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
396return EXIM_DBDEL(dbblock->dbptr, key_datum);
397}
398
399
400
401/*************************************************
402* Scan the keys of a database file *
403*************************************************/
404
405/*
406Arguments:
407 dbblock a pointer to an open database block
408 start TRUE if starting a new scan
409 FALSE if continuing with the current scan
410 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
411 that use the notion of a cursor
412
413Returns: the next record from the file, or
414 NULL if there are no more
415*/
416
417uschar *
418dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
419{
420EXIM_DATUM key_datum, value_datum;
421uschar *yield;
422value_datum = value_datum; /* dummy; not all db libraries use this */
423
6b1bf31e
JH
424DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");
425
059ec3d9
PH
426/* Some dbm require an initialization */
427
428if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
429
430EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
431EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
432
433yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
434 US EXIM_DATUM_DATA(key_datum) : NULL;
435
436/* Some dbm require a termination */
437
438if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
439return yield;
440}
441
442
443
444/*************************************************
445**************************************************
446* Stand-alone test program *
447**************************************************
448*************************************************/
449
450#ifdef STAND_ALONE
451
452int
453main(int argc, char **cargv)
454{
455open_db dbblock[8];
456int max_db = sizeof(dbblock)/sizeof(open_db);
457int current = -1;
458int showtime = 0;
459int i;
460dbdata_wait *dbwait = NULL;
461uschar **argv = USS cargv;
462uschar buffer[256];
463uschar structbuffer[1024];
464
465if (argc != 2)
466 {
467 printf("Usage: test_dbfn directory\n");
468 printf("The subdirectory called \"db\" in the given directory is used for\n");
469 printf("the files used in this test program.\n");
470 return 1;
471 }
472
473/* Initialize */
474
475spool_directory = argv[1];
476debug_selector = D_all - D_memory;
477debug_file = stderr;
478big_buffer = malloc(big_buffer_size);
479
480for (i = 0; i < max_db; i++) dbblock[i].dbptr = NULL;
481
482printf("\nExim's db functions tester: interface type is %s\n", EXIM_DBTYPE);
483printf("DBM library: ");
484
485#ifdef DB_VERSION_STRING
486printf("Berkeley DB: %s\n", DB_VERSION_STRING);
487#elif defined(BTREEVERSION) && defined(HASHVERSION)
488 #ifdef USE_DB
489 printf("probably Berkeley DB version 1.8x (native mode)\n");
490 #else
491 printf("probably Berkeley DB version 1.8x (compatibility mode)\n");
492 #endif
493#elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
494printf("probably ndbm\n");
495#elif defined(USE_TDB)
496printf("using tdb\n");
497#else
498 #ifdef USE_GDBM
499 printf("probably GDBM (native mode)\n");
500 #else
501 printf("probably GDBM (compatibility mode)\n");
502 #endif
503#endif
504
505/* Test the functions */
506
507printf("\nTest the functions\n> ");
508
509while (Ufgets(buffer, 256, stdin) != NULL)
510 {
511 int len = Ustrlen(buffer);
512 int count = 1;
513 clock_t start = 1;
514 clock_t stop = 0;
515 uschar *cmd = buffer;
516 while (len > 0 && isspace((uschar)buffer[len-1])) len--;
517 buffer[len] = 0;
518
519 if (isdigit((uschar)*cmd))
520 {
521 count = Uatoi(cmd);
522 while (isdigit((uschar)*cmd)) cmd++;
523 while (isspace((uschar)*cmd)) cmd++;
524 }
525
526 if (Ustrncmp(cmd, "open", 4) == 0)
527 {
528 int i;
529 open_db *odb;
530 uschar *s = cmd + 4;
531 while (isspace((uschar)*s)) s++;
532
533 for (i = 0; i < max_db; i++)
534 if (dbblock[i].dbptr == NULL) break;
535
536 if (i >= max_db)
537 {
538 printf("Too many open databases\n> ");
539 continue;
540 }
541
542 start = clock();
b10c87b3 543 odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE, TRUE);
059ec3d9
PH
544 stop = clock();
545
0a6c178c 546 if (odb)
059ec3d9
PH
547 {
548 current = i;
549 printf("opened %d\n", current);
550 }
551 /* Other error cases will have written messages */
552 else if (errno == ENOENT)
553 {
554 printf("open failed: %s%s\n", strerror(errno),
555 #ifdef USE_DB
556 " (or other Berkeley DB error)"
557 #else
558 ""
559 #endif
560 );
561 }
562 }
563
564 else if (Ustrncmp(cmd, "write", 5) == 0)
565 {
566 int rc = 0;
567 uschar *key = cmd + 5;
568 uschar *data;
569
570 if (current < 0)
571 {
572 printf("No current database\n");
573 continue;
574 }
575
576 while (isspace((uschar)*key)) key++;
577 data = key;
578 while (*data != 0 && !isspace((uschar)*data)) data++;
579 *data++ = 0;
580 while (isspace((uschar)*data)) data++;
581
582 dbwait = (dbdata_wait *)(&structbuffer);
583 Ustrcpy(dbwait->text, data);
584
585 start = clock();
586 while (count-- > 0)
587 rc = dbfn_write(dbblock + current, key, dbwait,
588 Ustrlen(data) + sizeof(dbdata_wait));
589 stop = clock();
590 if (rc != 0) printf("Failed: %s\n", strerror(errno));
591 }
592
593 else if (Ustrncmp(cmd, "read", 4) == 0)
594 {
595 uschar *key = cmd + 4;
596 if (current < 0)
597 {
598 printf("No current database\n");
599 continue;
600 }
601 while (isspace((uschar)*key)) key++;
602 start = clock();
603 while (count-- > 0)
604 dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock+ current, key, NULL);
605 stop = clock();
606 printf("%s\n", (dbwait == NULL)? "<not found>" : CS dbwait->text);
607 }
608
609 else if (Ustrncmp(cmd, "delete", 6) == 0)
610 {
611 uschar *key = cmd + 6;
612 if (current < 0)
613 {
614 printf("No current database\n");
615 continue;
616 }
617 while (isspace((uschar)*key)) key++;
618 dbfn_delete(dbblock + current, key);
619 }
620
621 else if (Ustrncmp(cmd, "scan", 4) == 0)
622 {
623 EXIM_CURSOR *cursor;
624 BOOL startflag = TRUE;
625 uschar *key;
626 uschar keybuffer[256];
627 if (current < 0)
628 {
629 printf("No current database\n");
630 continue;
631 }
632 start = clock();
633 while ((key = dbfn_scan(dbblock + current, startflag, &cursor)) != NULL)
634 {
635 startflag = FALSE;
636 Ustrcpy(keybuffer, key);
637 dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock + current,
638 keybuffer, NULL);
639 printf("%s: %s\n", keybuffer, dbwait->text);
640 }
641 stop = clock();
642 printf("End of scan\n");
643 }
644
645 else if (Ustrncmp(cmd, "close", 5) == 0)
646 {
647 uschar *s = cmd + 5;
648 while (isspace((uschar)*s)) s++;
649 i = Uatoi(s);
650 if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else
651 {
652 start = clock();
653 dbfn_close(dbblock + i);
654 stop = clock();
655 dbblock[i].dbptr = NULL;
656 if (i == current) current = -1;
657 }
658 }
659
660 else if (Ustrncmp(cmd, "file", 4) == 0)
661 {
662 uschar *s = cmd + 4;
663 while (isspace((uschar)*s)) s++;
664 i = Uatoi(s);
665 if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n");
666 else current = i;
667 }
668
669 else if (Ustrncmp(cmd, "time", 4) == 0)
670 {
671 showtime = ~showtime;
672 printf("Timing %s\n", showtime? "on" : "off");
673 }
674
675 else if (Ustrcmp(cmd, "q") == 0 || Ustrncmp(cmd, "quit", 4) == 0) break;
676
677 else if (Ustrncmp(cmd, "help", 4) == 0)
678 {
679 printf("close [<number>] close file [<number>]\n");
680 printf("delete <key> remove record from current file\n");
681 printf("file <number> make file <number> current\n");
682 printf("open <name> open db file\n");
683 printf("q[uit] exit program\n");
684 printf("read <key> read record from current file\n");
685 printf("scan scan current file\n");
686 printf("time time display on/off\n");
687 printf("write <key> <rest-of-line> write record to current file\n");
688 }
689
690 else printf("Eh?\n");
691
692 if (showtime && stop >= start)
693 printf("start=%d stop=%d difference=%d\n", (int)start, (int)stop,
694 (int)(stop - start));
695
696 printf("> ");
697 }
698
699for (i = 0; i < max_db; i++)
700 {
701 if (dbblock[i].dbptr != NULL)
702 {
703 printf("\nClosing %d", i);
704 dbfn_close(dbblock + i);
705 }
706 }
707
708printf("\n");
709return 0;
710}
711
712#endif
713
714/* End of dbfn.c */