#include "exim.h"
+#include "transports/smtp.h"
#include <assert.h>
addr2->transport_return = addr->transport_return;
addr2->basic_errno = addr->basic_errno;
addr2->more_errno = addr->more_errno;
+ addr2->delivery_usec = addr->delivery_usec;
addr2->special_action = addr->special_action;
addr2->message = addr->message;
addr2->user_message = addr->user_message;
}
#endif
-return d_log_interface(s, sp, pp);
+s = d_log_interface(s, sp, pp);
+
+if (testflag(addr, af_tcp_fastopen))
+ s = string_catn(s, sp, pp, US" TFO", 4);
+
+return s;
}
addr->host_used
|| Ustrcmp(addr->transport->driver_name, "smtp") == 0
|| Ustrcmp(addr->transport->driver_name, "lmtp") == 0
- ? addr->message : NULL);
+ ? addr->message : NULL);
deliver_host_port = save_port;
deliver_host_address = save_address;
}
+
+void
+timesince(struct timeval * diff, struct timeval * then)
+{
+gettimeofday(diff, NULL);
+diff->tv_sec -= then->tv_sec;
+if ((diff->tv_usec -= then->tv_usec) < 0)
+ {
+ diff->tv_sec--;
+ diff->tv_usec += 1000*1000;
+ }
+}
+
+
+
+static uschar *
+string_timediff(struct timeval * diff)
+{
+static uschar buf[sizeof("0.000s")];
+
+if (diff->tv_sec >= 5 || !LOGGING(millisec))
+ return readconf_printtime((int)diff->tv_sec);
+
+sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
+return buf;
+}
+
+
+uschar *
+string_timesince(struct timeval * then)
+{
+struct timeval diff;
+
+timesince(&diff, then);
+return string_timediff(&diff);
+}
+
/******************************************************************************/
}
#ifndef DISABLE_PRDR
- if (addr->flags & af_prdr_used)
+ if (testflag(addr, af_prdr_used))
s = string_catn(s, &size, &ptr, US" PRDR", 5);
#endif
- if (addr->flags & af_chunking_used)
+ if (testflag(addr, af_chunking_used))
s = string_catn(s, &size, &ptr, US" K", 2);
}
if (LOGGING(queue_time))
s = string_append(s, &size, &ptr, 2, US" QT=",
- readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+ string_timesince(&received_time));
if (LOGGING(deliver_time))
- s = string_append(s, &size, &ptr, 2, US" DT=",
- readconf_printtime(addr->more_errno));
+ {
+ struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
+ s = string_append(s, &size, &ptr, 2, US" DT=", string_timediff(&diff));
+ }
/* string_cat() always leaves room for the terminator. Release the
store we used to build the line after writing it. */
later (with a log entry). */
if (!*sender_address && message_age >= ignore_bounce_errors_after)
- setflag(addr, af_ignore_error);
+ addr->prop.ignore_error = TRUE;
/* Freeze the message if requested, or if this is a bounce message (or other
message with null sender) and this address does not have its own errors
to ignore occurs later, instead of sending a message. Logging of freezing
occurs later, just before writing the -H file. */
- if ( !testflag(addr, af_ignore_error)
+ if ( !addr->prop.ignore_error
&& ( addr->special_action == SPECIAL_FREEZE
|| (sender_address[0] == 0 && !addr->prop.errors_address)
) )
addr->return_filename =
spool_fname(US"msglog", message_subdir, message_id,
string_sprintf("-%d-%d", getpid(), return_count++));
-
+
if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
{
common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
|| (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags)
|| (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], &addr2->delivery_usec, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->transport,
sizeof(transport_instance *))) != sizeof(transport_instance *)
len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags));
len = read(pfd[pipe_read], &addr2->basic_errno, sizeof(int));
len = read(pfd[pipe_read], &addr2->more_errno, sizeof(int));
+ len = read(pfd[pipe_read], &addr2->delivery_usec, sizeof(int));
len = read(pfd[pipe_read], &addr2->special_action, sizeof(int));
len = read(pfd[pipe_read], &addr2->transport,
sizeof(transport_instance *));
while (addr_local)
{
- time_t delivery_start;
- int deliver_time;
+ struct timeval delivery_start;
+ struct timeval deliver_time;
address_item *addr2, *addr3, *nextaddr;
int logflags = LOG_MAIN;
int logchar = dont_deliver? '*' : '=';
BOOL ok =
tp == next->transport
&& !previously_transported(next, TRUE)
- && (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file))
+ && testflag(addr, af_pfr) == testflag(next, af_pfr)
+ && testflag(addr, af_file) == testflag(next, af_file)
&& (!uses_lp || Ustrcmp(next->local_part, addr->local_part) == 0)
&& (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0)
&& same_strings(next->prop.errors_address, addr->prop.errors_address)
single delivery. */
deliver_set_expansions(addr);
- delivery_start = time(NULL);
+
+ gettimeofday(&delivery_start, NULL);
deliver_local(addr, FALSE);
- deliver_time = (int)(time(NULL) - delivery_start);
+ timesince(&deliver_time, &delivery_start);
/* If a shadow transport (which must perforce be another local transport), is
defined, and its condition is met, we must pass the message to the shadow
/* Done with this address */
- if (result == OK) addr2->more_errno = deliver_time;
+ if (result == OK)
+ {
+ addr2->more_errno = deliver_time.tv_sec;
+ addr2->delivery_usec = deliver_time.tv_usec;
+ }
post_process_one(addr2, result, logflags, DTYPE_TRANSPORT, logchar);
/* If a pipe delivery generated text to be sent back, the result may be
all short items, the writes will all be atomic and we should never find
ourselves in the position of having read an incomplete item. "Short" in this
case can mean up to about 1K in the case when there is a long error message
-associated with an address. */
+associated with an address.
+
+write(3) [Linux] says that atomic writes are not interleaved with each other.
+Not more.
+
+*/
DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
(int)p->pid, eop? "ended" : "not ended");
r->flags = *ptr++;
r->key = string_copy(ptr);
while (*ptr++);
- memcpy(&(r->basic_errno), ptr, sizeof(r->basic_errno));
+ memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno));
ptr += sizeof(r->basic_errno);
- memcpy(&(r->more_errno), ptr, sizeof(r->more_errno));
+ memcpy(&r->more_errno, ptr, sizeof(r->more_errno));
ptr += sizeof(r->more_errno);
r->message = *ptr ? string_copy(ptr) : NULL;
DEBUG(D_deliver|D_retry) debug_printf(" added %s item\n",
#ifndef DISABLE_PRDR
case 'P':
- addr->flags |= af_prdr_used;
+ setflag(addr, af_prdr_used);
break;
#endif
case 'K':
- addr->flags |= af_chunking_used;
+ setflag(addr, af_chunking_used);
+ break;
+
+ case 'T':
+ setflag(addr, af_tcp_fastopen);
break;
case 'D':
DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr);
addr->transport_return = *ptr++;
addr->special_action = *ptr++;
- memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
+ memcpy(&addr->basic_errno, ptr, sizeof(addr->basic_errno));
ptr += sizeof(addr->basic_errno);
- memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
+ memcpy(&addr->more_errno, ptr, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(&(addr->flags), ptr, sizeof(addr->flags));
+ memcpy(&addr->delivery_usec, ptr, sizeof(addr->delivery_usec));
+ ptr += sizeof(addr->delivery_usec);
+ memcpy(&addr->flags, ptr, sizeof(addr->flags));
ptr += sizeof(addr->flags);
addr->message = *ptr ? string_copy(ptr) : NULL;
while(*ptr++);
{
readycount--;
if (par_read_pipe(poffset, FALSE)) /* Finished with this pipe */
- {
for (;;) /* Loop for signals */
{
pid_t endedpid = waitpid(pid, &status, 0);
"%d (errno = %d) from waitpid() for process %d",
(int)endedpid, errno, (int)pid);
}
- }
}
}
-
static void
rmt_dlv_checked_write(int fd, char id, char subid, void * buf, int size)
{
-uschar writebuffer[PIPE_HEADER_SIZE + BIG_BUFFER_SIZE];
-int header_length;
+uschar pipe_header[PIPE_HEADER_SIZE+1];
+size_t total_len = PIPE_HEADER_SIZE + size;
+
+struct iovec iov[2] = {
+ { pipe_header, PIPE_HEADER_SIZE }, /* indication about the data to expect */
+ { buf, size } /* *the* data */
+};
+
int ret;
/* we assume that size can't get larger then BIG_BUFFER_SIZE which currently is set to 16k */
/* complain to log if someone tries with buffer sizes we can't handle*/
-if (size > 99999)
+if (size > BIG_BUFFER_SIZE-1)
{
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "Failed writing transport result to pipe: can't handle buffers > 99999 bytes. truncating!\n");
- size = 99999;
+ "Failed writing transport result to pipe: can't handle buffers > %d bytes. truncating!\n",
+ BIG_BUFFER_SIZE-1);
+ size = BIG_BUFFER_SIZE;
}
-/* to keep the write() atomic we build header in writebuffer and copy buf behind */
-/* two write() calls would increase the complexity of reading from pipe */
+/* Should we check that we do not write more than PIPE_BUF? What whould
+that help? */
/* convert size to human readable string prepended by id and subid */
-header_length = snprintf(CS writebuffer, PIPE_HEADER_SIZE+1, "%c%c%05d", id, subid, size);
-if (header_length != PIPE_HEADER_SIZE)
- {
+if (PIPE_HEADER_SIZE != snprintf(CS pipe_header, PIPE_HEADER_SIZE+1, "%c%c%05d", id, subid, size))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n");
- writebuffer[0] = '\0';
- }
DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%d,final:%s\n",
- id, subid, size, writebuffer);
+ id, subid, size, pipe_header);
-if (buf && size > 0)
- memcpy(writebuffer + PIPE_HEADER_SIZE, buf, size);
-
-size += PIPE_HEADER_SIZE;
-if ((ret = write(fd, writebuffer, size)) != size)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
+if ((ret = writev(fd, iov, 2)) != total_len)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe (%d of %d bytes): %s",
+ ret == -1 ? 0 : ret, total_len,
ret == -1 ? strerror(errno) : "short write");
}
if (continue_transport)
{
BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
- if (ok && addr->host_list)
+
+ /* If the transport is about to override the host list do not check
+ it here but take the cost of running the transport process to discover
+ if the continued_hostname connection is suitable. This is a layering
+ violation which is unfortunate as it requires we haul in the smtp
+ include file. */
+
+ if (ok)
{
- host_item *h;
- ok = FALSE;
- for (h = addr->host_list; h; h = h->next)
- if (Ustrcmp(h->name, continue_hostname) == 0)
-/*XXX should also check port here */
- { ok = TRUE; break; }
+ smtp_transport_options_block * ob;
+
+ if ( !( Ustrcmp(tp->info->driver_name, "smtp") == 0
+ && (ob = (smtp_transport_options_block *)tp->options_block)
+ && ob->hosts_override && ob->hosts
+ )
+ && addr->host_list
+ )
+ {
+ host_item * h;
+ ok = FALSE;
+ for (h = addr->host_list; h; h = h->next)
+ if (Ustrcmp(h->name, continue_hostname) == 0)
+ /*XXX should also check port here */
+ { ok = TRUE; break; }
+ }
}
/* Addresses not suitable; defer or queue for fallback hosts (which
that it can use either of them, though it prefers O_NONBLOCK, which
distinguishes between EOF and no-more-data. */
+/* The data appears in a timely manner and we already did a select on
+all pipes, so I do not see a reason to use non-blocking IO here
+
#ifdef O_NONBLOCK
(void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
#else
(void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
#endif
+*/
/* If the maximum number of subprocesses already exist, wait for a process
to finish. If we ran out of file descriptors, parmax will have been reduced
}
#ifndef DISABLE_PRDR
- if (addr->flags & af_prdr_used)
+ if (testflag(addr, af_prdr_used))
rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
#endif
- if (addr->flags & af_chunking_used)
+ if (testflag(addr, af_chunking_used))
rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
+ if (testflag(addr, af_tcp_fastopen))
+ rmt_dlv_checked_write(fd, 'T', '0', NULL, 0);
+
memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
{
sprintf(CS big_buffer, "%c%.500s", r->flags, r->key);
ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
- memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno));
+ memcpy(ptr, &r->basic_errno, sizeof(r->basic_errno));
ptr += sizeof(r->basic_errno);
- memcpy(ptr, &(r->more_errno), sizeof(r->more_errno));
+ memcpy(ptr, &r->more_errno, sizeof(r->more_errno));
ptr += sizeof(r->more_errno);
if (!r->message) *ptr++ = 0; else
{
sprintf(CS big_buffer, "%c%c", addr->transport_return, addr->special_action);
ptr = big_buffer + 2;
- memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
+ memcpy(ptr, &addr->basic_errno, sizeof(addr->basic_errno));
ptr += sizeof(addr->basic_errno);
- memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno));
+ memcpy(ptr, &addr->more_errno, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(ptr, &(addr->flags), sizeof(addr->flags));
+ memcpy(ptr, &addr->delivery_usec, sizeof(addr->delivery_usec));
+ ptr += sizeof(addr->delivery_usec);
+ memcpy(ptr, &addr->flags, sizeof(addr->flags));
ptr += sizeof(addr->flags);
if (!addr->message) *ptr++ = 0; else
{
ptr += sprintf(CS ptr, "%.256s", addr->host_used->name) + 1;
ptr += sprintf(CS ptr, "%.64s", addr->host_used->address) + 1;
- memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
+ memcpy(ptr, &addr->host_used->port, sizeof(addr->host_used->port));
ptr += sizeof(addr->host_used->port);
/* DNS lookup status */
if (rc != spool_read_hdrerror)
{
- received_time = 0;
+ received_time.tv_sec = received_time.tv_usec = 0;
+ /*XXX subsec precision?*/
for (i = 0; i < 6; i++)
- received_time = received_time * BASE_62 + tab62[id[i] - '0'];
+ received_time.tv_sec = received_time.tv_sec * BASE_62 + tab62[id[i] - '0'];
}
/* If we've had this malformed message too long, sling it. */
- if (now - received_time > keep_malformed)
+ if (now - received_time.tv_sec > keep_malformed)
{
Uunlink(spool_fname(US"msglog", message_subdir, id, US""));
Uunlink(spool_fname(US"input", message_subdir, id, US"-D"));
uschar *type;
p->uid = uid;
p->gid = gid;
- setflag(p, af_uid_set |
- af_gid_set |
- af_allow_file |
- af_allow_pipe |
- af_allow_reply);
+ setflag(p, af_uid_set);
+ setflag(p, af_gid_set);
+ setflag(p, af_allow_file);
+ setflag(p, af_allow_pipe);
+ setflag(p, af_allow_reply);
/* Find the name of the system filter's appropriate pfr transport */
addr->local_part = addr->address;
addr->message =
US"filter autoreply generated syntactically invalid recipient";
- setflag(addr, af_ignore_error);
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ addr->prop.ignore_error = TRUE;
+ (void) post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
addr2->host_list = addr->host_list;
addr2->fallback_hosts = addr->fallback_hosts;
addr2->prop.errors_address = addr->prop.errors_address;
- copyflag(addr2, addr, af_hide_child | af_local_host_removed);
+ copyflag(addr2, addr, af_hide_child);
+ copyflag(addr2, addr, af_local_host_removed);
DEBUG(D_deliver|D_route)
- {
debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
"routing %s\n"
"Routing for %s copied from %s\n",
addr2->address, addr2->address, addr->address);
- }
}
}
} /* Continue with routing the next address. */
if (journal_fd < 0)
{
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
-
+
if ((journal_fd = Uopen(fname,
#ifdef O_CLOEXEC
O_CLOEXEC |
DEBUG(D_deliver)
debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n");
-cancel_cutthrough_connection(TRUE, "deliveries are done");
+cancel_cutthrough_connection(TRUE, US"deliveries are done");
/* Root privilege is no longer needed */
FILE *f = fdopen(fd, "wb");
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
uschar * bound;
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
DEBUG(D_deliver)
debug_printf("sending error message to: %s\n", sender_address);
if (sender_address[0] == 0 && !addr_failed->prop.errors_address)
{
if ( !testflag(addr_failed, af_retry_timedout)
- && !testflag(addr_failed, af_ignore_error))
- {
+ && !addr_failed->prop.ignore_error)
log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
"failure is neither frozen nor ignored (it's been ignored)");
- }
- setflag(addr_failed, af_ignore_error);
+
+ addr_failed->prop.ignore_error = TRUE;
}
/* If the first address on the list has af_ignore_error set, just remove
it from the list, throw away any saved message file, log it, and
mark the recipient done. */
- if ( testflag(addr_failed, af_ignore_error)
+ if ( addr_failed->prop.ignore_error
|| ( addr_failed->dsn_flags & rf_dsnflags
&& (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
) )
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
{ /* Dummy transport for headers add */
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
transport_instance tb = {0};
tctx.u.fd = fileno(f);
if (rc != 0)
{
uschar *s = US"";
- if (now - received_time < retry_maximum_timeout && !addr_defer)
+ if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
{
addr_defer = (address_item *)(+1);
deliver_freeze = TRUE;
if (LOGGING(queue_time_overall))
log_write(0, LOG_MAIN, "Completed QT=%s",
- readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+ string_timesince(&received_time));
else
log_write(0, LOG_MAIN, "Completed");
{
int count;
int show_time;
- int queue_time = time(NULL) - received_time;
+ int queue_time = time(NULL) - received_time.tv_sec;
/* When running in the test harness, there's an option that allows us to
fudge this time so as to get repeatability of the tests. Take the first
FILE *wmf = NULL;
FILE *f = fdopen(fd, "wb");
uschar * bound;
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
if (warn_message_file)
if (!(wmf = Ufopen(warn_message_file, "rb")))
void
delivery_re_exec(int exec_type)
{
-uschar * s;
+uschar * where;
if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
{
#ifdef SUPPORT_TLS
if (cutthrough.is_tls)
{
- smtp_peer_options |= PEER_OFFERED_TLS;
+ smtp_peer_options |= OPTION_TLS;
sending_ip_address = cutthrough.snd_ip;
sending_port = cutthrough.snd_port;
- s = US"socketpair";
+ where = US"socketpair";
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0)
goto fail;
- s = US"fork";
+ where = US"fork";
if ((pid = fork()) < 0)
goto fail;
- else if (pid == 0) /* child: fork again to totally dosconnect */
+ else if (pid == 0) /* child: fork again to totally disconnect */
{
close(pfd[1]);
if ((pid = fork()))
}
else
{
- cancel_cutthrough_connection(TRUE, "non-continued delivery");
+ cancel_cutthrough_connection(TRUE, US"non-continued delivery");
(void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id);
}
-/* Control does not return here. */
+return; /* compiler quietening; control does not reach here. */
fail:
log_write(0,
LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
- "delivery re-exec failed: %s", strerror(errno));
+ "delivery re-exec %s failed: %s", where, strerror(errno));
/* Get here if exec_type == CEE_EXEC_EXIT.
Note: this must be _exit(), not exit(). */