1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.3 2005/01/04 10:00:42 ph10 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
74 /*************************************************
75 * Berkeley DB error callback *
76 *************************************************/
78 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
79 errors. This should help with debugging strange DB problems, e.g. getting "File
80 exists" when you try to open a db file. */
82 #if defined(USE_DB) && defined(DB_VERSION_STRING)
84 dbfn_bdb_error_callback(const char *pfx, char *msg)
87 printf("Berkeley DB error: %s\n", msg);
93 /*************************************************
95 *************************************************/
97 static int sigalrm_seen;
100 sigalrm_handler(int sig)
102 sig = sig; /* Keep picky compilers happy */
108 /*************************************************
109 * Output usage message and exit *
110 *************************************************/
113 usage(uschar *name, uschar *options)
115 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
116 printf(" <database-name> = retry | misc | wait-<transport-name> | callout\n");
122 /*************************************************
123 * Sort out the command arguments *
124 *************************************************/
126 /* This function checks that there are exactly 2 arguments, and checks the
127 second of them to be sure it is a known database name. */
130 check_args(int argc, uschar **argv, uschar *name, uschar *options)
134 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
135 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
136 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
137 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
139 usage(name, options);
140 return -1; /* Never obeyed */
145 /*************************************************
146 * Handle attempts to write the log *
147 *************************************************/
149 /* The message gets written to stderr when log_write() is called from a
150 utility. The message always gets '\n' added on the end of it. These calls come
151 from modules such as store.c when things go drastically wrong (e.g. malloc()
152 failing). In normal use they won't get obeyed.
155 selector not relevant when running a utility
156 flags not relevant when running a utility
157 format a printf() format
158 ... arguments for format
164 log_write(unsigned int selector, int flags, char *format, ...)
167 va_start(ap, format);
168 vfprintf(stderr, format, ap);
169 fprintf(stderr, "\n");
171 selector = selector; /* Keep picky compilers happy */
177 /*************************************************
178 * Format a time value for printing *
179 *************************************************/
181 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
186 struct tm *tmstr = localtime(&t);
187 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
193 /*************************************************
194 * Format a cache value for printing *
195 *************************************************/
198 print_cache(int value)
200 return (value == ccache_accept)? US"accept" :
201 (value == ccache_reject)? US"reject" :
207 /*************************************************
209 *************************************************/
217 time_t now = time(NULL);
218 struct tm *tm = localtime(&now);
223 for (t = s + Ustrlen(s) - 1; t >= s; t--)
225 if (*t == ':') continue;
226 if (!isdigit((uschar)*t)) return -1;
231 if (!isdigit((uschar)*t)) return -1;
232 value = value + (*t - '0')*10;
237 case 0: tm->tm_min = value; break;
238 case 1: tm->tm_hour = value; break;
239 case 2: tm->tm_mday = value; break;
240 case 3: tm->tm_mon = value - 1; break;
241 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
248 #endif /* EXIM_FIXDB */
252 /*************************************************
253 * Open and lock a database file *
254 *************************************************/
256 /* This is a cut-down version from the function in dbfn.h that Exim itself
257 uses. We assume the database exists, and therefore give up if we cannot open
261 spool The spool directory
262 name The single-component name of one of Exim's database files.
263 flags O_RDONLY or O_RDWR
264 dbblock Points to an open_db block to be filled in.
266 Returns: NULL if the open failed, or the locking failed.
267 On success, dbblock is returned. This contains the dbm pointer and
268 the fd of the locked lock file.
272 dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
275 struct flock lock_data;
276 BOOL read_only = flags == O_RDONLY;
279 /* The first thing to do is to open a separate file on which to lock. This
280 ensures that Exim has exclusive use of the database before it even tries to
281 open it. If there is a database, there should be a lock file in existence. */
283 sprintf(CS buffer, "%s/db/%s.lockfile", spool, name);
285 dbblock->lockfd = Uopen(buffer, flags, 0);
286 if (dbblock->lockfd < 0)
288 printf("** Failed to open database lock file %s: %s\n", buffer,
293 /* Now we must get a lock on the opened lock file; do this with a blocking
294 lock that times out. */
296 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
297 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
299 sigalrm_seen = FALSE;
300 os_non_restarting_signal(SIGALRM, sigalrm_handler);
301 alarm(EXIMDB_LOCK_TIMEOUT);
302 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
305 if (sigalrm_seen) errno = ETIMEDOUT;
308 printf("** Failed to get %s lock for %s: %s",
309 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
310 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
311 close(dbblock->lockfd);
315 /* At this point we have an opened and locked separate lock file, that is,
316 exclusive access to the database, so we can go ahead and open it. */
318 sprintf(CS buffer, "%s/db/%s", spool, name);
319 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
321 if (dbblock->dbptr == NULL)
323 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
324 read_only? "reading" : "writing", strerror(errno),
326 " (or Berkeley DB error while opening)"
331 close(dbblock->lockfd);
341 /*************************************************
342 * Unlock and close a database file *
343 *************************************************/
345 /* Closing a file automatically unlocks it, so after closing the database, just
348 Argument: a pointer to an open database block
353 dbfn_close(open_db *dbblock)
355 EXIM_DBCLOSE(dbblock->dbptr);
356 close(dbblock->lockfd);
362 /*************************************************
363 * Read from database file *
364 *************************************************/
366 /* Passing back the pointer unchanged is useless, because there is no guarantee
367 of alignment. Since all the records used by Exim need to be properly aligned to
368 pick out the timestamps, etc., do the copying centrally here.
371 dbblock a pointer to an open database block
372 key the key of the record to be read
373 length where to put the length (or NULL if length not wanted)
375 Returns: a pointer to the retrieved record, or
376 NULL if the record is not found
380 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
383 EXIM_DATUM key_datum, result_datum;
385 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
386 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
387 EXIM_DATUM_DATA(key_datum) = CS key;
388 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
390 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
392 yield = store_get(EXIM_DATUM_SIZE(result_datum));
393 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
394 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
396 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
402 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
404 /*************************************************
405 * Write to database file *
406 *************************************************/
410 dbblock a pointer to an open database block
411 key the key of the record to be written
412 ptr a pointer to the record to be written
413 length the length of the record to be written
415 Returns: the yield of the underlying dbm or db "write" function. If this
416 is dbm, the value is zero for OK.
420 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
422 EXIM_DATUM key_datum, value_datum;
423 dbdata_generic *gptr = (dbdata_generic *)ptr;
424 gptr->time_stamp = time(NULL);
426 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
427 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
428 EXIM_DATUM_DATA(key_datum) = CS key;
429 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
430 EXIM_DATUM_DATA(value_datum) = CS ptr;
431 EXIM_DATUM_SIZE(value_datum) = length;
432 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
437 /*************************************************
438 * Delete record from database file *
439 *************************************************/
443 dbblock a pointer to an open database block
444 key the key of the record to be deleted
446 Returns: the yield of the underlying dbm or db "delete" function.
450 dbfn_delete(open_db *dbblock, uschar *key)
452 EXIM_DATUM key_datum;
453 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
454 EXIM_DATUM_DATA(key_datum) = CS key;
455 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
456 return EXIM_DBDEL(dbblock->dbptr, key_datum);
459 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
463 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
464 /*************************************************
465 * Scan the keys of a database file *
466 *************************************************/
470 dbblock a pointer to an open database block
471 start TRUE if starting a new scan
472 FALSE if continuing with the current scan
473 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
474 that use the notion of a cursor
476 Returns: the next record from the file, or
477 NULL if there are no more
481 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
483 EXIM_DATUM key_datum, value_datum;
485 value_datum = value_datum; /* dummy; not all db libraries use this */
487 /* Some dbm require an initialization */
489 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
491 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
492 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
494 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
495 US EXIM_DATUM_DATA(key_datum) : NULL;
497 /* Some dbm require a termination */
499 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
502 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
507 /*************************************************
508 * The exim_dumpdb main program *
509 *************************************************/
512 main(int argc, char **cargv)
519 uschar **argv = USS cargv;
521 uschar keybuffer[1024];
523 /* Check the arguments, and open the database */
525 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
526 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
527 if (dbm == NULL) exit(1);
529 /* Scan the file, formatting the information for each entry. Note
530 that data is returned in a malloc'ed block, in order that it be
531 correctly aligned. */
533 key = dbfn_scan(dbm, TRUE, &cursor);
538 dbdata_callout_cache *callout;
542 uschar name[MESSAGE_ID_LENGTH + 1];
545 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
546 which might change. */
548 if (Ustrlen(key) > sizeof(keybuffer) - 1)
550 printf("**** Overlong key encountered: %s\n", key);
553 Ustrcpy(keybuffer, key);
554 value = dbfn_read_with_length(dbm, keybuffer, &length);
557 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
558 "was not found in the file - something is wrong!\n",
562 /* Note: don't use print_time more than once in one statement, since
563 it uses a single buffer. */
568 retry = (dbdata_retry *)value;
569 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
570 retry->more_errno, retry->text,
571 print_time(retry->first_failed));
572 printf("%s ", print_time(retry->last_try));
573 printf("%s %s\n", print_time(retry->next_try),
574 (retry->expired)? "*" : "");
578 wait = (dbdata_wait *)value;
579 printf("%s ", keybuffer);
581 name[MESSAGE_ID_LENGTH] = 0;
583 if (wait->count > WAIT_NAME_MAX)
586 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
587 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
588 wait->count = WAIT_NAME_MAX;
589 yield = count_bad = 1;
591 for (i = 1; i <= wait->count; i++)
593 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
594 if (count_bad && name[0] == 0) break;
595 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
596 Ustrspn(name, "0123456789"
597 "abcdefghijklmnopqrstuvwxyz"
598 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
602 "**** Data for %s corrupted: bad character in message id\n",
604 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
605 fprintf(stderr, "%02x ", name[j]);
606 fprintf(stderr, "\n");
611 t += MESSAGE_ID_LENGTH;
617 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
622 callout = (dbdata_callout_cache *)value;
624 /* New-style address record */
626 if (length == sizeof(dbdata_callout_cache_address))
628 printf("%s %s callout=%s\n",
629 print_time(((dbdata_generic *)value)->time_stamp),
631 print_cache(callout->result));
634 /* New-style domain record */
636 else if (length == sizeof(dbdata_callout_cache))
638 printf("%s %s callout=%s postmaster=%s",
639 print_time(((dbdata_generic *)value)->time_stamp),
641 print_cache(callout->result),
642 print_cache(callout->postmaster_result));
643 if (callout->postmaster_result != ccache_unknown)
644 printf(" (%s)", print_time(callout->postmaster_stamp));
645 printf(" random=%s", print_cache(callout->random_result));
646 if (callout->random_result != ccache_unknown)
647 printf(" (%s)", print_time(callout->random_stamp));
651 /* Old-style domain record, without separate timestamps. This code can
652 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
656 printf("%s %s callout=%s postmaster=%s random=%s\n",
657 print_time(((dbdata_generic *)value)->time_stamp),
659 print_cache(callout->result),
660 print_cache(callout->postmaster_result),
661 print_cache(callout->random_result));
668 key = dbfn_scan(dbm, FALSE, &cursor);
675 #endif /* EXIM_DUMPDB */
681 /*************************************************
682 * The exim_fixdb main program *
683 *************************************************/
685 /* In order not to hold the database lock any longer than is necessary, each
686 operation on the database uses a separate open/close call. This is expensive,
687 but then using this utility is not expected to be very common. Its main use is
688 to provide a way of patching up hints databases in order to run tests.
693 This causes the data from the given record to be displayed, or "not found"
694 to be output. Note that in the retry database, destination names are
695 preceded by R: or T: for router or transport retry info.
698 This causes the given record to be deleted or "not found" to be output.
700 (3) <record name> <field number> <value>
701 This sets the given value into the given field, identified by a number
702 which is output by the display command. Not all types of record can
706 This exits from exim_fixdb.
708 If the record name is omitted from (2) or (3), the previously used record name
712 int main(int argc, char **cargv)
715 uschar **argv = USS cargv;
718 void *reset_point = store_get(0);
720 name[0] = 0; /* No name set */
722 /* Sort out the database type, verify what we are working on and then process
725 dbdata_type = check_args(argc, argv, US"fixdb", US"");
726 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
735 dbdata_callout_cache *callout;
738 uschar field[256], value[256];
740 store_reset(reset_point);
743 if (Ufgets(buffer, 256, stdin) == NULL) break;
745 buffer[Ustrlen(buffer)-1] = 0;
746 field[0] = value[0] = 0;
748 /* If the buffer contains just one digit, or just consists of "d", use the
749 previous name for an update. */
751 if ((isdigit((uschar)buffer[0]) && !isdigit((uschar)buffer[1])) ||
752 Ustrcmp(buffer, "d") == 0)
756 printf("No previous record name is set\n");
759 sscanf(CS buffer, "%s %s", field, value);
764 sscanf(CS buffer, "%s %s %s", name, field, value);
767 /* Handle an update request */
772 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
773 if (dbm == NULL) continue;
775 if (Ustrcmp(field, "d") == 0)
777 if (value[0] != 0) printf("unexpected value after \"d\"\n");
778 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
779 "not found" : "deleted");
784 else if (isdigit((uschar)field[0]))
786 int fieldno = Uatoi(field);
789 printf("value missing\n");
795 record = dbfn_read_with_length(dbm, name, &oldlength);
796 if (record == NULL) printf("not found\n"); else
799 int length = 0; /* Stops compiler warning */
804 retry = (dbdata_retry *)record;
805 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
810 retry->basic_errno = Uatoi(value);
814 retry->more_errno = Uatoi(value);
818 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
819 else printf("bad time value\n");
823 if ((tt = read_time(value)) > 0) retry->last_try = tt;
824 else printf("bad time value\n");
828 if ((tt = read_time(value)) > 0) retry->next_try = tt;
829 else printf("bad time value\n");
833 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
834 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
835 else printf("\"yes\" or \"no\" expected=n");
839 printf("unknown field number\n");
846 printf("Can't change contents of wait database record\n");
850 printf("Can't change contents of misc database record\n");
854 callout = (dbdata_callout_cache *)record;
855 length = sizeof(dbdata_callout_cache);
859 callout->result = Uatoi(value);
863 callout->postmaster_result = Uatoi(value);
867 callout->random_result = Uatoi(value);
871 printf("unknown field number\n");
878 dbfn_write(dbm, name, record, length);
885 printf("field number or d expected\n");
890 if (!verify) continue;
893 /* The "name" q causes an exit */
895 else if (Ustrcmp(name, "q") == 0) return 0;
897 /* Handle a read request, or verify after an update. */
899 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
900 if (dbm == NULL) continue;
902 record = dbfn_read_with_length(dbm, name, &oldlength);
905 printf("record %s not found\n", name);
911 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
915 retry = (dbdata_retry *)record;
916 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
917 printf("1 extra data: %d\n", retry->more_errno);
918 printf("2 first failed: %s\n", print_time(retry->first_failed));
919 printf("3 last try: %s\n", print_time(retry->last_try));
920 printf("4 next try: %s\n", print_time(retry->next_try));
921 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
925 wait = (dbdata_wait *)record;
927 printf("Sequence: %d\n", wait->sequence);
928 if (wait->count > WAIT_NAME_MAX)
930 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
931 wait->count, WAIT_NAME_MAX);
932 wait->count = WAIT_NAME_MAX;
935 for (i = 1; i <= wait->count; i++)
937 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
938 value[MESSAGE_ID_LENGTH] = 0;
939 if (count_bad && value[0] == 0) break;
940 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
941 Ustrspn(value, "0123456789"
942 "abcdefghijklmnopqrstuvwxyz"
943 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
946 printf("\n**** Data corrupted: bad character in message id ****\n");
947 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
948 printf("%02x ", value[j]);
952 printf("%s ", value);
953 t += MESSAGE_ID_LENGTH;
962 callout = (dbdata_callout_cache *)record;
963 printf("0 callout: %s (%d)\n", print_cache(callout->result),
965 if (oldlength > sizeof(dbdata_callout_cache_address))
967 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
968 callout->postmaster_result);
969 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
970 callout->random_result);
976 /* The database is closed after each request */
985 #endif /* EXIM_FIXDB */
990 /*************************************************
991 * The exim_tidydb main program *
992 *************************************************/
995 /* Utility program to tidy the contents of an exim database file. There is one
998 -t <time> expiry time for old records - default 30 days
1000 For backwards compatibility, an -f option is recognized and ignored. (It used
1001 to request a "full" tidy. This version always does the whole job.) */
1004 typedef struct key_item {
1005 struct key_item *next;
1010 int main(int argc, char **cargv)
1012 struct stat statbuf;
1013 int maxkeep = 30 * 24 * 60 * 60;
1014 int dbdata_type, i, oldest, path_len;
1015 key_item *keychain = NULL;
1019 EXIM_CURSOR *cursor;
1020 uschar **argv = USS cargv;
1024 /* Scan the options */
1026 for (i = 1; i < argc; i++)
1028 if (argv[i][0] != '-') break;
1029 if (Ustrcmp(argv[i], "-f") == 0) continue;
1030 if (Ustrcmp(argv[i], "-t") == 0)
1038 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1039 (void)sscanf(CS s, "%d%n", &value, &count);
1043 case 'w': value *= 7;
1044 case 'd': value *= 24;
1045 case 'h': value *= 60;
1046 case 'm': value *= 60;
1049 default: usage(US"tidydb", US" [-t <time>]");
1054 else usage(US"tidydb", US" [-t <time>]");
1057 /* Adjust argument values and process arguments */
1062 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1064 /* Compute the oldest keep time, verify what we are doing, and open the
1067 oldest = time(NULL) - maxkeep;
1068 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1070 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
1071 if (dbm == NULL) exit(1);
1073 /* Prepare for building file names */
1075 sprintf(CS buffer, "%s/input/", argv[1]);
1076 path_len = Ustrlen(buffer);
1079 /* It appears, by experiment, that it is a bad idea to make changes
1080 to the file while scanning it. Pity the man page doesn't warn you about that.
1081 Therefore, we scan and build a list of all the keys. Then we use that to
1082 read the records and possibly update them. */
1084 key = dbfn_scan(dbm, TRUE, &cursor);
1087 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1090 Ustrcpy(k->key, key);
1091 key = dbfn_scan(dbm, FALSE, &cursor);
1094 /* Now scan the collected keys and operate on the records, resetting
1095 the store each time round. */
1097 reset_point = store_get(0);
1099 while (keychain != NULL)
1101 dbdata_generic *value;
1103 store_reset(reset_point);
1104 key = keychain->key;
1105 keychain = keychain->next;
1106 value = dbfn_read_with_length(dbm, key, NULL);
1108 /* A continuation record may have been deleted or renamed already, so
1109 non-existence is not serious. */
1111 if (value == NULL) continue;
1113 /* Delete if too old */
1115 if (value->time_stamp < oldest)
1117 printf("deleted %s (too old)\n", key);
1118 dbfn_delete(dbm, key);
1122 /* Do database-specific tidying for wait databases, and message-
1123 specific tidying for the retry database. */
1125 if (dbdata_type == type_wait)
1127 dbdata_wait *wait = (dbdata_wait *)value;
1128 BOOL update = FALSE;
1130 /* Leave corrupt records alone */
1132 if (wait->count > WAIT_NAME_MAX)
1134 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1135 key, wait->count, wait->count, WAIT_NAME_MAX);
1139 /* Loop for renamed continuation records. For each message id,
1140 check to see if the message exists, and if not, remove its entry
1141 from the record. Because of the possibility of split input directories,
1142 we must look in both possible places for a -D file. */
1147 int length = wait->count * MESSAGE_ID_LENGTH;
1149 for (offset = length - MESSAGE_ID_LENGTH;
1150 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1152 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1153 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1155 if (Ustat(buffer, &statbuf) != 0)
1157 buffer[path_len] = wait->text[offset+5];
1158 buffer[path_len+1] = '/';
1159 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1160 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1162 if (Ustat(buffer, &statbuf) != 0)
1164 int left = length - offset - MESSAGE_ID_LENGTH;
1165 if (left > 0) Ustrncpy(wait->text + offset,
1166 wait->text + offset + MESSAGE_ID_LENGTH, left);
1168 length -= MESSAGE_ID_LENGTH;
1174 /* If record is empty and the main record, either delete it or rename
1175 the next continuation, repeating if that is also empty. */
1177 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1179 while (wait->count == 0 && wait->sequence > 0)
1182 dbdata_generic *newvalue;
1183 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1184 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1185 if (newvalue != NULL)
1188 wait = (dbdata_wait *)newvalue;
1189 dbfn_delete(dbm, newkey);
1190 printf("renamed %s\n", newkey);
1193 else wait->sequence--;
1196 /* If we have ended up with an empty main record, delete it
1197 and break the loop. Otherwise the new record will be scanned. */
1199 if (wait->count == 0 && wait->sequence == 0)
1201 dbfn_delete(dbm, key);
1202 printf("deleted %s (empty)\n", key);
1208 /* If not an empty main record, break the loop */
1213 /* Re-write the record if required */
1217 printf("updated %s\n", key);
1218 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1219 wait->count * MESSAGE_ID_LENGTH);
1223 /* If a retry record's key ends with a message-id, check that that message
1224 still exists; if not, remove this record. */
1226 else if (dbdata_type == type_retry)
1229 int len = Ustrlen(key);
1231 if (len < MESSAGE_ID_LENGTH + 1) continue;
1232 id = key + len - MESSAGE_ID_LENGTH - 1;
1233 if (*id++ != ':') continue;
1235 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1237 if (i == 6 || i == 13)
1238 { if (id[i] != '-') break; }
1240 { if (!isalnum(id[i])) break; }
1242 if (i < MESSAGE_ID_LENGTH) continue;
1244 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1245 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1247 if (Ustat(buffer, &statbuf) != 0)
1249 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1250 if (Ustat(buffer, &statbuf) != 0)
1252 dbfn_delete(dbm, key);
1253 printf("deleted %s (no message)\n", key);
1260 printf("Tidying complete\n");
1264 #endif /* EXIM_TIDYDB */
1266 /* End of exim_dbutil.c */