service-process.c revision a8aec60b5359e5ea7fc7a9464217ae3626df8785
f79d43bbe70a01454049b77d6f15f6369744959eStéphane Graber/* Copyright (c) 2005-2013 Dovecot authors, see the included COPYING file */
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "common.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "array.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "aqueue.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "ioloop.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "istream.h"
9afe19d634946d50eab30e3b90cb5cebcde39eeaDaniel Lezcano#include "ostream.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "write-full.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "base64.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "hash.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "str.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "strescape.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "llist.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "hostpid.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "env-util.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "fd-close-on-exec.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "restrict-access.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "restrict-process-size.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "eacces-error.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "master-service.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "master-service-settings.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "dup2-array.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "service.h"
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include "service-anvil.h"
7f95145833bb24f54e037f73ecc37444d6635697Dwight Engen#include "service-log.h"
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normand#include "service-process-notify.h"
10fba81b9d0221b8e47aa1e0b43236413b7d28dfMichel Normand#include "service-process.h"
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normand
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normand#include <stdlib.h>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include <unistd.h>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include <fcntl.h>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include <syslog.h>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include <signal.h>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano#include <sys/wait.h>
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanostatic void
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcanoservice_dup_fds(struct service *service)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano{
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano struct service_listener *const *listeners;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano ARRAY_TYPE(dup2) dups;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano string_t *listener_names;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano int fd = MASTER_LISTEN_FD_FIRST;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano unsigned int i, count, socket_listener_count, ssl_socket_count;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano /* stdin/stdout is already redirected to /dev/null. Other master fds
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano should have been opened with fd_close_on_exec() so we don't have to
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano worry about them.
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano because the destination fd might be another one's source fd we have
b4578c5b380130a41a69b5b49c970157acaf1dbbDwight Engen to be careful not to overwrite anything. dup() the fd when needed */
b4578c5b380130a41a69b5b49c970157acaf1dbbDwight Engen
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano socket_listener_count = 0;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano listeners = array_get(&service->listeners, &count);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano t_array_init(&dups, count + 10);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano listener_names = t_str_new(256);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano switch (service->type) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_LOG:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano i_assert(fd == MASTER_LISTEN_FD_FIRST);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano services_log_dup2(&dups, service->list, fd,
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano &socket_listener_count);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano fd += socket_listener_count;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano break;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_ANVIL:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, service_anvil_global->log_fdpass_fd[0],
10fba81b9d0221b8e47aa1e0b43236413b7d28dfMichel Normand MASTER_ANVIL_LOG_FDPASS_FD);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano /* nonblocking anvil fd must be the first one. anvil treats it
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano as the master's fd */
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, service_anvil_global->nonblocking_fd[0], fd++);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, service_anvil_global->blocking_fd[0], fd++);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano socket_listener_count += 2;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano break;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano default:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano break;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano /* anvil/log fds have no names */
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano for (i = MASTER_LISTEN_FD_FIRST; i < (unsigned int)fd; i++)
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano str_append_c(listener_names, '\t');
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano /* first add non-ssl listeners */
f79d43bbe70a01454049b77d6f15f6369744959eStéphane Graber for (i = 0; i < count; i++) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (listeners[i]->fd != -1 &&
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano (listeners[i]->type != SERVICE_LISTENER_INET ||
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano !listeners[i]->set.inetset.set->ssl)) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano str_append_tabescaped(listener_names, listeners[i]->name);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano str_append_c(listener_names, '\t');
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, listeners[i]->fd, fd++);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano socket_listener_count++;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
f79d43bbe70a01454049b77d6f15f6369744959eStéphane Graber /* then ssl-listeners */
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano ssl_socket_count = 0;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano for (i = 0; i < count; i++) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (listeners[i]->fd != -1 &&
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano listeners[i]->type == SERVICE_LISTENER_INET &&
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano listeners[i]->set.inetset.set->ssl) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano str_append_tabescaped(listener_names, listeners[i]->name);
99e4008cad9e959b683c6f48411fcf15a92be3b5Michel Normand str_append_c(listener_names, '\t');
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, listeners[i]->fd, fd++);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano socket_listener_count++;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano ssl_socket_count++;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano if (service->login_notify_fd != -1) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, service->login_notify_fd,
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano MASTER_LOGIN_NOTIFY_FD);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano switch (service->type) {
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_LOG:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_ANVIL:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_CONFIG:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, null_fd, MASTER_ANVIL_FD);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano break;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_UNKNOWN:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_LOGIN:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano case SERVICE_TYPE_STARTUP:
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, service_anvil_global->blocking_fd[1],
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano MASTER_ANVIL_FD);
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano break;
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano }
d823d5b966f49d975a09a8512d084389d6d7ffc7dlezcano dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
if (service->type != SERVICE_TYPE_ANVIL) {
dup2_append(&dups, service->list->master_dead_pipe_fd[1],
MASTER_DEAD_FD);
} else {
dup2_append(&dups, global_master_dead_pipe_fd[1],
MASTER_DEAD_FD);
}
if (service->type == SERVICE_TYPE_LOG) {
/* keep stderr as-is. this is especially important when
log_path=/dev/stderr, but might be helpful even in other
situations for logging startup errors */
} else {
/* set log file to stderr. dup2() here immediately so that
we can set up logging to it without causing any log messages
to be lost. */
i_assert(service->log_fd[1] != -1);
env_put("LOG_SERVICE=1");
if (dup2(service->log_fd[1], STDERR_FILENO) < 0)
i_fatal("dup2(log fd) failed: %m");
i_set_failure_internal();
}
/* make sure we don't leak syslog fd. try to do it as late as possible,
but also before dup2()s in case syslog fd is one of them. */
closelog();
if (dup2_array(&dups) < 0)
i_fatal("service(%s): dup2s failed", service->set->name);
i_assert(fd == MASTER_LISTEN_FD_FIRST + (int)socket_listener_count);
env_put(t_strdup_printf("SOCKET_COUNT=%d", socket_listener_count));
env_put(t_strdup_printf("SSL_SOCKET_COUNT=%d", ssl_socket_count));
env_put(t_strdup_printf("SOCKET_NAMES=%s", str_c(listener_names)));
}
static void
drop_privileges(struct service *service)
{
struct restrict_access_settings rset;
bool disallow_root;
unsigned int len;
if (service->vsz_limit != 0)
restrict_process_size(service->vsz_limit);
restrict_access_init(&rset);
rset.uid = service->uid;
rset.gid = service->gid;
rset.privileged_gid = service->privileged_gid;
rset.chroot_dir = *service->set->chroot == '\0' ? NULL :
service->set->chroot;
if (rset.chroot_dir != NULL) {
/* drop trailing / if it exists */
len = strlen(rset.chroot_dir);
if (rset.chroot_dir[len-1] == '/')
rset.chroot_dir = t_strndup(rset.chroot_dir, len-1);
}
rset.extra_groups = service->extra_gids;
restrict_access_set_env(&rset);
if (service->set->drop_priv_before_exec) {
disallow_root = service->type == SERVICE_TYPE_LOGIN;
restrict_access(&rset, NULL, disallow_root);
}
}
static void service_process_setup_config_environment(struct service *service)
{
const struct master_service_settings *set = service->list->service_set;
switch (service->type) {
case SERVICE_TYPE_CONFIG:
env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
service->config_file_path, NULL));
break;
case SERVICE_TYPE_LOG:
/* give the log's configuration directly, so it won't depend
on config process */
env_put("DOVECONF_ENV=1");
env_put(t_strconcat("LOG_PATH=", set->log_path, NULL));
env_put(t_strconcat("INFO_LOG_PATH=", set->info_log_path, NULL));
env_put(t_strconcat("DEBUG_LOG_PATH=", set->debug_log_path, NULL));
env_put(t_strconcat("LOG_TIMESTAMP=", set->log_timestamp, NULL));
env_put(t_strconcat("SYSLOG_FACILITY=", set->syslog_facility, NULL));
env_put("SSL=no");
break;
default:
env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
services_get_config_socket_path(service->list), NULL));
break;
}
}
static void
service_process_setup_environment(struct service *service, unsigned int uid)
{
master_service_env_clean();
env_put(MASTER_IS_PARENT_ENV"=1");
service_process_setup_config_environment(service);
env_put(t_strdup_printf(MASTER_CLIENT_LIMIT_ENV"=%u",
service->client_limit));
env_put(t_strdup_printf(MASTER_PROCESS_LIMIT_ENV"=%u",
service->process_limit));
env_put(t_strdup_printf(MASTER_SERVICE_IDLE_KILL_ENV"=%u",
service->idle_kill));
if (service->set->service_count != 0) {
env_put(t_strdup_printf(MASTER_SERVICE_COUNT_ENV"=%u",
service->set->service_count));
}
env_put(t_strdup_printf(MASTER_UID_ENV"=%u", uid));
env_put(t_strdup_printf(MY_HOSTNAME_ENV"=%s", my_hostname));
env_put(t_strdup_printf(MY_HOSTDOMAIN_ENV"=%s", my_hostdomain()));
if (!service->set->master_set->version_ignore)
env_put(MASTER_DOVECOT_VERSION_ENV"="PACKAGE_VERSION);
if (ssl_manual_key_password != NULL && service->have_inet_listeners) {
/* manually given SSL password. give it only to services
that have inet listeners. */
env_put(t_strconcat(MASTER_SSL_KEY_PASSWORD_ENV"=",
ssl_manual_key_password, NULL));
}
if (service->type == SERVICE_TYPE_ANVIL &&
service_anvil_global->restarted)
env_put("ANVIL_RESTARTED=1");
}
static void service_process_status_timeout(struct service_process *process)
{
service_error(process->service,
"Initial status notification not received in %d "
"seconds, killing the process",
SERVICE_FIRST_STATUS_TIMEOUT_SECS);
if (kill(process->pid, SIGKILL) < 0 && errno != ESRCH) {
service_error(process->service, "kill(%s, SIGKILL) failed: %m",
dec2str(process->pid));
}
timeout_remove(&process->to_status);
}
struct service_process *service_process_create(struct service *service)
{
static unsigned int uid_counter = 0;
struct service_process *process;
unsigned int uid = ++uid_counter;
pid_t pid;
bool process_forked;
i_assert(service->status_fd[0] != -1);
if (service->to_throttle != NULL) {
/* throttling service, don't create new processes */
return NULL;
}
if (service->list->destroying) {
/* these services are being destroyed, no point in creating
new processes now */
return NULL;
}
if (service->type == SERVICE_TYPE_ANVIL &&
service_anvil_global->pid != 0) {
pid = service_anvil_global->pid;
uid = service_anvil_global->uid;
process_forked = FALSE;
} else {
pid = fork();
process_forked = TRUE;
}
if (pid < 0) {
service_error(service, "fork() failed: %m");
return NULL;
}
if (pid == 0) {
/* child */
service_process_setup_environment(service, uid);
service_dup_fds(service);
drop_privileges(service);
process_exec(service->executable, NULL);
}
process = i_new(struct service_process, 1);
process->service = service;
process->refcount = 1;
process->pid = pid;
process->uid = uid;
if (process_forked) {
process->to_status =
timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
service_process_status_timeout, process);
}
process->available_count = service->client_limit;
service->process_count++;
service->process_avail++;
DLLIST_PREPEND(&service->processes, process);
service_list_ref(service->list);
hash_table_insert(service_pids, POINTER_CAST(process->pid), process);
if (service->type == SERVICE_TYPE_ANVIL && process_forked)
service_anvil_process_created(process);
return process;
}
void service_process_destroy(struct service_process *process)
{
struct service *service = process->service;
struct service_list *service_list = service->list;
DLLIST_REMOVE(&service->processes, process);
hash_table_remove(service_pids, POINTER_CAST(process->pid));
if (process->available_count > 0)
service->process_avail--;
service->process_count--;
i_assert(service->process_avail <= service->process_count);
if (process->to_status != NULL)
timeout_remove(&process->to_status);
if (process->to_idle != NULL)
timeout_remove(&process->to_idle);
if (service->list->log_byes != NULL)
service_process_notify_add(service->list->log_byes, process);
process->destroyed = TRUE;
service_process_unref(process);
if (service->process_count < service->process_limit &&
service->type == SERVICE_TYPE_LOGIN)
service_login_notify(service, FALSE);
service_list_unref(service_list);
}
void service_process_ref(struct service_process *process)
{
i_assert(process->refcount > 0);
process->refcount++;
}
void service_process_unref(struct service_process *process)
{
i_assert(process->refcount > 0);
if (--process->refcount > 0)
return;
i_assert(process->destroyed);
i_free(process);
}
static const char *
get_exit_status_message(struct service *service, enum fatal_exit_status status)
{
switch (status) {
case FATAL_LOGOPEN:
return "Can't open log file";
case FATAL_LOGWRITE:
return "Can't write to log file";
case FATAL_LOGERROR:
return "Internal logging error";
case FATAL_OUTOFMEM:
if (service->vsz_limit == 0)
return "Out of memory";
return t_strdup_printf("Out of memory (service %s { vsz_limit=%u MB }, "
"you may need to increase it)",
service->set->name,
(unsigned int)(service->vsz_limit/1024/1024));
case FATAL_EXEC:
return "exec() failed";
case FATAL_DEFAULT:
return "Fatal failure";
}
return NULL;
}
static void
log_coredump(struct service *service, string_t *str, int status)
{
#ifdef WCOREDUMP
int signum = WTERMSIG(status);
if (WCOREDUMP(status)) {
str_append(str, " (core dumped)");
return;
}
if (signum != SIGABRT && signum != SIGSEGV && signum != SIGBUS)
return;
/* let's try to figure out why we didn't get a core dump */
if (core_dumps_disabled) {
str_printfa(str, " (core dumps disabled)");
return;
}
#ifndef HAVE_PR_SET_DUMPABLE
if (!service->set->drop_priv_before_exec && service->uid != 0) {
str_printfa(str, " (core not dumped - set service %s "
"{ drop_priv_before_exec=yes })",
service->set->name);
return;
}
if (*service->set->privileged_group != '\0' && service->uid != 0) {
str_printfa(str, " (core not dumped - service %s "
"{ privileged_group } prevented it)",
service->set->name);
return;
}
#else
if (!service->set->login_dump_core &&
service->type == SERVICE_TYPE_LOGIN) {
str_printfa(str, " (core not dumped - add -D parameter to "
"service %s { executable }", service->set->name);
return;
}
#endif
str_append(str, " (core not dumped)");
#endif
}
static void
service_process_get_status_error(string_t *str, struct service_process *process,
int status, bool *default_fatal_r)
{
struct service *service = process->service;
const char *msg;
*default_fatal_r = FALSE;
str_printfa(str, "service(%s): child %s ", service->set->name,
dec2str(process->pid));
if (WIFSIGNALED(status)) {
str_printfa(str, "killed with signal %d", WTERMSIG(status));
log_coredump(service, str, status);
return;
}
if (!WIFEXITED(status)) {
str_printfa(str, "died with status %d", status);
return;
}
status = WEXITSTATUS(status);
if (status == 0) {
str_truncate(str, 0);
return;
}
str_printfa(str, "returned error %d", status);
msg = get_exit_status_message(service, status);
if (msg != NULL)
str_printfa(str, " (%s)", msg);
if (status == FATAL_DEFAULT)
*default_fatal_r = TRUE;
}
static void service_process_log(struct service_process *process,
bool default_fatal, const char *str)
{
const char *data;
if (process->service->log_fd[1] == -1) {
i_error("%s", str);
return;
}
/* log it via the log process in charge of handling
this process's logging */
data = t_strdup_printf("%d %s %s %s\n",
process->service->log_process_internal_fd,
dec2str(process->pid),
default_fatal ? "DEFAULT-FATAL" : "FATAL", str);
if (write(process->service->list->master_log_fd[1],
data, strlen(data)) < 0) {
i_error("write(log process) failed: %m");
i_error("%s", str);
}
}
void service_process_log_status_error(struct service_process *process,
int status)
{
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
/* fast path */
return;
}
T_BEGIN {
string_t *str = t_str_new(256);
bool default_fatal;
service_process_get_status_error(str, process, status,
&default_fatal);
if (str_len(str) > 0)
service_process_log(process, default_fatal, str_c(str));
} T_END;
}