Let's optionally issue a Varlink Synchronize() call in --follow mode when asked to terminate. This is useful so that the tool can be called and it is guaranteed it processed all messages generated before the request to exit before it exits. We want this in "systemd-run -v" in particular, so that we can be sure we are not missing any log output from the invoked service before it exits
204 lines
7.1 KiB
C
204 lines
7.1 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "build-path.h"
|
|
#include "escape.h"
|
|
#include "event-util.h"
|
|
#include "exit-status.h"
|
|
#include "fd-util.h"
|
|
#include "fork-journal.h"
|
|
#include "log.h"
|
|
#include "notify-recv.h"
|
|
#include "parse-util.h"
|
|
#include "process-util.h"
|
|
#include "signal-util.h"
|
|
#include "socket-util.h"
|
|
#include "strv.h"
|
|
|
|
static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
|
PidRef *child = ASSERT_PTR(userdata);
|
|
|
|
assert(si);
|
|
assert(si->si_pid == child->pid);
|
|
|
|
/* Let's first do some debug logging about the exit status of the child */
|
|
|
|
if (si->si_code == CLD_EXITED) {
|
|
if (si->si_status == EXIT_SUCCESS)
|
|
log_debug("journalctl " PID_FMT " exited successfully.", si->si_pid);
|
|
else
|
|
log_debug("journalctl " PID_FMT " died with a failure exit status %i, ignoring.", si->si_pid, si->si_status);
|
|
} else if (si->si_code == CLD_KILLED)
|
|
log_debug("journalctl " PID_FMT " was killed by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
|
|
else if (si->si_code == CLD_DUMPED)
|
|
log_debug("journalctl " PID_FMT " dumped core by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status));
|
|
else
|
|
log_debug("Got unexpected exit code %i via SIGCHLD, ignoring.", si->si_code);
|
|
|
|
/* And let's then fail the whole thing, because regardless what the exit status of the child is
|
|
* (i.e. even if successful), if it exits before sending READY=1 something is wrong. */
|
|
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Child " PID_FMT " died before sending notification message.", child->pid);
|
|
}
|
|
|
|
static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
|
PidRef *child = ASSERT_PTR(userdata);
|
|
int r;
|
|
|
|
assert(s);
|
|
assert(fd >= 0);
|
|
|
|
_cleanup_strv_free_ char **msg = NULL;
|
|
_cleanup_(pidref_done) PidRef sender = PIDREF_NULL;
|
|
r = notify_recv_strv(fd, &msg, /* ret_ucred= */ NULL, &sender);
|
|
if (r == -EAGAIN)
|
|
return 0;
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!pidref_equal(child, &sender)) {
|
|
log_warning("Received notification message from unexpected process " PID_FMT " (expected " PID_FMT "), ignoring.",
|
|
sender.pid, child->pid);
|
|
return 0;
|
|
}
|
|
|
|
if (strv_contains(msg, "READY=1"))
|
|
return sd_event_exit(sd_event_source_get_event(s), EXIT_SUCCESS);
|
|
|
|
const char *e = strv_find_startswith(msg, "ERRNO=");
|
|
if (e) {
|
|
int error;
|
|
|
|
r = safe_atoi(e, &error);
|
|
if (r < 0) {
|
|
log_debug_errno(r, "Received invalid ERRNO= notification message, ignoring: %s", e);
|
|
return 0;
|
|
}
|
|
if (error <= 0) {
|
|
log_debug("Received non-positive ERRNO= notification message, ignoring: %m");
|
|
return 0;
|
|
}
|
|
|
|
return -error;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int journal_fork(RuntimeScope scope, const char * const *units, PidRef *ret_pidref) {
|
|
int r;
|
|
|
|
assert(scope >= 0);
|
|
assert(scope < _RUNTIME_SCOPE_MAX);
|
|
assert(ret_pidref);
|
|
|
|
if (!is_main_thread())
|
|
return -EPERM;
|
|
|
|
if (strv_isempty((char**) units))
|
|
return 0;
|
|
|
|
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
|
r = sd_event_new(&event);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *notify_event_source = NULL;
|
|
_cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
|
|
_cleanup_free_ char *addr_string = NULL;
|
|
r = notify_socket_prepare(
|
|
event,
|
|
SD_EVENT_PRIORITY_NORMAL-10, /* We want the notification message from the child before the SIGCHLD */
|
|
on_child_notify,
|
|
&child,
|
|
&addr_string,
|
|
¬ify_event_source);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_event_source_set_exit_on_failure(notify_event_source, true);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
_cleanup_strv_free_ char **argv = strv_new(
|
|
"journalctl",
|
|
"-q",
|
|
"--follow",
|
|
"--no-pager",
|
|
"--lines=1",
|
|
"--synchronize-on-exit=yes");
|
|
if (!argv)
|
|
return log_oom_debug();
|
|
|
|
STRV_FOREACH(u, units)
|
|
if (strv_extendf(&argv,
|
|
scope == RUNTIME_SCOPE_SYSTEM ? "--unit=%s" : "--user-unit=%s",
|
|
*u) < 0)
|
|
return log_oom_debug();
|
|
|
|
if (DEBUG_LOGGING) {
|
|
_cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
|
|
log_debug("Invoking '%s' as child.", strnull(l));
|
|
}
|
|
|
|
BLOCK_SIGNALS(SIGCHLD);
|
|
|
|
r = pidref_safe_fork_full(
|
|
"(journalctl)",
|
|
(const int[3]) { -EBADF, STDOUT_FILENO, STDERR_FILENO },
|
|
/* except_fds= */ NULL,
|
|
/* n_except_fds= */ 0,
|
|
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO,
|
|
&child);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0) {
|
|
/* In the child: */
|
|
|
|
if (setenv("NOTIFY_SOCKET", addr_string, /* overwrite= */ true) < 0) {
|
|
log_debug_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
|
|
_exit(EXIT_MEMORY);
|
|
}
|
|
|
|
r = invoke_callout_binary(argv[0], (char**) argv);
|
|
log_debug_errno(r, "Failed to invoke journalctl: %m");
|
|
_exit(EXIT_EXEC);
|
|
}
|
|
|
|
_cleanup_(sd_event_source_disable_unrefp) sd_event_source *child_event_source = NULL;
|
|
r = event_add_child_pidref(event, &child_event_source, &child, WEXITED, on_child_exit, &child);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_event_source_set_exit_on_failure(child_event_source, true);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
(void) sd_event_source_set_description(child_event_source, "fork-journal-child");
|
|
|
|
r = sd_event_loop(event);
|
|
if (r < 0)
|
|
return r;
|
|
assert(r == 0);
|
|
|
|
*ret_pidref = TAKE_PIDREF(child);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void journal_terminate(PidRef *pidref) {
|
|
int r;
|
|
|
|
if (!pidref_is_set(pidref))
|
|
return;
|
|
|
|
r = pidref_kill(pidref, SIGTERM);
|
|
if (r < 0)
|
|
log_debug_errno(r, "Failed to send SIGTERM to journalctl child " PID_FMT ", ignoring: %m", pidref->pid);
|
|
|
|
(void) pidref_wait_for_terminate_and_check("journalctl", pidref, /* flags= */ 0);
|
|
pidref_done(pidref);
|
|
}
|