/*
* sigchld.c - event for SIGCHLD
* Copyright (c) 2013 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "config.h"
#include <errno.h>
#include <event2/event.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "src/conf.h"
#include "src/util.h"
#include "src/tlsdate.h"
/* Returns 1 if a death was handled, otherwise 0. */
int
handle_child_death (struct state *state)
{
siginfo_t info;
int ret;
info.si_pid = 0;
ret = waitid (P_ALL, -1, &info, WEXITED|WNOHANG);
if (ret == -1)
{
if (errno == ECHILD)
return 0;
perror ("[event:%s] waitid() failed after SIGCHLD", __func__);
return 0;
}
if (info.si_pid == 0)
{
return 0;
}
if (info.si_pid == state->setter_pid)
{
report_setter_error (&info);
event_base_loopbreak (state->base);
return 1;
}
if (info.si_pid != state->tlsdate_pid)
{
error ("[event:%s] SIGCHLD for an unknown process -- "
"pid:%d uid:%d status:%d code:%d", __func__,
info.si_pid, info.si_uid, info.si_status, info.si_code);
return 1;
}
verb ("[event:%s] tlsdate reaped => "
"pid:%d uid:%d status:%d code:%d", __func__,
info.si_pid, info.si_uid, info.si_status, info.si_code);
/* If it was still active, remove it. */
event_del (state->events[E_TLSDATE_TIMEOUT]);
state->running = 0;
state->tlsdate_pid = 0;
/* Clean exit - don't rerun! */
if (info.si_status == 0)
return 1;
verb_debug ("[event:%s] scheduling a retry", __func__);
/* Rerun a failed tlsdate */
if (state->backoff < MAX_SANE_BACKOFF)
state->backoff *= 2;
/* If there is no resolver, call tlsdate directly. */
if (!state->events[E_RESOLVER])
{
trigger_event (state, E_TLSDATE, state->backoff);
return 1;
}
/* Run tlsdate even if the resolver doesn't come back. */
trigger_event (state, E_TLSDATE, RESOLVER_TIMEOUT + state->backoff);
/* Schedule the resolver. This is always done after tlsdate in case there
* is no resolver.
*/
trigger_event (state, E_RESOLVER, state->backoff);
return 1;
}
/* Returns 1 if a death was handled, otherwise 0. */
int
handle_child_stop (struct state *state)
{
/* Handle unexpected external interactions */
siginfo_t info;
int ret;
info.si_pid = 0;
ret = waitid (P_ALL, -1, &info, WSTOPPED|WCONTINUED|WNOHANG);
if (ret == -1)
{
if (errno == ECHILD)
return 0;
perror ("[event:%s] waitid() failed after SIGCHLD", __func__);
return 0;
}
if (info.si_pid == 0)
return 0;
info ("[event:%s] a child has been STOPPED or CONTINUED. Killing it.",
__func__);
/* Kill it then catch the next SIGCHLD. */
if (kill (info.si_pid, SIGKILL))
{
if (errno == EPERM)
fatal ("[event:%s] cannot terminate STOPPED privileged child",
__func__);
if (errno == ESRCH)
info ("[event:%s] child gone before we could kill it",
__func__);
}
return 1;
}
void
action_sigchld (evutil_socket_t fd, short what, void *arg)
{
struct state *state = arg;
verb_debug ("[event:%s] a child process has SIGCHLD'd!", __func__);
/* Process SIGCHLDs in two steps: death and stopped until all
* pending children are sorted.
*/
if (!handle_child_death (state) && !handle_child_stop (state))
verb ("[event:%s] SIGCHLD fired but no children ready!", __func__);
while (handle_child_death (state) || handle_child_stop (state));
}
int
setup_sigchld_event (struct state *state, int persist)
{
state->events[E_SIGCHLD] = event_new (state->base, SIGCHLD,
EV_SIGNAL| (persist ? EV_PERSIST : 0),
action_sigchld, state);
if (!state->events[E_SIGCHLD])
return 1;
/* Make sure this is lower than SAVE so we get any error
* messages back from the time setter.
*/
event_priority_set (state->events[E_SIGCHLD], PRI_NET);
event_add (state->events[E_SIGCHLD], NULL);
return 0;
}