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:
Matthew Barnes 2015-08-10 10:47:59 -04:00
parent f1973d7557
commit cfc52adfea
2 changed files with 198 additions and 1 deletions

View File

@ -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)

View File

@ -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);
}