#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
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++);
maxpipe = 0;
FD_ZERO(&select_pipes);
for (poffset = 0; poffset < remote_max_parallel; poffset++)
- {
if (parlist[poffset].pid != 0)
{
int fd = parlist[poffset].fd;
FD_SET(fd, &select_pipes);
if (fd > maxpipe) maxpipe = fd;
}
- }
/* Stick in a 60-second timeout, just in case. */
{
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);
}
- }
}
}
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
if (!ok)
{
- DEBUG(D_deliver) debug_printf("not suitable for continue_transport\n");
+ DEBUG(D_deliver) debug_printf("not suitable for continue_transport (%s)\n",
+ Ustrcmp(continue_transport, tp->name) != 0
+ ? string_sprintf("tpt %s vs %s", continue_transport, tp->name)
+ : string_sprintf("no host matching %s", continue_hostname));
if (serialize_key) enq_end(serialize_key);
if (addr->fallback_hosts && !fallback)
/* Set a flag indicating whether there are further addresses that list
the continued host. This tells the transport to leave the channel open,
- but not to pass it to another delivery process. */
+ but not to pass it to another delivery process. We'd like to do that
+ for non-continue_transport cases too but the knowlege of which host is
+ connected to is too hard to manage. Perhaps we need a finer-grain
+ interface to the transport. */
- for (next = addr_remote; next; next = next->next)
+ for (next = addr_remote; next && !continue_more; next = next->next)
{
host_item *h;
for (h = next->host_list; h; h = h->next)
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
predictable settings for each delivery process, so do something explicit
here rather they rely on the fixed reset in the random number function. */
- random_seed = running_in_test_harness? 42 + 2*delivery_count : 0;
+ random_seed = running_in_test_harness ? 42 + 2*delivery_count : 0;
/* Set close-on-exec on the pipe so that it doesn't get passed on to
a new process that may be forked to do another delivery down the same
}
#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 */
complications for local addresses. */
if (process_recipients != RECIP_IGNORE)
- {
for (i = 0; i < recipients_count; i++)
- {
if (!tree_search(tree_nonrecipients, recipients_list[i].address))
{
recipient_item *r = recipients_list + i;
}
#endif
}
- }
- }
DEBUG(D_deliver)
{
not exist. In both cases, dbm_file is NULL. */
if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
- {
DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
debug_printf("no retry data available\n");
- }
/* Scan the current batch of new addresses, to handle pipes, files and
autoreplies, and determine which others are ready for routing. */
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 */
}
if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0,
&domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
!= OK)
- {
if (rc == DEFER)
{
addr->basic_errno = ERRNO_LISTDEFER;
addr->next = okaddr;
okaddr = addr;
}
- }
else
{
addr->basic_errno = ERRNO_QUEUE_DOMAIN;
&addr_succeed, v_none)) == DEFER)
retry_add_item(addr,
addr->router->retry_use_local_part
- ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
+ ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
: string_sprintf("R:%s", addr->domain),
0);
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);
/* Write the original email out */
+ tctx.u.fd = fileno(f);
tctx.options = topt_add_return_path | topt_no_body;
- transport_write_message(fileno(f), &tctx, 0);
+ transport_write_message(&tctx, 0);
fflush(f);
fprintf(f,"\n--%s--\n", bound);
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);
tctx.tblock = &tb;
tctx.options = topt;
tb.add_headers = dsnnotifyhdr;
- transport_write_message(fileno(f), &tctx, 0);
+ transport_write_message(&tctx, 0);
}
fflush(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")))
fflush(f);
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
+ tctx.u.fd = fileno(f);
tctx.options = topt_add_return_path | topt_no_body;
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
/* Write the original email out */
- transport_write_message(fileno(f), &tctx, 0);
+ transport_write_message(&tctx, 0);
fflush(f);
fprintf(f,"\n--%s--\n", bound);
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 */
+ else if (pid == 0) /* child: fork again to totally dosconnect */
{
+ close(pfd[1]);
+ if ((pid = fork()))
+ _exit(pid ? EXIT_FAILURE : EXIT_SUCCESS);
smtp_proxy_tls(big_buffer, big_buffer_size, pfd[0], 5*60);
exim_exit(0);
}
+ close(pfd[0]);
+ waitpid(pid, NULL, 0);
(void) close(channel_fd); /* release the client socket */
channel_fd = pfd[1];
}
}
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(). */