/* * This file is part of Cockpit. * * Copyright (C) 2015 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 "cockpitchannel.h" #include "common/cockpitjson.h" #include "common/cockpitpipetransport.h" #include #include /* * The bridge implements two channel types. * * 'uppercase': Make all data upper case * 'lowercase': Make all data lower case * * By default only the first one is available. If run with --lower * then the latter is available. */ static gboolean opt_lower; static gboolean opt_upper; static gboolean opt_show_count; static gboolean opt_problem; static GHashTable *channels; static GType mock_case_channel_get_type (void) G_GNUC_CONST; typedef struct { CockpitChannel parent; gchar (* function) (gchar); } MockCaseChannel; typedef CockpitChannelClass MockCaseChannelClass; G_DEFINE_TYPE (MockCaseChannel, mock_case_channel, COCKPIT_TYPE_CHANNEL); static void mock_case_channel_recv (CockpitChannel *channel, GBytes *message) { MockCaseChannel *self = (MockCaseChannel *)channel; GByteArray *array = g_bytes_unref_to_array (g_bytes_ref (message)); GBytes *bytes; gsize i; for (i = 0; i < array->len; i++) array->data[i] = self->function(array->data[i]); bytes = g_byte_array_free_to_bytes (array); cockpit_channel_send (channel, bytes, FALSE); g_bytes_unref (bytes); } static gboolean mock_case_channel_control (CockpitChannel *channel, const gchar *command, JsonObject *options) { if (g_strcmp0 (command, "close-later") == 0) cockpit_channel_close (channel, "closed"); return TRUE; } static void mock_case_channel_init (MockCaseChannel *self) { } static void mock_case_channel_constructed (GObject *obj) { MockCaseChannel *self = (MockCaseChannel *)obj; const gchar *payload = NULL; JsonObject *options; JsonObject *ready = json_object_new (); const gchar *env = g_getenv ("COCKPIT_TEST_PARAM_ENV"); G_OBJECT_CLASS (mock_case_channel_parent_class)->constructed (obj); options = cockpit_channel_get_options (COCKPIT_CHANNEL (obj)); if (!cockpit_json_get_string (options, "payload", NULL, &payload)) g_assert_not_reached (); if (g_strcmp0 (payload, "upper") == 0) self->function = g_ascii_toupper; else if (g_strcmp0 (payload, "lower") == 0) self->function = g_ascii_tolower; else g_assert_not_reached (); if (env) json_object_set_string_member (ready, "test-env", env); if (opt_show_count) json_object_set_int_member (ready, "count", g_hash_table_size (channels)); cockpit_channel_ready (COCKPIT_CHANNEL (self), ready); json_object_unref (ready); } static void mock_case_channel_class_init (MockCaseChannelClass *klass) { CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = mock_case_channel_constructed; channel_class->recv = mock_case_channel_recv; channel_class->control = mock_case_channel_control; } static gboolean init_received; static gboolean opt_lower; static void on_channel_closed (CockpitChannel *channel, const gchar *problem, gpointer user_data) { g_hash_table_remove (channels, cockpit_channel_get_id (channel)); } static void process_init (CockpitTransport *transport, JsonObject *options) { gint64 version = -1; if (!cockpit_json_get_int (options, "version", -1, &version)) { g_warning ("invalid version field in init message"); cockpit_transport_close (transport, "protocol-error"); } if (version == 1) { g_debug ("received init message"); init_received = TRUE; } else { g_message ("unsupported version of cockpit protocol: %" G_GINT64_FORMAT, version); cockpit_transport_close (transport, "not-supported"); } } static void process_open (CockpitTransport *transport, const gchar *channel_id, JsonObject *options) { CockpitChannel *channel; GType channel_type; const gchar *payload; if (!channel_id) { g_warning ("Caller tried to open channel with invalid id"); cockpit_transport_close (transport, "protocol-error"); } else if (g_hash_table_lookup (channels, channel_id)) { g_warning ("Caller tried to reuse a channel that's already in use"); cockpit_transport_close (transport, "protocol-error"); } else { if (!cockpit_json_get_string (options, "payload", NULL, &payload)) payload = NULL; /* This will close with "not-supported" */ channel_type = COCKPIT_TYPE_CHANNEL; if ((opt_lower && g_strcmp0 (payload, "lower") == 0) || (opt_upper && g_strcmp0 (payload, "upper") == 0)) channel_type = mock_case_channel_get_type (); channel = g_object_new (channel_type, "transport", transport, "id", channel_id, "options", options, NULL); g_hash_table_insert (channels, g_strdup (channel_id), channel); g_signal_connect (channel, "closed", G_CALLBACK (on_channel_closed), NULL); } } static gboolean on_transport_control (CockpitTransport *transport, const char *command, const gchar *channel_id, JsonObject *options, GBytes *message, gpointer user_data) { if (g_str_equal (command, "init")) { process_init (transport, options); return TRUE; } else if (!init_received) { g_warning ("caller did not send 'init' message first"); cockpit_transport_close (transport, "protocol-error"); return TRUE; } else if (g_str_equal (command, "open")) { process_open (transport, channel_id, options); return TRUE; } return FALSE; } static void on_closed_set_flag (CockpitTransport *transport, const gchar *problem, gpointer user_data) { gboolean *flag = user_data; *flag = TRUE; } static void send_init_command (CockpitTransport *transport) { JsonObject *object; GBytes *bytes; object = json_object_new (); json_object_set_string_member (object, "command", "init"); json_object_set_int_member (object, "version", 1); if (opt_problem) { json_object_set_string_member (object, "problem", "canna-do-it"); json_object_set_string_member (object, "another-field", "extra"); } bytes = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); } static gboolean on_signal_done (gpointer data) { gboolean *closed = data; *closed = TRUE; return TRUE; } int main (int argc, char **argv) { CockpitTransport *transport; gboolean terminated = FALSE; gboolean interupted = FALSE; gboolean closed = FALSE; GOptionContext *context; GError *error = NULL; guint sig_term; guint sig_int; int outfd; static GOptionEntry entries[] = { { "lower", 0, 0, G_OPTION_ARG_NONE, &opt_lower, "Lower case channel type", NULL }, { "count", 0, 0, G_OPTION_ARG_NONE, &opt_show_count, "Show open channels count in ready messages", NULL }, { "upper", 0, 0, G_OPTION_ARG_NONE, &opt_upper, "Upper case channel type", NULL }, { "problem", 0, 0, G_OPTION_ARG_NONE, &opt_problem, "Set a problem in \"init\" message", NULL }, { NULL } }; signal (SIGPIPE, SIG_IGN); g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); g_setenv ("GIO_USE_PROXY_RESOLVER", "dummy", TRUE); g_setenv ("GIO_USE_VFS", "local", TRUE); context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, NULL); g_option_context_set_description (context, "mock-bridge as used in tests\n"); g_option_context_parse (context, &argc, &argv, &error); g_option_context_free (context); if (error) { g_printerr ("mock-bridge: %s\n", error->message); g_error_free (error); return 255; } outfd = dup (1); if (outfd < 0 || dup2 (2, 1) < 1) { g_warning ("bridge couldn't redirect stdout to stderr"); outfd = 1; } sig_term = g_unix_signal_add (SIGTERM, on_signal_done, &terminated); sig_int = g_unix_signal_add (SIGINT, on_signal_done, &interupted); g_type_init (); transport = cockpit_pipe_transport_new_fds ("stdio", 0, outfd); g_signal_connect (transport, "control", G_CALLBACK (on_transport_control), NULL); g_signal_connect (transport, "closed", G_CALLBACK (on_closed_set_flag), &closed); send_init_command (transport); /* Owns the channels */ channels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); while (!terminated && !closed && !interupted) g_main_context_iteration (NULL, TRUE); g_object_unref (transport); g_hash_table_destroy (channels); g_source_remove (sig_term); g_source_remove (sig_int); /* So the caller gets the right signal */ if (terminated) raise (SIGTERM); return 0; }