daemon: Catch systemd journal messages by redirecting stdout
libostree logs messages to systemd's journal and also to stdout. Redirect our own stdout back to ourselves so we can capture those messages and pass them on to clients. Admittedly hokey but avoids hacking libostree directly (for now).
This commit is contained in:
parent
f1973d7557
commit
cfc52adfea
@ -46,13 +46,14 @@ librpmostreed_la_CFLAGS = \
|
||||
$(PKGDEP_RPMOSTREE_CFLAGS) \
|
||||
-DG_LOG_DOMAIN=\"rpm-ostreed\" \
|
||||
-I$(srcdir)/src/daemon \
|
||||
-I$(srcdir)/libglnx \
|
||||
-I$(srcdir)/src/lib \
|
||||
-I$(libglnx_srcpath) \
|
||||
$(NULL)
|
||||
|
||||
librpmostreed_la_LIBADD = \
|
||||
$(AM_LDFLAGS) \
|
||||
$(PKGDEP_RPMOSTREE_LIBS) \
|
||||
librpmostreepriv.la \
|
||||
librpmostree-1.la \
|
||||
$(CAP_LIBS)
|
||||
$(NULL)
|
||||
|
@ -31,6 +31,8 @@
|
||||
|
||||
#include "libgsystem.h"
|
||||
#include <libglnx.h>
|
||||
#include <gio/gunixinputstream.h>
|
||||
#include <gio/gunixoutputstream.h>
|
||||
|
||||
/**
|
||||
* SECTION:daemon
|
||||
@ -65,6 +67,8 @@ struct _Sysroot
|
||||
GFileMonitor *monitor;
|
||||
guint sig_changed;
|
||||
guint64 last_monitor_event;
|
||||
|
||||
guint stdout_source_id;
|
||||
};
|
||||
|
||||
struct _SysrootClass
|
||||
@ -97,6 +101,187 @@ static Sysroot *_sysroot_instance;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
GWeakRef sysroot;
|
||||
GOutputStream *real_stdout;
|
||||
GDataInputStream *data_stream;
|
||||
} StdoutClosure;
|
||||
|
||||
static void
|
||||
stdout_closure_free (StdoutClosure *closure)
|
||||
{
|
||||
g_weak_ref_set (&closure->sysroot, NULL);
|
||||
g_clear_object (&closure->real_stdout);
|
||||
g_clear_object (&closure->data_stream);
|
||||
g_slice_free (StdoutClosure, closure);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sysroot_stdout_ready_cb (GPollableInputStream *pollable_stream,
|
||||
StdoutClosure *closure)
|
||||
{
|
||||
glnx_unref_object Sysroot *sysroot = NULL;
|
||||
glnx_unref_object RPMOSTreeTransaction *transaction = NULL;
|
||||
GMemoryInputStream *memory_stream;
|
||||
GBufferedInputStream *buffered_stream;
|
||||
char buffer[1024];
|
||||
gconstpointer stream_buffer;
|
||||
gsize stream_buffer_size;
|
||||
gsize total_bytes_read = 0;
|
||||
gboolean have_line = FALSE;
|
||||
GError *local_error = NULL;
|
||||
|
||||
sysroot = g_weak_ref_get (&closure->sysroot);
|
||||
if (sysroot != NULL)
|
||||
transaction = transaction_monitor_ref_active_transaction (sysroot->transaction_monitor);
|
||||
|
||||
/* XXX Would very much like g_buffered_input_stream_fill_nonblocking().
|
||||
* Much of this function is a clumsy and inefficient attempt to
|
||||
* achieve the same thing.
|
||||
*
|
||||
* See: https://bugzilla.gnome.org/726797
|
||||
*/
|
||||
|
||||
buffered_stream = G_BUFFERED_INPUT_STREAM (closure->data_stream);
|
||||
memory_stream = (GMemoryInputStream *) g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (buffered_stream));
|
||||
|
||||
while (local_error == NULL)
|
||||
{
|
||||
gssize n_read;
|
||||
|
||||
n_read = g_pollable_input_stream_read_nonblocking (pollable_stream,
|
||||
buffer,
|
||||
sizeof (buffer),
|
||||
NULL, &local_error);
|
||||
|
||||
if (n_read > 0)
|
||||
{
|
||||
/* XXX Gotta use GBytes so the data gets copied. */
|
||||
g_autoptr(GBytes) bytes = g_bytes_new (buffer, n_read);
|
||||
g_memory_input_stream_add_bytes (memory_stream, bytes);
|
||||
total_bytes_read += n_read;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
||||
goto out;
|
||||
|
||||
g_clear_error (&local_error);
|
||||
|
||||
read_another_line:
|
||||
|
||||
/* Fill the buffered stream with the data we just put in the
|
||||
* memory stream, then peek at the buffer to see if it's safe
|
||||
* to call g_data_input_stream_read_line() without blocking.
|
||||
*
|
||||
* XXX Oye, there's gotta be an easier way to do this...
|
||||
*/
|
||||
|
||||
/* This should never fail since it's just reading from memory. */
|
||||
g_buffered_input_stream_fill (buffered_stream, total_bytes_read, NULL, NULL);
|
||||
|
||||
stream_buffer = g_buffered_input_stream_peek_buffer (buffered_stream, &stream_buffer_size);
|
||||
have_line = (memchr (stream_buffer, '\n', stream_buffer_size) != NULL);
|
||||
|
||||
if (have_line)
|
||||
{
|
||||
g_autofree char *line = NULL;
|
||||
gsize length;
|
||||
|
||||
line = g_data_input_stream_read_line (closure->data_stream,
|
||||
&length, NULL, &local_error);
|
||||
|
||||
if (local_error != NULL)
|
||||
goto out;
|
||||
|
||||
/* If there's an active transaction, forward the line to the
|
||||
* transaction's owner through the "Message" signal. Otherwise
|
||||
* dump it to the non-redirected standard output stream. */
|
||||
if (transaction != NULL)
|
||||
{
|
||||
rpmostree_transaction_emit_message (transaction, line);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This is essentially puts(), don't care about errors. */
|
||||
g_output_stream_write_all (closure->real_stdout,
|
||||
line, length, NULL, NULL, NULL);
|
||||
g_output_stream_write_all (closure->real_stdout,
|
||||
"\n", 1, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
goto read_another_line;
|
||||
}
|
||||
|
||||
out:
|
||||
if (local_error != NULL)
|
||||
{
|
||||
g_warning ("Failed to read stdout pipe: %s", local_error->message);
|
||||
g_clear_error (&local_error);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sysroot_setup_stdout_redirect (Sysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GInputStream) stream = NULL;
|
||||
g_autoptr(GSource) source = NULL;
|
||||
StdoutClosure *closure;
|
||||
gint pipefd[2];
|
||||
gboolean ret = FALSE;
|
||||
|
||||
/* XXX libostree logs messages to systemd's journal and also to stdout.
|
||||
* Redirect our own stdout back to ourselves so we can capture those
|
||||
* messages and pass them on to clients. Admittedly hokey but avoids
|
||||
* hacking libostree directly (for now). */
|
||||
|
||||
closure = g_slice_new0 (StdoutClosure);
|
||||
g_weak_ref_set (&closure->sysroot, self);
|
||||
|
||||
/* Save the real stdout before overwriting its file descriptor. */
|
||||
closure->real_stdout = g_unix_output_stream_new (dup (STDOUT_FILENO), FALSE);
|
||||
|
||||
if (pipe (pipefd) < 0)
|
||||
{
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "pipe() failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dup2 (pipefd[1], STDOUT_FILENO) < 0)
|
||||
{
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "dup2() failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
stream = g_memory_input_stream_new ();
|
||||
closure->data_stream = g_data_input_stream_new (stream);
|
||||
g_clear_object (&stream);
|
||||
|
||||
stream = g_unix_input_stream_new (pipefd[0], FALSE);
|
||||
|
||||
source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (stream),
|
||||
NULL);
|
||||
/* Transfer ownership of the StdoutClosure. */
|
||||
g_source_set_callback (source,
|
||||
(GSourceFunc) sysroot_stdout_ready_cb,
|
||||
closure,
|
||||
(GDestroyNotify) stdout_closure_free);
|
||||
closure = NULL;
|
||||
|
||||
self->stdout_source_id = g_source_attach (source, NULL);
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
out:
|
||||
if (closure != NULL)
|
||||
stdout_closure_free (closure);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GFile *
|
||||
_build_file (const char *first, ...)
|
||||
{
|
||||
@ -346,6 +531,9 @@ sysroot_finalize (GObject *object)
|
||||
g_clear_object (&self->cancellable);
|
||||
g_clear_object (&self->monitor);
|
||||
|
||||
if (self->stdout_source_id > 0)
|
||||
g_source_remove (self->stdout_source_id);
|
||||
|
||||
G_OBJECT_CLASS (sysroot_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@ -373,6 +561,7 @@ static void
|
||||
sysroot_constructed (GObject *object)
|
||||
{
|
||||
Sysroot *self = SYSROOT (object);
|
||||
GError *local_error = NULL;
|
||||
|
||||
g_signal_connect (RPMOSTREE_SYSROOT(self), "g-authorize-method",
|
||||
G_CALLBACK (auth_check_root_or_access_denied), NULL);
|
||||
@ -388,6 +577,13 @@ sysroot_constructed (GObject *object)
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
/* Failure is not fatal, but the client may miss some messages. */
|
||||
if (!sysroot_setup_stdout_redirect (self, &local_error))
|
||||
{
|
||||
g_critical ("%s", local_error->message);
|
||||
g_clear_error (&local_error);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (sysroot_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user