/* * This file is part of Cockpit. * * Copyright (C) 2014 Red Hat, Inc. * * Cockpit is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * Cockpit is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Cockpit; If not, see . */ #include "config.h" #include "cockpitinteracttransport.h" #include "common/cockpitpipe.h" #include /** * CockpitInteractTransport: * * A #CockpitTransport implementation that shuttles data over a * #CockpitPipe connected to stdio and handles framing in a way * that it's more usable for debugging channels. */ struct _CockpitInteractTransport { CockpitTransport parent_instance; gchar *name; gchar *delimiter; gsize delimiter_len; gboolean colored; CockpitPipe *pipe; gulong read_sig; gulong close_sig; }; typedef struct { CockpitTransportClass parent_class; } CockpitInteractTransportClass; enum { PROP_0, PROP_NAME, PROP_PIPE, PROP_BOUNDARY, PROP_COLOR }; G_DEFINE_TYPE (CockpitInteractTransport, cockpit_interact_transport, COCKPIT_TYPE_TRANSPORT); static void cockpit_interact_transport_init (CockpitInteractTransport *self) { } static void on_pipe_read (CockpitPipe *pipe, GByteArray *input, gboolean end_of_data, gpointer user_data) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (user_data); GBytes *message; GBytes *payload; gchar *channel; guint8 *pos; guint32 size; for (;;) { pos = NULL; if (input->len > 0) pos = (guint8 *)g_strstr_len ((gchar *)input->data, input->len, self->delimiter); if (!pos) { if (!end_of_data) g_debug ("%s: want more data", self->name); break; } size = pos - input->data; message = cockpit_pipe_consume (input, 0, size, self->delimiter_len); payload = cockpit_transport_parse_frame (message, &channel); if (payload) { g_debug ("%s: received a %d byte payload", self->name, (int)size); cockpit_transport_emit_recv ((CockpitTransport *)self, channel, payload); g_bytes_unref (payload); g_free (channel); } g_bytes_unref (message); } } static void on_pipe_close (CockpitPipe *pipe, const gchar *problem, gpointer user_data) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (user_data); g_debug ("%s: closed%s%s", self->name, problem ? ": " : "", problem ? problem : ""); cockpit_transport_emit_closed (COCKPIT_TRANSPORT (self), problem); } static void cockpit_interact_transport_constructed (GObject *object) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object); G_OBJECT_CLASS (cockpit_interact_transport_parent_class)->constructed (object); g_return_if_fail (self->pipe != NULL); g_object_get (self->pipe, "name", &self->name, NULL); self->read_sig = g_signal_connect (self->pipe, "read", G_CALLBACK (on_pipe_read), self); self->close_sig = g_signal_connect (self->pipe, "close", G_CALLBACK (on_pipe_close), self); self->delimiter_len = strlen (self->delimiter); } static void cockpit_interact_transport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object); switch (prop_id) { case PROP_NAME: g_value_set_string (value, self->name); break; case PROP_PIPE: g_value_set_object (value, self->pipe); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cockpit_interact_transport_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object); switch (prop_id) { case PROP_PIPE: self->pipe = g_value_dup_object (value); break; case PROP_BOUNDARY: self->delimiter = g_strdup_printf ("\n%s\n", g_value_get_string (value)); self->delimiter_len = strlen (self->delimiter); break; case PROP_COLOR: self->colored = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cockpit_interact_transport_finalize (GObject *object) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (object); g_signal_handler_disconnect (self->pipe, self->read_sig); g_signal_handler_disconnect (self->pipe, self->close_sig); g_free (self->name); g_free (self->delimiter); g_clear_object (&self->pipe); G_OBJECT_CLASS (cockpit_interact_transport_parent_class)->finalize (object); } static void cockpit_interact_transport_send (CockpitTransport *transport, const gchar *channel_id, GBytes *payload) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (transport); GBytes *prefix; GBytes *suffix; GBytes *color; gchar *prefix_str; if (self->colored) { color = g_bytes_new_static ("\x1b[1m", 4); cockpit_pipe_write (self->pipe, color); g_bytes_unref (color); } prefix_str = g_strdup_printf ("%s\n", channel_id ? channel_id : ""); prefix = g_bytes_new_take (prefix_str, strlen (prefix_str)); cockpit_pipe_write (self->pipe, prefix); g_bytes_unref (prefix); cockpit_pipe_write (self->pipe, payload); suffix = g_bytes_new (self->delimiter, self->delimiter_len); cockpit_pipe_write (self->pipe, suffix); g_bytes_unref (suffix); if (self->colored) { color = g_bytes_new_static ("\x1b[0m", 4); cockpit_pipe_write (self->pipe, color); g_bytes_unref (color); } g_debug ("%s: queued %d byte payload", self->name, (int)g_bytes_get_size (payload)); } static void cockpit_interact_transport_close (CockpitTransport *transport, const gchar *problem) { CockpitInteractTransport *self = COCKPIT_INTERACT_TRANSPORT (transport); cockpit_pipe_close (self->pipe, problem); } static void cockpit_interact_transport_class_init (CockpitInteractTransportClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); CockpitTransportClass *transport_class = COCKPIT_TRANSPORT_CLASS (klass); transport_class->send = cockpit_interact_transport_send; transport_class->close = cockpit_interact_transport_close; gobject_class->constructed = cockpit_interact_transport_constructed; gobject_class->get_property = cockpit_interact_transport_get_property; gobject_class->set_property = cockpit_interact_transport_set_property; gobject_class->finalize = cockpit_interact_transport_finalize; g_object_class_override_property (gobject_class, PROP_NAME, "name"); g_object_class_install_property (gobject_class, PROP_PIPE, g_param_spec_object ("pipe", NULL, NULL, COCKPIT_TYPE_PIPE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_BOUNDARY, g_param_spec_string ("boundary", NULL, NULL, "---", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_COLOR, g_param_spec_boolean ("color", NULL, NULL, FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } CockpitTransport * cockpit_interact_transport_new (gint in_fd, gint out_fd, const gchar *boundary) { CockpitTransport *transport; CockpitPipe *pipe; pipe = cockpit_pipe_new ("interact", in_fd, out_fd); transport = g_object_new (COCKPIT_TYPE_INTERACT_TRANSPORT, "pipe", pipe, "boundary", boundary, "color", (gboolean)isatty (out_fd), NULL); g_object_unref (pipe); return transport; }