1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.10 2006/02/07 11:19:00 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2006 */
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. The API changed at release 4.3. */
83 #if defined(USE_DB) && defined(DB_VERSION_STRING)
85 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
86 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
90 dbfn_bdb_error_callback(const char *pfx, char *msg)
94 printf("Berkeley DB error: %s\n", msg);
100 /*************************************************
102 *************************************************/
104 static int sigalrm_seen;
107 sigalrm_handler(int sig)
109 sig = sig; /* Keep picky compilers happy */
115 /*************************************************
116 * Output usage message and exit *
117 *************************************************/
120 usage(uschar *name, uschar *options)
122 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
123 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
129 /*************************************************
130 * Sort out the command arguments *
131 *************************************************/
133 /* This function checks that there are exactly 2 arguments, and checks the
134 second of them to be sure it is a known database name. */
137 check_args(int argc, uschar **argv, uschar *name, uschar *options)
141 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
142 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
143 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
144 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
145 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
147 usage(name, options);
148 return -1; /* Never obeyed */
153 /*************************************************
154 * Handle attempts to write the log *
155 *************************************************/
157 /* The message gets written to stderr when log_write() is called from a
158 utility. The message always gets '\n' added on the end of it. These calls come
159 from modules such as store.c when things go drastically wrong (e.g. malloc()
160 failing). In normal use they won't get obeyed.
163 selector not relevant when running a utility
164 flags not relevant when running a utility
165 format a printf() format
166 ... arguments for format
172 log_write(unsigned int selector, int flags, char *format, ...)
175 va_start(ap, format);
176 vfprintf(stderr, format, ap);
177 fprintf(stderr, "\n");
179 selector = selector; /* Keep picky compilers happy */
185 /*************************************************
186 * Format a time value for printing *
187 *************************************************/
189 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
194 struct tm *tmstr = localtime(&t);
195 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
201 /*************************************************
202 * Format a cache value for printing *
203 *************************************************/
206 print_cache(int value)
208 return (value == ccache_accept)? US"accept" :
209 (value == ccache_reject)? US"reject" :
215 /*************************************************
217 *************************************************/
225 time_t now = time(NULL);
226 struct tm *tm = localtime(&now);
231 for (t = s + Ustrlen(s) - 1; t >= s; t--)
233 if (*t == ':') continue;
234 if (!isdigit((uschar)*t)) return -1;
239 if (!isdigit((uschar)*t)) return -1;
240 value = value + (*t - '0')*10;
245 case 0: tm->tm_min = value; break;
246 case 1: tm->tm_hour = value; break;
247 case 2: tm->tm_mday = value; break;
248 case 3: tm->tm_mon = value - 1; break;
249 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
256 #endif /* EXIM_FIXDB */
260 /*************************************************
261 * Open and lock a database file *
262 *************************************************/
264 /* This is a cut-down version from the function in dbfn.h that Exim itself
265 uses. We assume the database exists, and therefore give up if we cannot open
269 spool The spool directory
270 name The single-component name of one of Exim's database files.
271 flags O_RDONLY or O_RDWR
272 dbblock Points to an open_db block to be filled in.
274 Returns: NULL if the open failed, or the locking failed.
275 On success, dbblock is returned. This contains the dbm pointer and
276 the fd of the locked lock file.
280 dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
283 struct flock lock_data;
284 BOOL read_only = flags == O_RDONLY;
287 /* The first thing to do is to open a separate file on which to lock. This
288 ensures that Exim has exclusive use of the database before it even tries to
289 open it. If there is a database, there should be a lock file in existence. */
291 sprintf(CS buffer, "%s/db/%s.lockfile", spool, name);
293 dbblock->lockfd = Uopen(buffer, flags, 0);
294 if (dbblock->lockfd < 0)
296 printf("** Failed to open database lock file %s: %s\n", buffer,
301 /* Now we must get a lock on the opened lock file; do this with a blocking
302 lock that times out. */
304 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
305 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
307 sigalrm_seen = FALSE;
308 os_non_restarting_signal(SIGALRM, sigalrm_handler);
309 alarm(EXIMDB_LOCK_TIMEOUT);
310 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
313 if (sigalrm_seen) errno = ETIMEDOUT;
316 printf("** Failed to get %s lock for %s: %s",
317 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
318 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
319 (void)close(dbblock->lockfd);
323 /* At this point we have an opened and locked separate lock file, that is,
324 exclusive access to the database, so we can go ahead and open it. */
326 sprintf(CS buffer, "%s/db/%s", spool, name);
327 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
329 if (dbblock->dbptr == NULL)
331 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
332 read_only? "reading" : "writing", strerror(errno),
334 " (or Berkeley DB error while opening)"
339 (void)close(dbblock->lockfd);
349 /*************************************************
350 * Unlock and close a database file *
351 *************************************************/
353 /* Closing a file automatically unlocks it, so after closing the database, just
356 Argument: a pointer to an open database block
361 dbfn_close(open_db *dbblock)
363 EXIM_DBCLOSE(dbblock->dbptr);
364 (void)close(dbblock->lockfd);
370 /*************************************************
371 * Read from database file *
372 *************************************************/
374 /* Passing back the pointer unchanged is useless, because there is no guarantee
375 of alignment. Since all the records used by Exim need to be properly aligned to
376 pick out the timestamps, etc., do the copying centrally here.
379 dbblock a pointer to an open database block
380 key the key of the record to be read
381 length where to put the length (or NULL if length not wanted)
383 Returns: a pointer to the retrieved record, or
384 NULL if the record is not found
388 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
391 EXIM_DATUM key_datum, result_datum;
393 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
394 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
395 EXIM_DATUM_DATA(key_datum) = CS key;
396 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
398 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
400 yield = store_get(EXIM_DATUM_SIZE(result_datum));
401 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
402 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
404 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
410 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
412 /*************************************************
413 * Write to database file *
414 *************************************************/
418 dbblock a pointer to an open database block
419 key the key of the record to be written
420 ptr a pointer to the record to be written
421 length the length of the record to be written
423 Returns: the yield of the underlying dbm or db "write" function. If this
424 is dbm, the value is zero for OK.
428 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
430 EXIM_DATUM key_datum, value_datum;
431 dbdata_generic *gptr = (dbdata_generic *)ptr;
432 gptr->time_stamp = time(NULL);
434 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
435 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
436 EXIM_DATUM_DATA(key_datum) = CS key;
437 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
438 EXIM_DATUM_DATA(value_datum) = CS ptr;
439 EXIM_DATUM_SIZE(value_datum) = length;
440 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
445 /*************************************************
446 * Delete record from database file *
447 *************************************************/
451 dbblock a pointer to an open database block
452 key the key of the record to be deleted
454 Returns: the yield of the underlying dbm or db "delete" function.
458 dbfn_delete(open_db *dbblock, uschar *key)
460 EXIM_DATUM key_datum;
461 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
462 EXIM_DATUM_DATA(key_datum) = CS key;
463 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
464 return EXIM_DBDEL(dbblock->dbptr, key_datum);
467 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
471 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
472 /*************************************************
473 * Scan the keys of a database file *
474 *************************************************/
478 dbblock a pointer to an open database block
479 start TRUE if starting a new scan
480 FALSE if continuing with the current scan
481 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
482 that use the notion of a cursor
484 Returns: the next record from the file, or
485 NULL if there are no more
489 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
491 EXIM_DATUM key_datum, value_datum;
493 value_datum = value_datum; /* dummy; not all db libraries use this */
495 /* Some dbm require an initialization */
497 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
499 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
500 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
502 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
503 US EXIM_DATUM_DATA(key_datum) : NULL;
505 /* Some dbm require a termination */
507 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
510 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
515 /*************************************************
516 * The exim_dumpdb main program *
517 *************************************************/
520 main(int argc, char **cargv)
527 uschar **argv = USS cargv;
529 uschar keybuffer[1024];
531 /* Check the arguments, and open the database */
533 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
534 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
535 if (dbm == NULL) exit(1);
537 /* Scan the file, formatting the information for each entry. Note
538 that data is returned in a malloc'ed block, in order that it be
539 correctly aligned. */
541 key = dbfn_scan(dbm, TRUE, &cursor);
546 dbdata_callout_cache *callout;
547 dbdata_ratelimit *ratelimit;
551 uschar name[MESSAGE_ID_LENGTH + 1];
554 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
555 which might change. */
557 if (Ustrlen(key) > sizeof(keybuffer) - 1)
559 printf("**** Overlong key encountered: %s\n", key);
562 Ustrcpy(keybuffer, key);
563 value = dbfn_read_with_length(dbm, keybuffer, &length);
566 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
567 "was not found in the file - something is wrong!\n",
571 /* Note: don't use print_time more than once in one statement, since
572 it uses a single buffer. */
577 retry = (dbdata_retry *)value;
578 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
579 retry->more_errno, retry->text,
580 print_time(retry->first_failed));
581 printf("%s ", print_time(retry->last_try));
582 printf("%s %s\n", print_time(retry->next_try),
583 (retry->expired)? "*" : "");
587 wait = (dbdata_wait *)value;
588 printf("%s ", keybuffer);
590 name[MESSAGE_ID_LENGTH] = 0;
592 if (wait->count > WAIT_NAME_MAX)
595 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
596 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
597 wait->count = WAIT_NAME_MAX;
598 yield = count_bad = 1;
600 for (i = 1; i <= wait->count; i++)
602 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
603 if (count_bad && name[0] == 0) break;
604 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
605 Ustrspn(name, "0123456789"
606 "abcdefghijklmnopqrstuvwxyz"
607 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
611 "**** Data for %s corrupted: bad character in message id\n",
613 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
614 fprintf(stderr, "%02x ", name[j]);
615 fprintf(stderr, "\n");
620 t += MESSAGE_ID_LENGTH;
626 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
631 callout = (dbdata_callout_cache *)value;
633 /* New-style address record */
635 if (length == sizeof(dbdata_callout_cache_address))
637 printf("%s %s callout=%s\n",
638 print_time(((dbdata_generic *)value)->time_stamp),
640 print_cache(callout->result));
643 /* New-style domain record */
645 else if (length == sizeof(dbdata_callout_cache))
647 printf("%s %s callout=%s postmaster=%s",
648 print_time(((dbdata_generic *)value)->time_stamp),
650 print_cache(callout->result),
651 print_cache(callout->postmaster_result));
652 if (callout->postmaster_result != ccache_unknown)
653 printf(" (%s)", print_time(callout->postmaster_stamp));
654 printf(" random=%s", print_cache(callout->random_result));
655 if (callout->random_result != ccache_unknown)
656 printf(" (%s)", print_time(callout->random_stamp));
660 /* Old-style domain record, without separate timestamps. This code can
661 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
665 printf("%s %s callout=%s postmaster=%s random=%s\n",
666 print_time(((dbdata_generic *)value)->time_stamp),
668 print_cache(callout->result),
669 print_cache(callout->postmaster_result),
670 print_cache(callout->random_result));
676 ratelimit = (dbdata_ratelimit *)value;
678 printf("%s.%06d rate: %10.3f key: %s\n",
679 print_time(ratelimit->time_stamp), ratelimit->time_usec,
680 ratelimit->rate, keybuffer);
686 key = dbfn_scan(dbm, FALSE, &cursor);
693 #endif /* EXIM_DUMPDB */
699 /*************************************************
700 * The exim_fixdb main program *
701 *************************************************/
703 /* In order not to hold the database lock any longer than is necessary, each
704 operation on the database uses a separate open/close call. This is expensive,
705 but then using this utility is not expected to be very common. Its main use is
706 to provide a way of patching up hints databases in order to run tests.
711 This causes the data from the given record to be displayed, or "not found"
712 to be output. Note that in the retry database, destination names are
713 preceded by R: or T: for router or transport retry info.
716 This causes the given record to be deleted or "not found" to be output.
718 (3) <record name> <field number> <value>
719 This sets the given value into the given field, identified by a number
720 which is output by the display command. Not all types of record can
724 This exits from exim_fixdb.
726 If the record name is omitted from (2) or (3), the previously used record name
730 int main(int argc, char **cargv)
733 uschar **argv = USS cargv;
736 void *reset_point = store_get(0);
738 name[0] = 0; /* No name set */
740 /* Sort out the database type, verify what we are working on and then process
743 dbdata_type = check_args(argc, argv, US"fixdb", US"");
744 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
753 dbdata_callout_cache *callout;
754 dbdata_ratelimit *ratelimit;
757 uschar field[256], value[256];
759 store_reset(reset_point);
762 if (Ufgets(buffer, 256, stdin) == NULL) break;
764 buffer[Ustrlen(buffer)-1] = 0;
765 field[0] = value[0] = 0;
767 /* If the buffer contains just one digit, or just consists of "d", use the
768 previous name for an update. */
770 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
771 || Ustrcmp(buffer, "d") == 0)
775 printf("No previous record name is set\n");
778 (void)sscanf(CS buffer, "%s %s", field, value);
783 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
786 /* Handle an update request */
791 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
792 if (dbm == NULL) continue;
794 if (Ustrcmp(field, "d") == 0)
796 if (value[0] != 0) printf("unexpected value after \"d\"\n");
797 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
798 "not found" : "deleted");
803 else if (isdigit((uschar)field[0]))
805 int fieldno = Uatoi(field);
808 printf("value missing\n");
814 record = dbfn_read_with_length(dbm, name, &oldlength);
815 if (record == NULL) printf("not found\n"); else
818 int length = 0; /* Stops compiler warning */
823 retry = (dbdata_retry *)record;
824 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
829 retry->basic_errno = Uatoi(value);
833 retry->more_errno = Uatoi(value);
837 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
838 else printf("bad time value\n");
842 if ((tt = read_time(value)) > 0) retry->last_try = tt;
843 else printf("bad time value\n");
847 if ((tt = read_time(value)) > 0) retry->next_try = tt;
848 else printf("bad time value\n");
852 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
853 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
854 else printf("\"yes\" or \"no\" expected=n");
858 printf("unknown field number\n");
865 printf("Can't change contents of wait database record\n");
869 printf("Can't change contents of misc database record\n");
873 callout = (dbdata_callout_cache *)record;
874 length = sizeof(dbdata_callout_cache);
878 callout->result = Uatoi(value);
882 callout->postmaster_result = Uatoi(value);
886 callout->random_result = Uatoi(value);
890 printf("unknown field number\n");
897 ratelimit = (dbdata_ratelimit *)record;
898 length = sizeof(dbdata_ratelimit);
902 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
903 else printf("bad time value\n");
907 ratelimit->time_usec = Uatoi(value);
911 ratelimit->rate = Ustrtod(value, NULL);
915 printf("unknown field number\n");
922 dbfn_write(dbm, name, record, length);
929 printf("field number or d expected\n");
934 if (!verify) continue;
937 /* The "name" q causes an exit */
939 else if (Ustrcmp(name, "q") == 0) return 0;
941 /* Handle a read request, or verify after an update. */
943 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
944 if (dbm == NULL) continue;
946 record = dbfn_read_with_length(dbm, name, &oldlength);
949 printf("record %s not found\n", name);
955 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
959 retry = (dbdata_retry *)record;
960 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
961 printf("1 extra data: %d\n", retry->more_errno);
962 printf("2 first failed: %s\n", print_time(retry->first_failed));
963 printf("3 last try: %s\n", print_time(retry->last_try));
964 printf("4 next try: %s\n", print_time(retry->next_try));
965 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
969 wait = (dbdata_wait *)record;
971 printf("Sequence: %d\n", wait->sequence);
972 if (wait->count > WAIT_NAME_MAX)
974 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
975 wait->count, WAIT_NAME_MAX);
976 wait->count = WAIT_NAME_MAX;
979 for (i = 1; i <= wait->count; i++)
981 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
982 value[MESSAGE_ID_LENGTH] = 0;
983 if (count_bad && value[0] == 0) break;
984 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
985 Ustrspn(value, "0123456789"
986 "abcdefghijklmnopqrstuvwxyz"
987 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
990 printf("\n**** Data corrupted: bad character in message id ****\n");
991 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
992 printf("%02x ", value[j]);
996 printf("%s ", value);
997 t += MESSAGE_ID_LENGTH;
1006 callout = (dbdata_callout_cache *)record;
1007 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1009 if (oldlength > sizeof(dbdata_callout_cache_address))
1011 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1012 callout->postmaster_result);
1013 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1014 callout->random_result);
1018 case type_ratelimit:
1019 ratelimit = (dbdata_ratelimit *)record;
1020 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1021 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1022 printf("2 sender rate: % .3f\n", ratelimit->rate);
1027 /* The database is closed after each request */
1036 #endif /* EXIM_FIXDB */
1041 /*************************************************
1042 * The exim_tidydb main program *
1043 *************************************************/
1046 /* Utility program to tidy the contents of an exim database file. There is one
1049 -t <time> expiry time for old records - default 30 days
1051 For backwards compatibility, an -f option is recognized and ignored. (It used
1052 to request a "full" tidy. This version always does the whole job.) */
1055 typedef struct key_item {
1056 struct key_item *next;
1061 int main(int argc, char **cargv)
1063 struct stat statbuf;
1064 int maxkeep = 30 * 24 * 60 * 60;
1065 int dbdata_type, i, oldest, path_len;
1066 key_item *keychain = NULL;
1070 EXIM_CURSOR *cursor;
1071 uschar **argv = USS cargv;
1075 /* Scan the options */
1077 for (i = 1; i < argc; i++)
1079 if (argv[i][0] != '-') break;
1080 if (Ustrcmp(argv[i], "-f") == 0) continue;
1081 if (Ustrcmp(argv[i], "-t") == 0)
1089 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1090 (void)sscanf(CS s, "%d%n", &value, &count);
1094 case 'w': value *= 7;
1095 case 'd': value *= 24;
1096 case 'h': value *= 60;
1097 case 'm': value *= 60;
1100 default: usage(US"tidydb", US" [-t <time>]");
1105 else usage(US"tidydb", US" [-t <time>]");
1108 /* Adjust argument values and process arguments */
1113 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1115 /* Compute the oldest keep time, verify what we are doing, and open the
1118 oldest = time(NULL) - maxkeep;
1119 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1121 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
1122 if (dbm == NULL) exit(1);
1124 /* Prepare for building file names */
1126 sprintf(CS buffer, "%s/input/", argv[1]);
1127 path_len = Ustrlen(buffer);
1130 /* It appears, by experiment, that it is a bad idea to make changes
1131 to the file while scanning it. Pity the man page doesn't warn you about that.
1132 Therefore, we scan and build a list of all the keys. Then we use that to
1133 read the records and possibly update them. */
1135 key = dbfn_scan(dbm, TRUE, &cursor);
1138 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1141 Ustrcpy(k->key, key);
1142 key = dbfn_scan(dbm, FALSE, &cursor);
1145 /* Now scan the collected keys and operate on the records, resetting
1146 the store each time round. */
1148 reset_point = store_get(0);
1150 while (keychain != NULL)
1152 dbdata_generic *value;
1154 store_reset(reset_point);
1155 key = keychain->key;
1156 keychain = keychain->next;
1157 value = dbfn_read_with_length(dbm, key, NULL);
1159 /* A continuation record may have been deleted or renamed already, so
1160 non-existence is not serious. */
1162 if (value == NULL) continue;
1164 /* Delete if too old */
1166 if (value->time_stamp < oldest)
1168 printf("deleted %s (too old)\n", key);
1169 dbfn_delete(dbm, key);
1173 /* Do database-specific tidying for wait databases, and message-
1174 specific tidying for the retry database. */
1176 if (dbdata_type == type_wait)
1178 dbdata_wait *wait = (dbdata_wait *)value;
1179 BOOL update = FALSE;
1181 /* Leave corrupt records alone */
1183 if (wait->count > WAIT_NAME_MAX)
1185 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1186 key, wait->count, wait->count, WAIT_NAME_MAX);
1190 /* Loop for renamed continuation records. For each message id,
1191 check to see if the message exists, and if not, remove its entry
1192 from the record. Because of the possibility of split input directories,
1193 we must look in both possible places for a -D file. */
1198 int length = wait->count * MESSAGE_ID_LENGTH;
1200 for (offset = length - MESSAGE_ID_LENGTH;
1201 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1203 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1204 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1206 if (Ustat(buffer, &statbuf) != 0)
1208 buffer[path_len] = wait->text[offset+5];
1209 buffer[path_len+1] = '/';
1210 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1211 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1213 if (Ustat(buffer, &statbuf) != 0)
1215 int left = length - offset - MESSAGE_ID_LENGTH;
1216 if (left > 0) Ustrncpy(wait->text + offset,
1217 wait->text + offset + MESSAGE_ID_LENGTH, left);
1219 length -= MESSAGE_ID_LENGTH;
1225 /* If record is empty and the main record, either delete it or rename
1226 the next continuation, repeating if that is also empty. */
1228 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1230 while (wait->count == 0 && wait->sequence > 0)
1233 dbdata_generic *newvalue;
1234 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1235 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1236 if (newvalue != NULL)
1239 wait = (dbdata_wait *)newvalue;
1240 dbfn_delete(dbm, newkey);
1241 printf("renamed %s\n", newkey);
1244 else wait->sequence--;
1247 /* If we have ended up with an empty main record, delete it
1248 and break the loop. Otherwise the new record will be scanned. */
1250 if (wait->count == 0 && wait->sequence == 0)
1252 dbfn_delete(dbm, key);
1253 printf("deleted %s (empty)\n", key);
1259 /* If not an empty main record, break the loop */
1264 /* Re-write the record if required */
1268 printf("updated %s\n", key);
1269 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1270 wait->count * MESSAGE_ID_LENGTH);
1274 /* If a retry record's key ends with a message-id, check that that message
1275 still exists; if not, remove this record. */
1277 else if (dbdata_type == type_retry)
1280 int len = Ustrlen(key);
1282 if (len < MESSAGE_ID_LENGTH + 1) continue;
1283 id = key + len - MESSAGE_ID_LENGTH - 1;
1284 if (*id++ != ':') continue;
1286 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1288 if (i == 6 || i == 13)
1289 { if (id[i] != '-') break; }
1291 { if (!isalnum(id[i])) break; }
1293 if (i < MESSAGE_ID_LENGTH) continue;
1295 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1296 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1298 if (Ustat(buffer, &statbuf) != 0)
1300 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1301 if (Ustat(buffer, &statbuf) != 0)
1303 dbfn_delete(dbm, key);
1304 printf("deleted %s (no message)\n", key);
1311 printf("Tidying complete\n");
1315 #endif /* EXIM_TIDYDB */
1317 /* End of exim_dbutil.c */