/* * 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 "cockpitpipetransport.h" #include "cockpitframe.h" #include "cockpitpipe.h" #include #include #include #include #include #include #include #include /** * CockpitPipeTransport: * * A #CockpitTransport implementation that shuttles data over a * #CockpitPipe. See doc/protocol.md for information on how the * framing looks ... including the MSB length prefix. */ struct _CockpitPipeTransport { CockpitTransport parent_instance; gchar *name; CockpitPipe *pipe; gboolean closed; gulong read_sig; gulong close_sig; }; struct _CockpitPipeTransportClass { CockpitTransportClass parent_class; }; enum { PROP_0, PROP_NAME, PROP_PIPE, }; G_DEFINE_TYPE (CockpitPipeTransport, cockpit_pipe_transport, COCKPIT_TYPE_TRANSPORT); static void cockpit_pipe_transport_init (CockpitPipeTransport *self) { } static void on_pipe_read (CockpitPipe *pipe, GByteArray *input, gboolean end_of_data, gpointer user_data) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (user_data); cockpit_transport_read_from_pipe (COCKPIT_TRANSPORT (self), self->name, pipe, &self->closed, input, end_of_data); } static void on_pipe_close (CockpitPipe *pipe, const gchar *problem, gpointer user_data) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (user_data); gboolean is_cockpit; GError *error = NULL; gint status; self->closed = TRUE; /* This function is called by the base class when it is closed */ if (cockpit_pipe_get_pid (pipe, NULL)) { is_cockpit = g_str_equal (self->name, "cockpit-bridge") || g_str_equal (self->name, "cockpit-session"); if (problem == NULL || g_str_equal (problem, "internal-error")) { status = cockpit_pipe_exit_status (pipe); if (WIFSIGNALED (status) && WTERMSIG (status) == SIGTERM) problem = "terminated"; else if (is_cockpit && WIFEXITED (status) && WEXITSTATUS (status) == 127) problem = "no-cockpit"; // cockpit-bridge not installed else if (WIFEXITED (status) && WEXITSTATUS (status) == 255) problem = "terminated"; // failed or got a signal, etc. else if (!g_spawn_check_exit_status (status, &error)) { problem = "internal-error"; if (is_cockpit) g_warning ("%s: bridge program failed: %s", self->name, error->message); else g_debug ("%s: process failed: %s", self->name, error->message); g_error_free (error); } } else if (g_str_equal (problem, "not-found")) { if (is_cockpit) { g_message ("%s: failed to execute bridge: not found", self->name); problem = "no-cockpit"; } else { g_debug ("%s: failed to run: not found", self->name); } } } g_debug ("%s: closed%s%s", self->name, problem ? ": " : "", problem ? problem : ""); cockpit_transport_emit_closed (COCKPIT_TRANSPORT (self), problem); } static void cockpit_pipe_transport_constructed (GObject *object) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (object); G_OBJECT_CLASS (cockpit_pipe_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); } static void cockpit_pipe_transport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (object); switch (prop_id) { case PROP_NAME: g_value_set_string (value, self->name); break; case PROP_PIPE: g_value_set_object (value, cockpit_pipe_transport_get_pipe (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cockpit_pipe_transport_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (object); switch (prop_id) { case PROP_PIPE: self->pipe = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cockpit_pipe_transport_finalize (GObject *object) { CockpitPipeTransport *self = COCKPIT_PIPE_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_clear_object (&self->pipe); G_OBJECT_CLASS (cockpit_pipe_transport_parent_class)->finalize (object); } static void cockpit_pipe_transport_send (CockpitTransport *transport, const gchar *channel_id, GBytes *payload) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (transport); GBytes *prefix; gchar *prefix_str; gsize payload_len; gsize channel_len; if (self->closed) { g_debug ("dropping message on closed transport"); return; } channel_len = channel_id ? strlen (channel_id) : 0; payload_len = g_bytes_get_size (payload); prefix_str = g_strdup_printf ("%" G_GSIZE_FORMAT "\n%s\n", channel_len + 1 + payload_len, channel_id ? channel_id : ""); prefix = g_bytes_new_take (prefix_str, strlen (prefix_str)); cockpit_pipe_write (self->pipe, prefix); cockpit_pipe_write (self->pipe, payload); g_bytes_unref (prefix); g_debug ("%s: queued %" G_GSIZE_FORMAT " byte payload", self->name, payload_len); } static void cockpit_pipe_transport_close (CockpitTransport *transport, const gchar *problem) { CockpitPipeTransport *self = COCKPIT_PIPE_TRANSPORT (transport); cockpit_pipe_close (self->pipe, problem); } static void cockpit_pipe_transport_class_init (CockpitPipeTransportClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); CockpitTransportClass *transport_class = COCKPIT_TRANSPORT_CLASS (klass); transport_class->send = cockpit_pipe_transport_send; transport_class->close = cockpit_pipe_transport_close; gobject_class->constructed = cockpit_pipe_transport_constructed; gobject_class->get_property = cockpit_pipe_transport_get_property; gobject_class->set_property = cockpit_pipe_transport_set_property; gobject_class->finalize = cockpit_pipe_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)); } /** * cockpit_pipe_transport_new: * @pipe: the pipe to send data over * * Create a new CockpitPipeTransport for a pipe * * Returns: (transfer full): the new transport */ CockpitTransport * cockpit_pipe_transport_new (CockpitPipe *pipe) { return g_object_new (COCKPIT_TYPE_PIPE_TRANSPORT, "pipe", pipe, NULL); } /** * cockpit_pipe_transport_new_fds: * @name: name for debugging * @in_fd: the file descriptor to read from * @out_fd: the file descriptor to write to * * Create a new CockpitPipeTransport for a pair * of file descriptors. * * Returns: (transfer full): the new transport */ CockpitTransport * cockpit_pipe_transport_new_fds (const gchar *name, gint in_fd, gint out_fd) { CockpitTransport *transport; CockpitPipe *pipe; pipe = cockpit_pipe_new (name, in_fd, out_fd); transport = cockpit_pipe_transport_new (pipe); g_object_unref (pipe); return transport; } CockpitPipe * cockpit_pipe_transport_get_pipe (CockpitPipeTransport *self) { g_return_val_if_fail (COCKPIT_IS_PIPE_TRANSPORT (self), NULL); return self->pipe; } /** * cockpit_transport_read_from_pipe: * * Meant to be used in a "read" handler for a #CockpitPipe * Closed is pointer to a boolean value that may be updated * during the read and parse loop. */ void cockpit_transport_read_from_pipe (CockpitTransport *self, const gchar *logname, CockpitPipe *pipe, gboolean *closed, GByteArray *input, gboolean end_of_data) { GBytes *message; GBytes *payload; gchar *channel; gssize size; gsize i; /* This may be updated during the loop. */ g_assert (closed != NULL); g_object_ref (self); while (!*closed) { size = cockpit_frame_parse (input->data, input->len, &i); if (size == 0) { if (!end_of_data) g_debug ("%s: want more data", logname); break; } else if (size < 0) { g_warning ("%s: incorrect protocol: received invalid length prefix", logname); cockpit_pipe_close (pipe, "protocol-error"); break; } else if (input->len < i + size) { g_debug ("%s: want more data 2", logname); break; } message = cockpit_pipe_consume (input, i, size, 0); payload = cockpit_transport_parse_frame (message, &channel); if (payload) { g_debug ("%s: received a %d byte payload", logname, (int)size); cockpit_transport_emit_recv (self, channel, payload); g_bytes_unref (payload); g_free (channel); } g_bytes_unref (message); } if (end_of_data) { /* Received a partial message */ if (input->len > 0) { g_debug ("%s: received truncated %d byte frame", logname, input->len); cockpit_pipe_close (pipe, "disconnected"); } } g_object_unref (self); }