BOOL enq_start(uschar *key) { dbdata_serialize *serial_record; dbdata_serialize new_record; open_db dbblock; open_db *dbm_file; DEBUG(D_transport) debug_printf("check serialized: %s\n", key); /* Open and lock the waiting information database. The absence of O_CREAT is deliberate; the dbfn_open() function - which is an Exim function - always tries to create if it can't open a read/write file. It expects only O_RDWR or O_RDONLY as its argument. */ dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE); if (dbm_file == NULL) return FALSE; /* See if there is a record for this host or queue run; if there is, we cannot proceed with the connection unless the record is very old. */ serial_record = dbfn_read(dbm_file, key); if (serial_record != NULL && time(NULL) - serial_record->time_stamp < 6*60*60) { dbfn_close(dbm_file); DEBUG(D_transport) debug_printf("outstanding serialization record for %s\n", key); return FALSE; } /* We can proceed - insert a new record or update the old one. At present the count field is not used; just set it to 1. */ new_record.count = 1; dbfn_write(dbm_file, key, &new_record, (int)sizeof(dbdata_serialize)); dbfn_close(dbm_file); return TRUE; }
int main(int argc, char **cargv) { int dbdata_type; uschar **argv = USS cargv; uschar buffer[256]; uschar name[256]; void *reset_point = store_get(0); name[0] = 0; /* No name set */ /* Sort out the database type, verify what we are working on and then process user requests */ dbdata_type = check_args(argc, argv, US"fixdb", US""); printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]); for(;;) { open_db dbblock; open_db *dbm; void *record; dbdata_retry *retry; dbdata_wait *wait; dbdata_callout_cache *callout; dbdata_ratelimit *ratelimit; int i, oldlength; uschar *t; uschar field[256], value[256]; store_reset(reset_point); printf("> "); if (Ufgets(buffer, 256, stdin) == NULL) break; buffer[Ustrlen(buffer)-1] = 0; field[0] = value[0] = 0; /* If the buffer contains just one digit, or just consists of "d", use the previous name for an update. */ if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0')) || Ustrcmp(buffer, "d") == 0) { if (name[0] == 0) { printf("No previous record name is set\n"); continue; } (void)sscanf(CS buffer, "%s %s", field, value); } else { name[0] = 0; (void)sscanf(CS buffer, "%s %s %s", name, field, value); } /* Handle an update request */ if (field[0] != 0) { int verify = 1; dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock); if (dbm == NULL) continue; if (Ustrcmp(field, "d") == 0) { if (value[0] != 0) printf("unexpected value after \"d\"\n"); else printf("%s\n", (dbfn_delete(dbm, name) < 0)? "not found" : "deleted"); dbfn_close(dbm); continue; } else if (isdigit((uschar)field[0])) { int fieldno = Uatoi(field); if (value[0] == 0) { printf("value missing\n"); dbfn_close(dbm); continue; } else { record = dbfn_read_with_length(dbm, name, &oldlength); if (record == NULL) printf("not found\n"); else { time_t tt; int length = 0; /* Stops compiler warning */ switch(dbdata_type) { case type_retry: retry = (dbdata_retry *)record; length = sizeof(dbdata_retry) + Ustrlen(retry->text); switch(fieldno) { case 0: retry->basic_errno = Uatoi(value); break; case 1: retry->more_errno = Uatoi(value); break; case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt; else printf("bad time value\n"); break; case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt; else printf("bad time value\n"); break; case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt; else printf("bad time value\n"); break; case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE; else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE; else printf("\"yes\" or \"no\" expected=n"); break; default: printf("unknown field number\n"); verify = 0; break; } break; case type_wait: printf("Can't change contents of wait database record\n"); break; case type_misc: printf("Can't change contents of misc database record\n"); break; case type_callout: callout = (dbdata_callout_cache *)record; length = sizeof(dbdata_callout_cache); switch(fieldno) { case 0: callout->result = Uatoi(value); break; case 1: callout->postmaster_result = Uatoi(value); break; case 2: callout->random_result = Uatoi(value); break; default: printf("unknown field number\n"); verify = 0; break; } break; case type_ratelimit: ratelimit = (dbdata_ratelimit *)record; length = sizeof(dbdata_ratelimit); switch(fieldno) { case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt; else printf("bad time value\n"); break; case 1: ratelimit->time_usec = Uatoi(value); break; case 2: ratelimit->rate = Ustrtod(value, NULL); break; default: printf("unknown field number\n"); verify = 0; break; } break; } dbfn_write(dbm, name, record, length); } } } else { printf("field number or d expected\n"); verify = 0; } dbfn_close(dbm); if (!verify) continue; } /* The "name" q causes an exit */ else if (Ustrcmp(name, "q") == 0) return 0; /* Handle a read request, or verify after an update. */ dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock); if (dbm == NULL) continue; record = dbfn_read_with_length(dbm, name, &oldlength); if (record == NULL) { printf("record %s not found\n", name); name[0] = 0; } else { int count_bad = 0; printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp)); switch(dbdata_type) { case type_retry: retry = (dbdata_retry *)record; printf("0 error number: %d %s\n", retry->basic_errno, retry->text); printf("1 extra data: %d\n", retry->more_errno); printf("2 first failed: %s\n", print_time(retry->first_failed)); printf("3 last try: %s\n", print_time(retry->last_try)); printf("4 next try: %s\n", print_time(retry->next_try)); printf("5 expired: %s\n", (retry->expired)? "yes" : "no"); break; case type_wait: wait = (dbdata_wait *)record; t = wait->text; printf("Sequence: %d\n", wait->sequence); if (wait->count > WAIT_NAME_MAX) { printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count, wait->count, WAIT_NAME_MAX); wait->count = WAIT_NAME_MAX; count_bad = 1; } for (i = 1; i <= wait->count; i++) { Ustrncpy(value, t, MESSAGE_ID_LENGTH); value[MESSAGE_ID_LENGTH] = 0; if (count_bad && value[0] == 0) break; if (Ustrlen(value) != MESSAGE_ID_LENGTH || Ustrspn(value, "0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH) { int j; printf("\n**** Data corrupted: bad character in message id ****\n"); for (j = 0; j < MESSAGE_ID_LENGTH; j++) printf("%02x ", value[j]); printf("\n"); break; } printf("%s ", value); t += MESSAGE_ID_LENGTH; } printf("\n"); break; case type_misc: break; case type_callout: callout = (dbdata_callout_cache *)record; printf("0 callout: %s (%d)\n", print_cache(callout->result), callout->result); if (oldlength > sizeof(dbdata_callout_cache_address)) { printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result), callout->postmaster_result); printf("2 random: %s (%d)\n", print_cache(callout->random_result), callout->random_result); } break; case type_ratelimit: ratelimit = (dbdata_ratelimit *)record; printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp)); printf("1 fract. time: .%06d\n", ratelimit->time_usec); printf("2 sender rate: % .3f\n", ratelimit->rate); break; } } /* The database is closed after each request */ dbfn_close(dbm); } printf("\n"); return 0; }
int main(int argc, char **cargv) { open_db dbblock[8]; int max_db = sizeof(dbblock)/sizeof(open_db); int current = -1; int showtime = 0; int i; dbdata_wait *dbwait = NULL; uschar **argv = USS cargv; uschar buffer[256]; uschar structbuffer[1024]; if (argc != 2) { printf("Usage: test_dbfn directory\n"); printf("The subdirectory called \"db\" in the given directory is used for\n"); printf("the files used in this test program.\n"); return 1; } /* Initialize */ spool_directory = argv[1]; debug_selector = D_all - D_memory; debug_file = stderr; big_buffer = malloc(big_buffer_size); for (i = 0; i < max_db; i++) dbblock[i].dbptr = NULL; printf("\nExim's db functions tester: interface type is %s\n", EXIM_DBTYPE); printf("DBM library: "); #ifdef DB_VERSION_STRING printf("Berkeley DB: %s\n", DB_VERSION_STRING); #elif defined(BTREEVERSION) && defined(HASHVERSION) #ifdef USE_DB printf("probably Berkeley DB version 1.8x (native mode)\n"); #else printf("probably Berkeley DB version 1.8x (compatibility mode)\n"); #endif #elif defined(_DBM_RDONLY) || defined(dbm_dirfno) printf("probably ndbm\n"); #elif defined(USE_TDB) printf("using tdb\n"); #else #ifdef USE_GDBM printf("probably GDBM (native mode)\n"); #else printf("probably GDBM (compatibility mode)\n"); #endif #endif /* Test the functions */ printf("\nTest the functions\n> "); while (Ufgets(buffer, 256, stdin) != NULL) { int len = Ustrlen(buffer); int count = 1; clock_t start = 1; clock_t stop = 0; uschar *cmd = buffer; while (len > 0 && isspace((uschar)buffer[len-1])) len--; buffer[len] = 0; if (isdigit((uschar)*cmd)) { count = Uatoi(cmd); while (isdigit((uschar)*cmd)) cmd++; while (isspace((uschar)*cmd)) cmd++; } if (Ustrncmp(cmd, "open", 4) == 0) { int i; open_db *odb; uschar *s = cmd + 4; while (isspace((uschar)*s)) s++; for (i = 0; i < max_db; i++) if (dbblock[i].dbptr == NULL) break; if (i >= max_db) { printf("Too many open databases\n> "); continue; } start = clock(); odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE); stop = clock(); if (odb != NULL) { current = i; printf("opened %d\n", current); } /* Other error cases will have written messages */ else if (errno == ENOENT) { printf("open failed: %s%s\n", strerror(errno), #ifdef USE_DB " (or other Berkeley DB error)" #else "" #endif ); } } else if (Ustrncmp(cmd, "write", 5) == 0) { int rc = 0; uschar *key = cmd + 5; uschar *data; if (current < 0) { printf("No current database\n"); continue; } while (isspace((uschar)*key)) key++; data = key; while (*data != 0 && !isspace((uschar)*data)) data++; *data++ = 0; while (isspace((uschar)*data)) data++; dbwait = (dbdata_wait *)(&structbuffer); Ustrcpy(dbwait->text, data); start = clock(); while (count-- > 0) rc = dbfn_write(dbblock + current, key, dbwait, Ustrlen(data) + sizeof(dbdata_wait)); stop = clock(); if (rc != 0) printf("Failed: %s\n", strerror(errno)); } else if (Ustrncmp(cmd, "read", 4) == 0) { uschar *key = cmd + 4; if (current < 0) { printf("No current database\n"); continue; } while (isspace((uschar)*key)) key++; start = clock(); while (count-- > 0) dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock+ current, key, NULL); stop = clock(); printf("%s\n", (dbwait == NULL)? "<not found>" : CS dbwait->text); } else if (Ustrncmp(cmd, "delete", 6) == 0) { uschar *key = cmd + 6; if (current < 0) { printf("No current database\n"); continue; } while (isspace((uschar)*key)) key++; dbfn_delete(dbblock + current, key); } else if (Ustrncmp(cmd, "scan", 4) == 0) { EXIM_CURSOR *cursor; BOOL startflag = TRUE; uschar *key; uschar keybuffer[256]; if (current < 0) { printf("No current database\n"); continue; } start = clock(); while ((key = dbfn_scan(dbblock + current, startflag, &cursor)) != NULL) { startflag = FALSE; Ustrcpy(keybuffer, key); dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock + current, keybuffer, NULL); printf("%s: %s\n", keybuffer, dbwait->text); } stop = clock(); printf("End of scan\n"); } else if (Ustrncmp(cmd, "close", 5) == 0) { uschar *s = cmd + 5; while (isspace((uschar)*s)) s++; i = Uatoi(s); if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else { start = clock(); dbfn_close(dbblock + i); stop = clock(); dbblock[i].dbptr = NULL; if (i == current) current = -1; } } else if (Ustrncmp(cmd, "file", 4) == 0) { uschar *s = cmd + 4; while (isspace((uschar)*s)) s++; i = Uatoi(s); if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else current = i; } else if (Ustrncmp(cmd, "time", 4) == 0) { showtime = ~showtime; printf("Timing %s\n", showtime? "on" : "off"); } else if (Ustrcmp(cmd, "q") == 0 || Ustrncmp(cmd, "quit", 4) == 0) break; else if (Ustrncmp(cmd, "help", 4) == 0) { printf("close [<number>] close file [<number>]\n"); printf("delete <key> remove record from current file\n"); printf("file <number> make file <number> current\n"); printf("open <name> open db file\n"); printf("q[uit] exit program\n"); printf("read <key> read record from current file\n"); printf("scan scan current file\n"); printf("time time display on/off\n"); printf("write <key> <rest-of-line> write record to current file\n"); } else printf("Eh?\n"); if (showtime && stop >= start) printf("start=%d stop=%d difference=%d\n", (int)start, (int)stop, (int)(stop - start)); printf("> "); } for (i = 0; i < max_db; i++) { if (dbblock[i].dbptr != NULL) { printf("\nClosing %d", i); dbfn_close(dbblock + i); } } printf("\n"); return 0; }
int main(int argc, char **cargv) { struct stat statbuf; int maxkeep = 30 * 24 * 60 * 60; int dbdata_type, i, oldest, path_len; key_item *keychain = NULL; void *reset_point; open_db dbblock; open_db *dbm; EXIM_CURSOR *cursor; uschar **argv = USS cargv; uschar buffer[256]; uschar *key; /* Scan the options */ for (i = 1; i < argc; i++) { if (argv[i][0] != '-') break; if (Ustrcmp(argv[i], "-f") == 0) continue; if (Ustrcmp(argv[i], "-t") == 0) { uschar *s; s = argv[++i]; maxkeep = 0; while (*s != 0) { int value, count; if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]"); (void)sscanf(CS s, "%d%n", &value, &count); s += count; switch (*s) { case 'w': value *= 7; case 'd': value *= 24; case 'h': value *= 60; case 'm': value *= 60; case 's': s++; break; default: usage(US"tidydb", US" [-t <time>]"); } maxkeep += value; } } else usage(US"tidydb", US" [-t <time>]"); } /* Adjust argument values and process arguments */ argc -= --i; argv += i; dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]"); /* Compute the oldest keep time, verify what we are doing, and open the database */ oldest = time(NULL) - maxkeep; printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]); dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock); if (dbm == NULL) exit(1); /* Prepare for building file names */ sprintf(CS buffer, "%s/input/", argv[1]); path_len = Ustrlen(buffer); /* It appears, by experiment, that it is a bad idea to make changes to the file while scanning it. Pity the man page doesn't warn you about that. Therefore, we scan and build a list of all the keys. Then we use that to read the records and possibly update them. */ key = dbfn_scan(dbm, TRUE, &cursor); while (key != NULL) { key_item *k = store_get(sizeof(key_item) + Ustrlen(key)); k->next = keychain; keychain = k; Ustrcpy(k->key, key); key = dbfn_scan(dbm, FALSE, &cursor); } /* Now scan the collected keys and operate on the records, resetting the store each time round. */ reset_point = store_get(0); while (keychain != NULL) { dbdata_generic *value; store_reset(reset_point); key = keychain->key; keychain = keychain->next; value = dbfn_read_with_length(dbm, key, NULL); /* A continuation record may have been deleted or renamed already, so non-existence is not serious. */ if (value == NULL) continue; /* Delete if too old */ if (value->time_stamp < oldest) { printf("deleted %s (too old)\n", key); dbfn_delete(dbm, key); continue; } /* Do database-specific tidying for wait databases, and message- specific tidying for the retry database. */ if (dbdata_type == type_wait) { dbdata_wait *wait = (dbdata_wait *)value; BOOL update = FALSE; /* Leave corrupt records alone */ if (wait->count > WAIT_NAME_MAX) { printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n", key, wait->count, wait->count, WAIT_NAME_MAX); continue; } /* Loop for renamed continuation records. For each message id, check to see if the message exists, and if not, remove its entry from the record. Because of the possibility of split input directories, we must look in both possible places for a -D file. */ for (;;) { int offset; int length = wait->count * MESSAGE_ID_LENGTH; for (offset = length - MESSAGE_ID_LENGTH; offset >= 0; offset -= MESSAGE_ID_LENGTH) { Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH); sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D"); if (Ustat(buffer, &statbuf) != 0) { buffer[path_len] = wait->text[offset+5]; buffer[path_len+1] = '/'; Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH); sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D"); if (Ustat(buffer, &statbuf) != 0) { int left = length - offset - MESSAGE_ID_LENGTH; if (left > 0) Ustrncpy(wait->text + offset, wait->text + offset + MESSAGE_ID_LENGTH, left); wait->count--; length -= MESSAGE_ID_LENGTH; update = TRUE; } } } /* If record is empty and the main record, either delete it or rename the next continuation, repeating if that is also empty. */ if (wait->count == 0 && Ustrchr(key, ':') == NULL) { while (wait->count == 0 && wait->sequence > 0) { uschar newkey[256]; dbdata_generic *newvalue; sprintf(CS newkey, "%s:%d", key, wait->sequence - 1); newvalue = dbfn_read_with_length(dbm, newkey, NULL); if (newvalue != NULL) { value = newvalue; wait = (dbdata_wait *)newvalue; dbfn_delete(dbm, newkey); printf("renamed %s\n", newkey); update = TRUE; } else wait->sequence--; } /* If we have ended up with an empty main record, delete it and break the loop. Otherwise the new record will be scanned. */ if (wait->count == 0 && wait->sequence == 0) { dbfn_delete(dbm, key); printf("deleted %s (empty)\n", key); update = FALSE; break; } } /* If not an empty main record, break the loop */ else break; } /* Re-write the record if required */ if (update) { printf("updated %s\n", key); dbfn_write(dbm, key, wait, sizeof(dbdata_wait) + wait->count * MESSAGE_ID_LENGTH); } } /* If a retry record's key ends with a message-id, check that that message still exists; if not, remove this record. */ else if (dbdata_type == type_retry) { uschar *id; int len = Ustrlen(key); if (len < MESSAGE_ID_LENGTH + 1) continue; id = key + len - MESSAGE_ID_LENGTH - 1; if (*id++ != ':') continue; for (i = 0; i < MESSAGE_ID_LENGTH; i++) { if (i == 6 || i == 13) { if (id[i] != '-') break; } else { if (!isalnum(id[i])) break; } } if (i < MESSAGE_ID_LENGTH) continue; Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH); sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D"); if (Ustat(buffer, &statbuf) != 0) { sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id); if (Ustat(buffer, &statbuf) != 0) { dbfn_delete(dbm, key); printf("deleted %s (no message)\n", key); } } } } dbfn_close(dbm); printf("Tidying complete\n"); return 0; }