1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.4 2005/05/23 16:58:56 fanf2 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2005 */
8 /* See the file NOTICE for conditions of use and distribution. */
11 /* This single source file is used to compile three utility programs for
12 maintaining Exim hints databases.
14 exim_dumpdb dumps out the contents
15 exim_fixdb patches the database (really for Exim maintenance/testing)
16 exim_tidydb removed obsolete data
18 In all cases, the first argument is the name of the spool directory. The second
19 argument is the name of the database file. The available names are:
21 retry: retry delivery information
22 misc: miscellaneous hints data
23 wait-<t>: message waiting information; <t> is a transport name
24 callout: callout verification cache
26 There are a number of common subroutines, followed by three main programs,
27 whose inclusion is controlled by -D on the compilation command. */
30 /* Standard C headers and Unix headers */
46 /* These are two values from macros.h which should perhaps be accessible in
47 some better way than just repeating them here. */
49 #define WAIT_NAME_MAX 50
50 #define MESSAGE_ID_LENGTH 16
53 /* This selection of Exim headers contains exactly what we need, and hopefully
54 not too much extra baggage. */
56 #include "config.h" /* Needed to get the DB type */
60 #include "osfunctions.h"
64 /* Identifiers for the different database types. */
69 #define type_callout 4
70 #define type_ratelimit 5
75 /*************************************************
76 * Berkeley DB error callback *
77 *************************************************/
79 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
80 errors. This should help with debugging strange DB problems, e.g. getting "File
81 exists" when you try to open a db file. */
83 #if defined(USE_DB) && defined(DB_VERSION_STRING)
85 dbfn_bdb_error_callback(const char *pfx, char *msg)
88 printf("Berkeley DB error: %s\n", msg);
94 /*************************************************
96 *************************************************/
98 static int sigalrm_seen;
101 sigalrm_handler(int sig)
103 sig = sig; /* Keep picky compilers happy */
109 /*************************************************
110 * Output usage message and exit *
111 *************************************************/
114 usage(uschar *name, uschar *options)
116 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
117 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
123 /*************************************************
124 * Sort out the command arguments *
125 *************************************************/
127 /* This function checks that there are exactly 2 arguments, and checks the
128 second of them to be sure it is a known database name. */
131 check_args(int argc, uschar **argv, uschar *name, uschar *options)
135 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
136 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
137 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
138 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
139 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
141 usage(name, options);
142 return -1; /* Never obeyed */
147 /*************************************************
148 * Handle attempts to write the log *
149 *************************************************/
151 /* The message gets written to stderr when log_write() is called from a
152 utility. The message always gets '\n' added on the end of it. These calls come
153 from modules such as store.c when things go drastically wrong (e.g. malloc()
154 failing). In normal use they won't get obeyed.
157 selector not relevant when running a utility
158 flags not relevant when running a utility
159 format a printf() format
160 ... arguments for format
166 log_write(unsigned int selector, int flags, char *format, ...)
169 va_start(ap, format);
170 vfprintf(stderr, format, ap);
171 fprintf(stderr, "\n");
173 selector = selector; /* Keep picky compilers happy */
179 /*************************************************
180 * Format a time value for printing *
181 *************************************************/
183 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
188 struct tm *tmstr = localtime(&t);
189 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
195 /*************************************************
196 * Format a cache value for printing *
197 *************************************************/
200 print_cache(int value)
202 return (value == ccache_accept)? US"accept" :
203 (value == ccache_reject)? US"reject" :
209 /*************************************************
211 *************************************************/
219 time_t now = time(NULL);
220 struct tm *tm = localtime(&now);
225 for (t = s + Ustrlen(s) - 1; t >= s; t--)
227 if (*t == ':') continue;
228 if (!isdigit((uschar)*t)) return -1;
233 if (!isdigit((uschar)*t)) return -1;
234 value = value + (*t - '0')*10;
239 case 0: tm->tm_min = value; break;
240 case 1: tm->tm_hour = value; break;
241 case 2: tm->tm_mday = value; break;
242 case 3: tm->tm_mon = value - 1; break;
243 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
250 #endif /* EXIM_FIXDB */
254 /*************************************************
255 * Open and lock a database file *
256 *************************************************/
258 /* This is a cut-down version from the function in dbfn.h that Exim itself
259 uses. We assume the database exists, and therefore give up if we cannot open
263 spool The spool directory
264 name The single-component name of one of Exim's database files.
265 flags O_RDONLY or O_RDWR
266 dbblock Points to an open_db block to be filled in.
268 Returns: NULL if the open failed, or the locking failed.
269 On success, dbblock is returned. This contains the dbm pointer and
270 the fd of the locked lock file.
274 dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
277 struct flock lock_data;
278 BOOL read_only = flags == O_RDONLY;
281 /* The first thing to do is to open a separate file on which to lock. This
282 ensures that Exim has exclusive use of the database before it even tries to
283 open it. If there is a database, there should be a lock file in existence. */
285 sprintf(CS buffer, "%s/db/%s.lockfile", spool, name);
287 dbblock->lockfd = Uopen(buffer, flags, 0);
288 if (dbblock->lockfd < 0)
290 printf("** Failed to open database lock file %s: %s\n", buffer,
295 /* Now we must get a lock on the opened lock file; do this with a blocking
296 lock that times out. */
298 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
299 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
301 sigalrm_seen = FALSE;
302 os_non_restarting_signal(SIGALRM, sigalrm_handler);
303 alarm(EXIMDB_LOCK_TIMEOUT);
304 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
307 if (sigalrm_seen) errno = ETIMEDOUT;
310 printf("** Failed to get %s lock for %s: %s",
311 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
312 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
313 close(dbblock->lockfd);
317 /* At this point we have an opened and locked separate lock file, that is,
318 exclusive access to the database, so we can go ahead and open it. */
320 sprintf(CS buffer, "%s/db/%s", spool, name);
321 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
323 if (dbblock->dbptr == NULL)
325 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
326 read_only? "reading" : "writing", strerror(errno),
328 " (or Berkeley DB error while opening)"
333 close(dbblock->lockfd);
343 /*************************************************
344 * Unlock and close a database file *
345 *************************************************/
347 /* Closing a file automatically unlocks it, so after closing the database, just
350 Argument: a pointer to an open database block
355 dbfn_close(open_db *dbblock)
357 EXIM_DBCLOSE(dbblock->dbptr);
358 close(dbblock->lockfd);
364 /*************************************************
365 * Read from database file *
366 *************************************************/
368 /* Passing back the pointer unchanged is useless, because there is no guarantee
369 of alignment. Since all the records used by Exim need to be properly aligned to
370 pick out the timestamps, etc., do the copying centrally here.
373 dbblock a pointer to an open database block
374 key the key of the record to be read
375 length where to put the length (or NULL if length not wanted)
377 Returns: a pointer to the retrieved record, or
378 NULL if the record is not found
382 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
385 EXIM_DATUM key_datum, result_datum;
387 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
388 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
389 EXIM_DATUM_DATA(key_datum) = CS key;
390 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
392 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
394 yield = store_get(EXIM_DATUM_SIZE(result_datum));
395 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
396 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
398 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
404 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
406 /*************************************************
407 * Write to database file *
408 *************************************************/
412 dbblock a pointer to an open database block
413 key the key of the record to be written
414 ptr a pointer to the record to be written
415 length the length of the record to be written
417 Returns: the yield of the underlying dbm or db "write" function. If this
418 is dbm, the value is zero for OK.
422 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
424 EXIM_DATUM key_datum, value_datum;
425 dbdata_generic *gptr = (dbdata_generic *)ptr;
426 gptr->time_stamp = time(NULL);
428 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
429 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
430 EXIM_DATUM_DATA(key_datum) = CS key;
431 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
432 EXIM_DATUM_DATA(value_datum) = CS ptr;
433 EXIM_DATUM_SIZE(value_datum) = length;
434 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
439 /*************************************************
440 * Delete record from database file *
441 *************************************************/
445 dbblock a pointer to an open database block
446 key the key of the record to be deleted
448 Returns: the yield of the underlying dbm or db "delete" function.
452 dbfn_delete(open_db *dbblock, uschar *key)
454 EXIM_DATUM key_datum;
455 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
456 EXIM_DATUM_DATA(key_datum) = CS key;
457 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
458 return EXIM_DBDEL(dbblock->dbptr, key_datum);
461 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
465 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
466 /*************************************************
467 * Scan the keys of a database file *
468 *************************************************/
472 dbblock a pointer to an open database block
473 start TRUE if starting a new scan
474 FALSE if continuing with the current scan
475 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
476 that use the notion of a cursor
478 Returns: the next record from the file, or
479 NULL if there are no more
483 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
485 EXIM_DATUM key_datum, value_datum;
487 value_datum = value_datum; /* dummy; not all db libraries use this */
489 /* Some dbm require an initialization */
491 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
493 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
494 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
496 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
497 US EXIM_DATUM_DATA(key_datum) : NULL;
499 /* Some dbm require a termination */
501 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
504 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
509 /*************************************************
510 * The exim_dumpdb main program *
511 *************************************************/
514 main(int argc, char **cargv)
521 uschar **argv = USS cargv;
523 uschar keybuffer[1024];
525 /* Check the arguments, and open the database */
527 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
528 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
529 if (dbm == NULL) exit(1);
531 /* Scan the file, formatting the information for each entry. Note
532 that data is returned in a malloc'ed block, in order that it be
533 correctly aligned. */
535 key = dbfn_scan(dbm, TRUE, &cursor);
540 dbdata_callout_cache *callout;
541 dbdata_ratelimit *ratelimit;
545 uschar name[MESSAGE_ID_LENGTH + 1];
548 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
549 which might change. */
551 if (Ustrlen(key) > sizeof(keybuffer) - 1)
553 printf("**** Overlong key encountered: %s\n", key);
556 Ustrcpy(keybuffer, key);
557 value = dbfn_read_with_length(dbm, keybuffer, &length);
560 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
561 "was not found in the file - something is wrong!\n",
565 /* Note: don't use print_time more than once in one statement, since
566 it uses a single buffer. */
571 retry = (dbdata_retry *)value;
572 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
573 retry->more_errno, retry->text,
574 print_time(retry->first_failed));
575 printf("%s ", print_time(retry->last_try));
576 printf("%s %s\n", print_time(retry->next_try),
577 (retry->expired)? "*" : "");
581 wait = (dbdata_wait *)value;
582 printf("%s ", keybuffer);
584 name[MESSAGE_ID_LENGTH] = 0;
586 if (wait->count > WAIT_NAME_MAX)
589 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
590 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
591 wait->count = WAIT_NAME_MAX;
592 yield = count_bad = 1;
594 for (i = 1; i <= wait->count; i++)
596 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
597 if (count_bad && name[0] == 0) break;
598 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
599 Ustrspn(name, "0123456789"
600 "abcdefghijklmnopqrstuvwxyz"
601 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
605 "**** Data for %s corrupted: bad character in message id\n",
607 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
608 fprintf(stderr, "%02x ", name[j]);
609 fprintf(stderr, "\n");
614 t += MESSAGE_ID_LENGTH;
620 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
625 callout = (dbdata_callout_cache *)value;
627 /* New-style address record */
629 if (length == sizeof(dbdata_callout_cache_address))
631 printf("%s %s callout=%s\n",
632 print_time(((dbdata_generic *)value)->time_stamp),
634 print_cache(callout->result));
637 /* New-style domain record */
639 else if (length == sizeof(dbdata_callout_cache))
641 printf("%s %s callout=%s postmaster=%s",
642 print_time(((dbdata_generic *)value)->time_stamp),
644 print_cache(callout->result),
645 print_cache(callout->postmaster_result));
646 if (callout->postmaster_result != ccache_unknown)
647 printf(" (%s)", print_time(callout->postmaster_stamp));
648 printf(" random=%s", print_cache(callout->random_result));
649 if (callout->random_result != ccache_unknown)
650 printf(" (%s)", print_time(callout->random_stamp));
654 /* Old-style domain record, without separate timestamps. This code can
655 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
659 printf("%s %s callout=%s postmaster=%s random=%s\n",
660 print_time(((dbdata_generic *)value)->time_stamp),
662 print_cache(callout->result),
663 print_cache(callout->postmaster_result),
664 print_cache(callout->random_result));
670 ratelimit = (dbdata_ratelimit *)value;
672 printf("%s.%06d rate: %10.3f key: %s\n",
673 print_time(ratelimit->time_stamp), ratelimit->time_usec,
674 ratelimit->rate, keybuffer);
680 key = dbfn_scan(dbm, FALSE, &cursor);
687 #endif /* EXIM_DUMPDB */
693 /*************************************************
694 * The exim_fixdb main program *
695 *************************************************/
697 /* In order not to hold the database lock any longer than is necessary, each
698 operation on the database uses a separate open/close call. This is expensive,
699 but then using this utility is not expected to be very common. Its main use is
700 to provide a way of patching up hints databases in order to run tests.
705 This causes the data from the given record to be displayed, or "not found"
706 to be output. Note that in the retry database, destination names are
707 preceded by R: or T: for router or transport retry info.
710 This causes the given record to be deleted or "not found" to be output.
712 (3) <record name> <field number> <value>
713 This sets the given value into the given field, identified by a number
714 which is output by the display command. Not all types of record can
718 This exits from exim_fixdb.
720 If the record name is omitted from (2) or (3), the previously used record name
724 int main(int argc, char **cargv)
727 uschar **argv = USS cargv;
730 void *reset_point = store_get(0);
732 name[0] = 0; /* No name set */
734 /* Sort out the database type, verify what we are working on and then process
737 dbdata_type = check_args(argc, argv, US"fixdb", US"");
738 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
747 dbdata_callout_cache *callout;
748 dbdata_ratelimit *ratelimit;
751 uschar field[256], value[256];
753 store_reset(reset_point);
756 if (Ufgets(buffer, 256, stdin) == NULL) break;
758 buffer[Ustrlen(buffer)-1] = 0;
759 field[0] = value[0] = 0;
761 /* If the buffer contains just one digit, or just consists of "d", use the
762 previous name for an update. */
764 if ((isdigit((uschar)buffer[0]) && !isdigit((uschar)buffer[1])) ||
765 Ustrcmp(buffer, "d") == 0)
769 printf("No previous record name is set\n");
772 sscanf(CS buffer, "%s %s", field, value);
777 sscanf(CS buffer, "%s %s %s", name, field, value);
780 /* Handle an update request */
785 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
786 if (dbm == NULL) continue;
788 if (Ustrcmp(field, "d") == 0)
790 if (value[0] != 0) printf("unexpected value after \"d\"\n");
791 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
792 "not found" : "deleted");
797 else if (isdigit((uschar)field[0]))
799 int fieldno = Uatoi(field);
802 printf("value missing\n");
808 record = dbfn_read_with_length(dbm, name, &oldlength);
809 if (record == NULL) printf("not found\n"); else
812 int length = 0; /* Stops compiler warning */
817 retry = (dbdata_retry *)record;
818 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
823 retry->basic_errno = Uatoi(value);
827 retry->more_errno = Uatoi(value);
831 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
832 else printf("bad time value\n");
836 if ((tt = read_time(value)) > 0) retry->last_try = tt;
837 else printf("bad time value\n");
841 if ((tt = read_time(value)) > 0) retry->next_try = tt;
842 else printf("bad time value\n");
846 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
847 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
848 else printf("\"yes\" or \"no\" expected=n");
852 printf("unknown field number\n");
859 printf("Can't change contents of wait database record\n");
863 printf("Can't change contents of misc database record\n");
867 callout = (dbdata_callout_cache *)record;
868 length = sizeof(dbdata_callout_cache);
872 callout->result = Uatoi(value);
876 callout->postmaster_result = Uatoi(value);
880 callout->random_result = Uatoi(value);
884 printf("unknown field number\n");
891 ratelimit = (dbdata_ratelimit *)value;
895 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
896 else printf("bad time value\n");
900 ratelimit->time_usec = Uatoi(value);
903 ratelimit->rate = Ustrtod(value, NULL);
907 printf("unknown field number\n");
914 dbfn_write(dbm, name, record, length);
921 printf("field number or d expected\n");
926 if (!verify) continue;
929 /* The "name" q causes an exit */
931 else if (Ustrcmp(name, "q") == 0) return 0;
933 /* Handle a read request, or verify after an update. */
935 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
936 if (dbm == NULL) continue;
938 record = dbfn_read_with_length(dbm, name, &oldlength);
941 printf("record %s not found\n", name);
947 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
951 retry = (dbdata_retry *)record;
952 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
953 printf("1 extra data: %d\n", retry->more_errno);
954 printf("2 first failed: %s\n", print_time(retry->first_failed));
955 printf("3 last try: %s\n", print_time(retry->last_try));
956 printf("4 next try: %s\n", print_time(retry->next_try));
957 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
961 wait = (dbdata_wait *)record;
963 printf("Sequence: %d\n", wait->sequence);
964 if (wait->count > WAIT_NAME_MAX)
966 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
967 wait->count, WAIT_NAME_MAX);
968 wait->count = WAIT_NAME_MAX;
971 for (i = 1; i <= wait->count; i++)
973 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
974 value[MESSAGE_ID_LENGTH] = 0;
975 if (count_bad && value[0] == 0) break;
976 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
977 Ustrspn(value, "0123456789"
978 "abcdefghijklmnopqrstuvwxyz"
979 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
982 printf("\n**** Data corrupted: bad character in message id ****\n");
983 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
984 printf("%02x ", value[j]);
988 printf("%s ", value);
989 t += MESSAGE_ID_LENGTH;
998 callout = (dbdata_callout_cache *)record;
999 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1001 if (oldlength > sizeof(dbdata_callout_cache_address))
1003 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1004 callout->postmaster_result);
1005 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1006 callout->random_result);
1010 case type_ratelimit:
1011 ratelimit = (dbdata_ratelimit *)value;
1012 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1013 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1014 printf("2 sender rate: % .3f\n", ratelimit->rate);
1019 /* The database is closed after each request */
1028 #endif /* EXIM_FIXDB */
1033 /*************************************************
1034 * The exim_tidydb main program *
1035 *************************************************/
1038 /* Utility program to tidy the contents of an exim database file. There is one
1041 -t <time> expiry time for old records - default 30 days
1043 For backwards compatibility, an -f option is recognized and ignored. (It used
1044 to request a "full" tidy. This version always does the whole job.) */
1047 typedef struct key_item {
1048 struct key_item *next;
1053 int main(int argc, char **cargv)
1055 struct stat statbuf;
1056 int maxkeep = 30 * 24 * 60 * 60;
1057 int dbdata_type, i, oldest, path_len;
1058 key_item *keychain = NULL;
1062 EXIM_CURSOR *cursor;
1063 uschar **argv = USS cargv;
1067 /* Scan the options */
1069 for (i = 1; i < argc; i++)
1071 if (argv[i][0] != '-') break;
1072 if (Ustrcmp(argv[i], "-f") == 0) continue;
1073 if (Ustrcmp(argv[i], "-t") == 0)
1081 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1082 (void)sscanf(CS s, "%d%n", &value, &count);
1086 case 'w': value *= 7;
1087 case 'd': value *= 24;
1088 case 'h': value *= 60;
1089 case 'm': value *= 60;
1092 default: usage(US"tidydb", US" [-t <time>]");
1097 else usage(US"tidydb", US" [-t <time>]");
1100 /* Adjust argument values and process arguments */
1105 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1107 /* Compute the oldest keep time, verify what we are doing, and open the
1110 oldest = time(NULL) - maxkeep;
1111 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1113 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
1114 if (dbm == NULL) exit(1);
1116 /* Prepare for building file names */
1118 sprintf(CS buffer, "%s/input/", argv[1]);
1119 path_len = Ustrlen(buffer);
1122 /* It appears, by experiment, that it is a bad idea to make changes
1123 to the file while scanning it. Pity the man page doesn't warn you about that.
1124 Therefore, we scan and build a list of all the keys. Then we use that to
1125 read the records and possibly update them. */
1127 key = dbfn_scan(dbm, TRUE, &cursor);
1130 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1133 Ustrcpy(k->key, key);
1134 key = dbfn_scan(dbm, FALSE, &cursor);
1137 /* Now scan the collected keys and operate on the records, resetting
1138 the store each time round. */
1140 reset_point = store_get(0);
1142 while (keychain != NULL)
1144 dbdata_generic *value;
1146 store_reset(reset_point);
1147 key = keychain->key;
1148 keychain = keychain->next;
1149 value = dbfn_read_with_length(dbm, key, NULL);
1151 /* A continuation record may have been deleted or renamed already, so
1152 non-existence is not serious. */
1154 if (value == NULL) continue;
1156 /* Delete if too old */
1158 if (value->time_stamp < oldest)
1160 printf("deleted %s (too old)\n", key);
1161 dbfn_delete(dbm, key);
1165 /* Do database-specific tidying for wait databases, and message-
1166 specific tidying for the retry database. */
1168 if (dbdata_type == type_wait)
1170 dbdata_wait *wait = (dbdata_wait *)value;
1171 BOOL update = FALSE;
1173 /* Leave corrupt records alone */
1175 if (wait->count > WAIT_NAME_MAX)
1177 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1178 key, wait->count, wait->count, WAIT_NAME_MAX);
1182 /* Loop for renamed continuation records. For each message id,
1183 check to see if the message exists, and if not, remove its entry
1184 from the record. Because of the possibility of split input directories,
1185 we must look in both possible places for a -D file. */
1190 int length = wait->count * MESSAGE_ID_LENGTH;
1192 for (offset = length - MESSAGE_ID_LENGTH;
1193 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1195 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1196 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1198 if (Ustat(buffer, &statbuf) != 0)
1200 buffer[path_len] = wait->text[offset+5];
1201 buffer[path_len+1] = '/';
1202 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1203 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1205 if (Ustat(buffer, &statbuf) != 0)
1207 int left = length - offset - MESSAGE_ID_LENGTH;
1208 if (left > 0) Ustrncpy(wait->text + offset,
1209 wait->text + offset + MESSAGE_ID_LENGTH, left);
1211 length -= MESSAGE_ID_LENGTH;
1217 /* If record is empty and the main record, either delete it or rename
1218 the next continuation, repeating if that is also empty. */
1220 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1222 while (wait->count == 0 && wait->sequence > 0)
1225 dbdata_generic *newvalue;
1226 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1227 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1228 if (newvalue != NULL)
1231 wait = (dbdata_wait *)newvalue;
1232 dbfn_delete(dbm, newkey);
1233 printf("renamed %s\n", newkey);
1236 else wait->sequence--;
1239 /* If we have ended up with an empty main record, delete it
1240 and break the loop. Otherwise the new record will be scanned. */
1242 if (wait->count == 0 && wait->sequence == 0)
1244 dbfn_delete(dbm, key);
1245 printf("deleted %s (empty)\n", key);
1251 /* If not an empty main record, break the loop */
1256 /* Re-write the record if required */
1260 printf("updated %s\n", key);
1261 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1262 wait->count * MESSAGE_ID_LENGTH);
1266 /* If a retry record's key ends with a message-id, check that that message
1267 still exists; if not, remove this record. */
1269 else if (dbdata_type == type_retry)
1272 int len = Ustrlen(key);
1274 if (len < MESSAGE_ID_LENGTH + 1) continue;
1275 id = key + len - MESSAGE_ID_LENGTH - 1;
1276 if (*id++ != ':') continue;
1278 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1280 if (i == 6 || i == 13)
1281 { if (id[i] != '-') break; }
1283 { if (!isalnum(id[i])) break; }
1285 if (i < MESSAGE_ID_LENGTH) continue;
1287 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1288 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1290 if (Ustat(buffer, &statbuf) != 0)
1292 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1293 if (Ustat(buffer, &statbuf) != 0)
1295 dbfn_delete(dbm, key);
1296 printf("deleted %s (no message)\n", key);
1303 printf("Tidying complete\n");
1307 #endif /* EXIM_TIDYDB */
1309 /* End of exim_dbutil.c */