/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:retry.c,v 1.73 2005/10/28 04:37:29 woods Exp"
*/
/*
* retry.c:
* Control how often addresses are retried, and how long
* retries are attempted until Smail gives up.
*
* This file was contributed by Chip Salzenberg <chip@tct.com>.
* It has been modified.
*
* external functions: read_retry_file, retry_addr_before,
* retry_addr_after, retry_addr_finished,
* retry_host_lock, retry_host_unlock
*/
#include "defs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
# include <sys/file.h>
# endif
#endif
#ifdef __GLIBC__
# include <sys/file.h>
#endif
#ifdef HAVE_SYS_LOCKING_H
# include <sys/locking.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include "smail.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "exitcodes.h"
#include "log.h"
#include "direct.h"
#include "route.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "lookup.h"
#include "jump.h"
#include "spool.h"
#include "transport.h"
#include "smailconf.h"
#include "extern.h"
#include "debug.h"
#include "error.h"
#include "smailport.h"
#define RETRY_MODE 0644 /* file creation modes */
#ifdef O_SYNC
# define O_SWYM O_SYNC
#else
# define O_SWYM 0
#endif
struct retry {
struct retry *succ; /* next in chain */
char *domains; /* target domain, or "*" */
time_t interval; /* minimum time between tries */
time_t duration; /* how long to keep trying */
};
static struct retry *retries = NULL; /* array of control structures */
static int locked_fd = -1;
static char *locked_path = NULL;
static JUMP_ENVBUF tryfile_jmp;
static struct retry *default_retry __P((void));
static int match_retry_domain __P((char *, char *));
static int allow_now __P((int, char *, time_t, time_t, struct retry *,
struct error **));
static int allow_later __P((int, char *, time_t, time_t, struct retry *, int, struct error **));
static char *tryfile_name __P((struct transport *, char *, int));
static int tryfile_cat __P((struct str *, char *, size_t, int, int, int));
static int tryfile_mkdir __P((struct str *));
static int tryfile_lock_wait __P((int, long));
static int tryfile_lock __P((int));
static void tryfile_unlock __P((int));
static void tryfile_alarm __P((int));
static void write_error_or_unlink __P((int, char *, struct error *));
static void read_retry_error __P((int, char *, struct error *));
/*
* read_retry_file - read and remember contents of retry file
*/
char *
read_retry_file()
{
struct stat statbuf;
struct retry *r, *nr;
FILE *f;
char *entry, *error;
/*
* forget any previously-read retry data
*/
for (r = retries; r; r = nr) {
nr = r->succ;
xfree(r->domains);
xfree((char *) r);
}
retries = NULL;
/*
* try to open retry file, stat file if possible
*/
if (retry_file == NULL || EQ(retry_file, "-")) {
return NULL;
}
f = fopen(retry_file, "r");
if (f == NULL) {
char *emsg = xprintf("cannot open %v: %s", retry_file, strerror(errno));
if (require_configs) {
return emsg;
} else if (errno != ENOENT) {
write_log(WRITE_LOG_PANIC, "%s", emsg);
}
xfree(emsg);
add_config_stat(retry_file, (struct stat *) NULL);
retries = default_retry();
return NULL;
}
if (fstat(fileno(f), &statbuf) == -1) {
char *emsg = xprintf("%v: fstat() failed: %s", retry_file, strerror(errno));
/* realistically this could only ever be EIO... */
if (require_configs) {
return emsg;
} else {
write_log(WRITE_LOG_PANIC, "%s", emsg);
}
xfree(emsg);
add_config_stat(retry_file, (struct stat *) NULL);
/* ... but go on and try reading from 'f' anyway... */
} else {
add_config_stat(retry_file, &statbuf);
}
/*
* loop and read all of the table entries in the retry file
*/
error = NULL;
nr = NULL;
while ((entry = read_entry(f, retry_file))) {
struct attribute *new;
char *p;
int len;
time_t interval, duration;
new = parse_table(entry, &error);
if (new == NULL) {
break;
}
/* trim any comment for the error messages */
if ((p = strchr(entry, '#'))) {
*p = '\0';
}
/* trim the trailing newline, if any, for error messages */
len = strlen(entry);
if (entry[len - 1] == '\n') {
entry[len - 1] = '\0';
}
/*
* split the string into two parts on the '/'
*/
p = strchr(new->value, '/');
if (p == NULL) {
error = xprintf("%v: %s: invalid entry, no '/': \"%v\".", retry_file, new->name, entry);
xfree(new->name);
/* note that new->value points to the same storage as new->name */
xfree((char *) new);
break;
}
/* terminate interval part and point at the duration part */
*p++ = '\0';
/* parse the interval, if specified */
if (*(new->value)) {
interval = ivaltol(new->value);
if (interval < 0) {
error = xprintf("%v: %s: invalid retry interval: %s (from entry \"%v\").", retry_file, new->name, new->value, entry);
xfree(new->name);
xfree((char *) new);
break;
}
} else {
interval = retry_interval;
}
/* parse the duration, if specified */
if (*p) {
duration = ivaltol(p);
if (duration < 0) {
error = xprintf("%v: %s: invalid retry duration: %s (from entry \"%v\").", retry_file, new->name, p, entry);
xfree(new->name);
xfree((char *) new);
break;
}
} else {
duration = retry_duration;
}
/* allocate new structure */
r = (struct retry *) xmalloc(sizeof(struct retry));
/* assign new structure's domain */
/* shrink the storage from parse_table() to toss the "value" part */
r->domains = xrealloc(new->name, (size_t) (strlen(new->name) + 1));
/* assign new structure's limits */
r->interval = interval;
r->duration = duration;
/* append new structure to chain */
r->succ = NULL;
if (nr == NULL) {
retries = r;
} else {
nr->succ = r;
}
nr = r;
/* free record and value returned by parse_table() but don't free the
* name as we're using it in the retry record above
*/
xfree((char *) new);
/* a lone wildcard record obviates any need to read further */
if (EQ(nr->domains, "*")) {
break;
}
}
(void) fclose(f);
/*
* if there were no errors reading the retry file, and there is still no
* default record, and if global configuration requires a default record,
* add it.
*/
if (!error && nr && !EQ(nr->domains, "*") && (r = default_retry())) {
if (nr == NULL) {
retries = r;
} else {
nr->succ = r;
}
}
/* return error message, or NULL for success */
return error;
}
/*
* dump_retry_config - dump out the current retry configuration
*/
void
dump_retry_config(f)
FILE *f;
{
struct retry *rp;
fputs("#\n# -- retry configuration\n#\n", f);
for (rp = retries; (rp); rp = rp->succ) {
char *value;
char *comment = NULL;
/* note also that ltoival() uses static memory which means that it
* cannot be called twice in the same expression even if it didn't
* return NULL....
*/
fprintf(f, "%s\t", rp->domains);
if (rp->interval == 0) {
/* queue_interval might not be set yet so just print it symbolically */
fputs("0/", f);
comment = "#\tnote: a retry_interval of zero implies (queue_interval * 2), unless debugging\n";
} else {
if (!(value = ltoival(rp->interval))) {
comment = "#\tWARNING: invalid 'interval' part in entry above\n";
value = "-1";
}
fprintf(f, "%s/", value);
}
if (!(value = ltoival(rp->duration))) {
comment = "#\tWARNING: invalid 'duration' part in entry above\n";
value = "-1";
}
fprintf(f, "%s\n", value);
if (comment) {
fputs(comment, f);
}
}
fputs("#\n# -- end of retry configuration\n#\n", f);
}
/*
* default_retry - build default retry record from global configuration
*/
static struct retry *
default_retry()
{
struct retry *r = NULL;
if (retry_duration >= 0) {
r = (struct retry *) xmalloc(sizeof(struct retry));
r->domains = COPY_STRING("*");
r->interval = retry_interval;
r->duration = retry_duration;
r->succ = NULL;
}
return r;
}
/*
* retry_addr_before - retry processing before transport attempt
*
* called from transport drivers that do local or external delivery
* (eg. appendfile and pipe).
*/
struct addr *
retry_addr_before(in, defer, fail)
struct addr *in;
struct addr **defer;
struct addr **fail;
{
time_t spooled = message_date();
struct addr *out, *cur, *next;
out = NULL;
for (cur = in; cur; cur = next) {
struct addr **where = &out;
struct retry *r;
char *lock_host = (cur->flags & ADDR_SMARTHOST) ? cur->target : cur->next_host;
next = cur->succ;
/* skip if no host name to lock */
if (!lock_host) {
cur->succ = out;
out = cur;
continue;
}
for (r = retries; r; r = r->succ) {
if (match_retry_domain(r->domains, lock_host)) {
char *tryname;
int may_deliver = FALSE;
int fd;
time_t started;
/* calculate path of retry file */
if (!(tryname = tryfile_name(cur->transport, lock_host, TRUE))) {
break;
}
DEBUG1(DBG_RETRY_MID, "retry_addr_before(): examining %v\n", tryname);
/*
* examine the retry file: lock and call allow_now() and then unlock;
* ignore empties or missing files
*/
for (;;) {
int lk;
if ((fd = open(tryname, O_RDWR|O_SWYM)) == -1) {
time(&started);
DEBUG2(DBG_RETRY_LO, "retry_addr_before(): open of %v failed: %s\n", tryname, strerror(errno));
may_deliver = TRUE;
break;
}
lk = tryfile_lock_wait(fd, 0L);
if (lk == DB_FAIL) {
DEBUG2(DBG_RETRY_LO, "retry_addr_before(): lock of %v failed: %s\n", tryname, strerror(errno));
/* XXX should this be written to the system log instead? */
(void) close(fd);
(void) unlink(tryname); /* XXX really??? well there was no timeout... */
where = defer;
break;
}
if (lk == DB_AGAIN) { /* it was removed from under us... */
(void) close(fd);
continue;
}
/* the rest happens only once */
if (lk == DB_SUCCEED) {
time(&started);
may_deliver = allow_now(fd, tryname, spooled, started,
r, &cur->error);
(void) tryfile_unlock(fd);
}
break;
}
if (may_deliver) {
/* try it; if it fails, we'll touch the retry file */
cur->flags |= ADDR_RETRY_FILE;
} else {
struct error *lckerr;
/*
* retry time has not yet come so check to see if we must
* wait for another retry interval or whether we should
* bounce the message right away...,
*/
where = defer;
if (!allow_later(fd, tryname, spooled, started, r, TRUE, &lckerr)) {
where = fail;
/*
* now merge the retry error with any existing error.
*/
if (cur->error) {
struct error *newerr;
newerr = note_error(cur->error->info | lckerr->info,
xprintf("%v: %v", /* XXX bound to get ugly... */
cur->error->message,
lckerr->message));
free_error(lckerr);
free_error(cur->error);
cur->error = newerr;
} else {
cur->error = lckerr;
}
}
}
(void) close(fd);
/* stop looking for retry specs after first match */
break;
}
}
cur->succ = *where;
*where = cur;
}
return out;
}
/*
* retry_addr_after - retry processing after delivery attempt (NOP for now)
*/
/* ARGSUSED */
struct addr *
retry_addr_after(started, in, fail)
time_t started __attribute__((unused));
struct addr *in;
struct addr **fail __attribute__((unused));
{
return in;
}
/*
* retry_addr_finished - record success or failure
*/
void
retry_addr_finished(addr)
struct addr *addr;
{
char *prev_host = NULL;
struct addr *cur;
for (cur = addr; cur; cur = cur->succ) {
if (cur->flags & ADDR_RETRY_FILE) { /* but only if we're supposed
* to touch the retry file */
struct error *error = cur->error;
char *tryname;
int fd;
char *lock_host = (cur->flags & ADDR_SMARTHOST) ? cur->target : cur->next_host;
/* don't bother writing to the same file twice in a row */
if (prev_host && EQIC(prev_host, lock_host)) {
continue;
}
prev_host = lock_host;
/* calculate path of retry file */
if (!(tryname = tryfile_name(cur->transport, lock_host, (error != NULL)))) {
continue;
}
DEBUG2(DBG_RETRY_LO, "retry_addr_finished(): %s %v\n",
error ? "writing error message in" : "removing", tryname);
/* write to, or remove, retry file */
for (;;) {
int lk;
if ((fd = open(tryname, O_WRONLY | (error ? O_CREAT : 0),
RETRY_MODE)) == -1)
{
/* worth noting if we were not going to remove it... */
if (error) {
write_log(WRITE_LOG_SYS, "retry_addr_finished(): open(%v) failed: %s",
tryname, strerror(errno));
}
break;
}
lk = tryfile_lock_wait(fd, 0L);
if (lk == DB_FAIL) {
write_log(WRITE_LOG_SYS, "retry_addr_finished(): lock of %v failed: %s%s %s",
tryname,
strerror(errno),
error ? "," : "",
error ? error->message : "(would have removed it)");
(void) close(fd);
break;
}
if (lk == DB_AGAIN) { /* it was removed from under us but... */
(void) close(fd);
if (error) {
continue; /* ...we still have something to say! */
} else {
break; /* ...or maybe we don't care */
}
}
/* the rest happens only once */
if (lk == DB_SUCCEED) {
/* write the error, or unlink if none */
write_error_or_unlink(fd, tryname, error);
tryfile_unlock(fd);
}
(void) close(fd);
break;
}
}
}
}
/*
* retry_host_lock - lock the target host for delivery the given "hostname"
*
* return SUCCEED if lock succeeded, FAIL if it failed.
* if failure is temporary, set *defer_failure.
*
* some transports will use this function. those that do so won't
* call retry_addr_before(), so retry_addr_*() will do nothing.
*
* Transports calling this function will/must always do the right thing to
* preserve or merge the most important error for the message to be delivered.
*/
int
retry_host_lock(transport, host, defer_failurep, errorp)
struct transport *transport; /* transport name to use under retry_file */
char *host; /* target hostname to lock */
int *defer_failurep; /* set to TRUE if retry duration not yet exceeded */
struct error **errorp; /* locking error if FAIL, prev. defer error if above */
{
time_t spooled = message_date();
time_t started;
struct retry *r;
char *tryname;
int fd, dfok, ret;
/* safety: unlock previously locked retry file */
if (locked_fd >= 0) {
tryfile_unlock(locked_fd);
(void) close(locked_fd);
locked_fd = -1;
}
if (locked_path) {
xfree(locked_path);
locked_path = NULL;
}
/* calculate path of retry file, creating directories */
if ((tryname = tryfile_name(transport, host, TRUE)) == NULL) {
*errorp = note_error(ERR_NPOSTMAST | ERR_175, xprintf("cannot create retry file directory for %v/%s", transport, host));
*defer_failurep = TRUE;
return FAIL;
}
/* open and lock retry file */
for (;;) {
int lk;
char *errstr;
if ((fd = open(tryname, O_RDWR|O_CREAT|O_SWYM, RETRY_MODE)) == -1) {
if (!*errorp) {
*errorp = note_error(ERR_NPOSTMAST | ERR_175,
xprintf("cannot open host retry file %v: %s",
tryname, strerror(errno)));
}
*defer_failurep = TRUE;
return FAIL;
}
if (process_queue) {
lk = tryfile_lock(fd); /* don't bother waiting! */
} else {
lk = tryfile_lock_wait(fd, host_lock_timeout);
}
if (lk == DB_SUCCEED) {
break; /* GOT IT! Get outta the loop! */
}
if (lk == DB_AGAIN) { /* lock was removed from under us... */
(void) close(fd);
continue; /* ... so try again */
}
/* else (lk == DB_FAIL) */
errstr = strerror(errno);
DEBUG2(DBG_RETRY_LO, "retry_host_lock(): %s: %v.\n",
process_queue ? "already locked" : "timed out", errstr);
(void) close(fd);
if (!*errorp) {
*errorp = note_error(ERR_DONTLOG | ERR_175,
xprintf("host retry file %v lock failed: %s (%s)",
tryname,
process_queue ? "already locked?" : "timed out?",
errstr));
}
/*
* XXX does this cause any problems? What if there is another process
* working on this message and it's just about to read the message log
* before we write an Xdefer entry to it?
*/
*defer_failurep = TRUE;
/* XXX should we unlink(tryname) here? */
return FAIL;
}
/* now is the official start of attempt */
(void) time(&started);
/* assume the best */
ret = SUCCEED;
dfok = TRUE;
/* if host matches any retry entries, enforce the matching one */
for (r = retries; r; r = r->succ) {
if (match_retry_domain(r->domains, host)) {
DEBUG2(EQ(r->domains, "*") ? DBG_RETRY_HI : DBG_RETRY_MID,
"host %v matched retry entry for '%s'.\n", host, r->domains);
/* notice if it's too soon to try delivery */
ret = allow_now(fd, tryname, spooled, started, r, errorp) ? SUCCEED : FAIL;
/* we can defer if further retries would be soon enough */
/*
* XXX we don't always want to include the previous retry file
* error message -- only if the invoking transport driver doesn't
* already have a better message to include. However at this point
* it's impossible for us to know what the transport driver will do
* with the error message.
*/
dfok = allow_later(fd, tryname, spooled, started, r, TRUE, errorp);
/* stop looking for retry specs after first match */
break;
}
}
/* keep human informed */
DEBUG5(DBG_RETRY_LO,
"lock of %v %s (will%s defer if delivery fails)%s%v\n",
tryname,
(ret == SUCCEED) ? "succeeded" : "failed",
dfok ? "" : " NOT",
(errorp && *errorp) ? ": " : "",
(errorp && *errorp) ? (*errorp)->message : "");
/* if retry control allows deferral, inform caller */
*defer_failurep = dfok;
if (ret == SUCCEED) {
/* remember name, fd and retry parameters */
locked_fd = fd;
locked_path = COPY_STRING(tryname);
} else {
/* we won't be needing the lock file after all */
tryfile_unlock(fd);
(void) close(fd);
}
return ret;
}
/*
* retry_host_unlock - unlock the target host for delivery, recording status
*
* set *fail if retry time has expired.
*
* some transports will use this function after using retry_host_lock(). those
* that do so won't call retry_addr_before(), so retry_addr_after() and
* retry_addr_finish() will do nothing because ADDR_RETRY_FILE isn't set by
* retry_host_lock().
*/
/* ARGSUSED */
void
retry_host_unlock(started, error)
time_t started __attribute__((unused));
struct error *error;
{
if (locked_fd < 0) {
return;
}
DEBUG3(DBG_RETRY_LO, "unlocking %s and %s%v.\n",
locked_path,
error ? "storing error: " : "unlinking",
error ? error->message : "");
/* write the error, or unlink if none */
write_error_or_unlink(locked_fd, locked_path, error);
/* unlock and close the retry file */
tryfile_unlock(locked_fd);
(void) close(locked_fd);
/* forget that we ever saw the lock file */
locked_fd = -1;
xfree(locked_path);
locked_path = NULL;
return;
}
/*
* match_retry_domain - determine if domain pattern matches given host
*/
static int
match_retry_domain(domains, target)
char *domains; /* colon separated list of domains */
char *target; /* target to test against */
{
register char *cur; /* current domain being checked */
if (EQ(domains, "*")) {
return TRUE;
}
for (cur = strcolon(domains); cur; cur = strcolon((char *)NULL)) {
if (is_suffix(cur, target, FALSE)) {
return TRUE;
}
}
return FALSE;
}
/*
* allow_now - can we deliver now? and if not, can we defer?
*/
static int
allow_now(fd, tryname, spooled, started, r, error)
int fd;
char *tryname;
time_t spooled __attribute__((unused)); /* when was the msg spooled? */
time_t started; /* when was this lock attempt started? */
struct retry *r;
struct error **error;
{
struct stat statbuf;
time_t interval;
if (fstat(fd, &statbuf) == -1) {
/*
* EIO is the only plausible error -- treat it as a fresh empty file
*/
write_log(WRITE_LOG_PANIC, "retry file %v: fstat() failed: %s", tryname, strerror(errno));
return TRUE;
}
if (statbuf.st_size == 0) {
/* fresh empty retry files mean retry time is right now */
DEBUG1(DBG_RETRY_HI, "retry file (%v) freshly created -- retry now!\n", tryname);
return TRUE;
}
if (r->interval == 0) {
interval = (time_t) ((queue_interval > 0) ? (queue_interval * 2) : 1);
} else {
interval = r->interval;
}
DEBUG3(DBG_RETRY_HI, "%v: retry interval: %s (queue_interval = %ld)\n",
tryname, ltoival(interval), (long) queue_interval);
if (statbuf.st_mtime <= started && (started - statbuf.st_mtime) < interval) {
/* just wait a little longer */
DEBUG1(DBG_RETRY_MID, "%v: retry time not yet reached\n", tryname);
if (*error == NULL) {
*error = note_error(ERR_DONTLOG | ERR_174,
xprintf("%v: retry time not yet reached", tryname));
}
return FALSE;
}
/* it must be retry time.... */
DEBUG1(DBG_RETRY_HI, "%v: retry time is now!\n", tryname);
return TRUE;
}
/*
* allow_later - can we make more retries after this one?
*/
static int
allow_later(fd, tryname, spooled, started, r, inclrtry, errorp)
int fd;
char *tryname;
time_t spooled; /* when was the msg spooled? */
time_t started; /* when was this lock attempt started? */
struct retry *r;
int inclrtry; /* include retry error msg? */
struct error **errorp;
{
/* if this was the last try... */
DEBUG1(DBG_RETRY_HI, "retry duration: %s\n", ltoival(r->duration));
if ((started - spooled) > r->duration) {
struct error rtry_err;
rtry_err.info = ERR_173;
rtry_err.message = NULL;
/*
* if we are asked to keep the retry error then try to read the
* previous one from the retry file
*/
if (inclrtry) {
read_retry_error(fd, tryname, &rtry_err);
}
*errorp = note_error(ERR_NSOWNER | rtry_err.info,
xprintf("%s%sRetry duration (%s) has been exceeded.\n\tNo further delivery attempts will be made.",
rtry_err.message ? rtry_err.message : "",
rtry_err.message ? "\n\t" : "",
ltoival(r->duration)));
return FALSE;
}
/* there is still time for more retries */
DEBUG(DBG_RETRY_MID, "retry timeout duration not yet reached\n");
return TRUE;
}
/*
* tryfile_name - figure name of file used to hold timestamp for
* the given host.
*
* Note: returns address of static area.
*/
static char *
tryfile_name(transport, host, create_dirs)
struct transport *transport;
char *host;
int create_dirs;
{
static long pathmax = 0;
static struct str path;
static int inited = FALSE;
#ifdef POSIX_OS
if (pathmax == 0) {
long n = pathconf(".", _PC_NAME_MAX);
if (n != -1) {
pathmax = n;
}
}
#else
#ifdef UNIX_BSD
if (pathmax == 0) {
pathmax = 250;
}
#endif /* UNIX_BSD */
#endif /* POSIX_OS */
if (pathmax == 0) {
pathmax = 14;
}
if (!inited) {
STR_INIT(&path);
inited = TRUE;
} else {
STR_CHECK(&path);
STR_CLEAR(&path);
}
str_cat(&path, "retry");
if (create_dirs && tryfile_mkdir(&path) == FAIL) {
return NULL;
}
STR_NEXT(&path, '/');
str_cat(&path, (transport->retry_dir && transport->retry_dir[0])
? transport->retry_dir : transport->name);
if (create_dirs && tryfile_mkdir(&path) == FAIL) {
return NULL;
}
if ((int)strlen(host) <= pathmax) {
if (tryfile_cat(&path, host, strlen(host), TRUE, FALSE,
create_dirs) == FAIL)
{
return NULL;
}
} else {
char *p, *q;
int first, partial;
first = TRUE;
p = host + strlen(host);
for (;;) {
while (p > host && *(p - 1) == '.') {
--p;
}
if (p == host) {
break;
}
q = p;
partial = FALSE;
while (p > host && *(p - 1) != '.') {
if ((q - p) >= (pathmax - 1)) {
partial = TRUE;
break;
}
--p;
}
if (tryfile_cat(&path, p, (size_t) (q - p), first, partial,
create_dirs) == FAIL)
{
return NULL;
}
first = FALSE;
}
}
STR_NEXT(&path, '\0');
return STR(&path);
}
static int
tryfile_cat(sp, p, len, first, partial, create_dirs)
struct str *sp;
char *p;
size_t len;
int first;
int partial;
int create_dirs;
{
size_t i;
if (!first && STR_LEN(sp) && STR(sp)[STR_LEN(sp) - 1] != '_') {
STR_NEXT(sp, '.');
}
if (create_dirs && tryfile_mkdir(sp) == FAIL) {
return FAIL;
}
STR_NEXT(sp, '/');
for (i = 0; i < len; ++i) {
STR_NEXT(sp, tolower((int) (*(p + i) & 0xFF))); /* hostnames are case insensitive */
}
if (partial) {
STR_NEXT(sp, '_');
}
return SUCCEED;
}
static int
tryfile_mkdir(sp)
struct str *sp;
{
struct stat statbuf;
int ret = SUCCEED;
STR_NEXT(sp, '\0'); /* NUL terminate */
if (stat(STR(sp), &statbuf) == -1) {
DEBUG1(DBG_RETRY_LO, "make directory %s\n", STR(sp));
(void) mkdir(STR(sp), auto_mkdir_mode);
if (stat(STR(sp), &statbuf) == -1) {
write_log(WRITE_LOG_SYS, "can't create directory %s", STR(sp));
ret = FAIL;
}
}
STR_PREV(sp); /* drop the NUL just added */
return ret;
}
#if ((__STDC__ - 0) <= 0)
/* most traditional compilers won't optimize global variables */
JUMPSIG old_sigalrm;
unsigned int old_alarm;
#else
static volatile JUMPSIG old_sigalrm;
static volatile unsigned int old_alarm;
#endif
/*
* tryfile_lock_wait - lock an open retry file, waiting until an exclusive lock
* can be obtained; optionally, time out
*
* Returns DB_SUCCESS (good), DB_FAIL (bad),
* or DB_AGAIN (if file was removed from under us).
*/
static int
tryfile_lock_wait(fd, timeout)
int fd;
long timeout;
{
int ret;
if (timeout && JUMP_SETJMP(tryfile_jmp)) {
ret = DB_FAIL;
#ifdef ETIMEDOUT
errno = ETIMEDOUT;
#else
errno = EBUSY; /* not an ideal msg on most systems but better
* than none....
*/
#endif
} else {
/* lock retry file with optional timeout */
if (timeout) {
old_alarm = alarm(0);
JUMP_SETSIG(SIGALRM, tryfile_alarm, &old_sigalrm);
(void) alarm(timeout < 2 ? 2 : (unsigned)timeout);
}
if (lock_fd_wait(fd) == FAIL) {
ret = DB_FAIL;
} else {
struct stat st;
ret = (fstat(fd, &st) == 0 && st.st_nlink > 0)
? DB_SUCCEED : DB_AGAIN;
}
if (timeout) {
(void) alarm(0);
JUMP_CLEARSIG(SIGALRM, &old_sigalrm);
if (old_alarm) {
(void) alarm(old_alarm);
}
}
}
return ret;
}
/*
* tryfile_lock - lock an open retry file, non-blocking mode
*
* Returns DB_SUCCESS (good), DB_FAIL (bad),
* or DB_AGAIN (if file was removed from under us).
*/
static int
tryfile_lock(fd)
int fd;
{
int ret;
if (lock_fd(fd) == FAIL) {
ret = DB_FAIL;
} else {
struct stat st;
ret = (fstat(fd, &st) == 0 && st.st_nlink > 0) ? DB_SUCCEED : DB_AGAIN;
}
return ret;
}
/*
* tryfile_unlock - unlock a retry file locked with timeout
*/
static void
tryfile_unlock(fd)
int fd;
{
unlock_fd_wait(fd);
}
/*
* tryfile_alarm - alarm function
*/
/* ARGSUSED */
static void
tryfile_alarm(sig)
int sig __attribute__((unused));
{
JUMP_LONGJMP(tryfile_jmp, 1);
}
/*
* write_error_or_unlink - write an error description to a retry file, or remove it
*/
static void
write_error_or_unlink(fd, tryfile, error)
int fd;
char *tryfile;
struct error *error;
{
if (error == NULL) {
/* no error: remove file */
(void) unlink(tryfile);
} else {
char *e;
int elen;
/* write error code and message */
e = xprintf("%ld %s\n", error->info & ERR_MASK, error->message);
elen = (int)strlen(e);
(void) lseek(fd, (off_t) 0L, SEEK_SET);
(void) write(fd, e, (size_t) (elen + 1));
(void) xfree(e);
/* eliminate remaining bytes, to avoid confusing human readers */
(void) fsetsize(fd, (off_t)elen, (off_t)elen + 1);
/* flush changes */
#if defined(HAVE_FSYNC) && !defined(O_SYNC)
(void) fsync(fd);
#endif
}
}
/*
* read_retry_error - read an error description from a retry file
*/
/* ARGSUSED */
static void
read_retry_error(fd, tryfile, err)
int fd; /* FD for the retry file */
char *tryfile; /* name of the retry file */
struct error *err; /* ptr to struct to fill */
{
struct stat statbuf;
char *buf, *errmsg, *p;
long errcode;
size_t len;
/* assume that we can't read the file */
err->info = ERR_173;
err->message = "(no message in retry file)";
/* read the whole file, if it exists, and if it's not too big */
if (fstat(fd, &statbuf) == -1) {
if (errno != ENOENT) {
err->info |= ERR_NPOSTMAST;
}
err->message = xprintf("(fstat(%v) failed: %s)", tryfile, strerror(errno));
return;
}
if (statbuf.st_size > 0x7FFF) { /* XXX ??? */
err->info |= ERR_NPOSTMAST;
err->message = xprintf("(retry file %v is insanely large (%ld))",
tryfile, (long) statbuf.st_size);
return;
}
len = statbuf.st_size;
buf = xmalloc(len + 1);
if ((size_t) read(fd, buf, (size_t) (len + 1)) != len) {
err->message = xprintf("(could not read all %ld bytes of retry file %v: %s)",
(long) statbuf.st_size, tryfile, strerror(errno));
err->info |= ERR_NPOSTMAST;
xfree(buf);
return;
}
buf[len--] = '\0';
if (buf[len] == '\n') {
buf[len--] = '\0'; /* trim any trailing newline */
}
/* parse the error number and message */
errcode = 0;
errmsg = NULL;
p = buf;
while (isdigit((int) *p)) {
++p;
}
if (*p == ' ') {
*p++ = '\0';
errcode = atoi(buf);
errmsg = p;
}
/* if both code and message are parsable, use them */
if (errcode && errmsg && *errmsg) {
err->info = errcode;
err->message = COPY_STRING(errmsg);
}
/* free copy of file */
xfree(buf);
}
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1