* Copyright (C) 2013 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "mock-service.h"
#include "mock-dbus-tests.h"
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <glib-unix.h>
#include <string.h>
typedef struct {
GDBusConnection *connection;
GDBusObjectManagerServer *object_manager;
GHashTable *other_names;
GList *never_return_invocations;
} MockData;
static gboolean
on_handle_hello_world (TestFrobber *object,
GDBusMethodInvocation *invocation,
const gchar *greeting,
gpointer user_data)
{
gchar *response;
response = g_strdup_printf ("Word! You said `%s'. I'm Skeleton, btw!", greeting);
test_frobber_complete_hello_world (object, invocation, response);
g_free (response);
return TRUE;
}
static gboolean
on_handle_never_return (TestFrobber *object,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
MockData *data = user_data;
* the global D-Bus timeout; so remember them and answer on shutdown */
data->never_return_invocations = g_list_append (data->never_return_invocations, g_object_ref (invocation));
return TRUE;
}
static gboolean
on_handle_test_primitive_types (TestFrobber *object,
GDBusMethodInvocation *invocation,
guchar val_byte,
gboolean val_boolean,
gint16 val_int16,
guint16 val_uint16,
gint val_int32,
guint val_uint32,
gint64 val_int64,
guint64 val_uint64,
gdouble val_double,
const gchar *val_string,
const gchar *val_objpath,
const gchar *val_signature,
const gchar *val_bytestring,
gpointer user_data)
{
gchar *s1;
gchar *s2;
gchar *s3;
s1 = g_strdup_printf ("Word! You said `%s'. Rock'n'roll!", val_string);
s2 = g_strdup_printf ("/modified%s", val_objpath);
s3 = g_strdup_printf ("assgit%s", val_signature);
test_frobber_complete_test_primitive_types (object,
invocation,
10 + val_byte,
!val_boolean,
100 + val_int16,
1000 + val_uint16,
10000 + val_int32,
100000 + val_uint32,
1000000 + val_int64,
10000000 + val_uint64,
val_double / G_PI,
s1,
s2,
s3,
"bytestring!\xff");
g_free (s1);
g_free (s2);
g_free (s3);
return TRUE;
}
static gboolean
on_handle_test_non_primitive_types (TestFrobber *object,
GDBusMethodInvocation *invocation,
GVariant *dict_s_to_s,
GVariant *dict_s_to_pairs,
GVariant *a_struct,
const gchar* const *array_of_strings,
const gchar* const *array_of_objpaths,
GVariant *array_of_signatures,
const gchar* const *array_of_bytestrings,
gpointer user_data)
{
gchar *s;
GString *str;
str = g_string_new (NULL);
s = g_variant_print (dict_s_to_s, TRUE);
g_string_append (str, s);
g_free (s);
s = g_variant_print (dict_s_to_pairs, TRUE);
g_string_append (str, s);
g_free (s);
s = g_variant_print (a_struct, TRUE);
g_string_append (str, s);
g_free (s);
s = g_strjoinv (", ", (gchar **)array_of_strings);
g_string_append_printf (str, "array_of_strings: [%s] ", s);
g_free (s);
s = g_strjoinv (", ", (gchar **)array_of_objpaths);
g_string_append_printf (str, "array_of_objpaths: [%s] ", s);
g_free (s);
s = g_variant_print (array_of_signatures, TRUE);
g_string_append_printf (str, "array_of_signatures: %s ", s);
g_free (s);
s = g_strjoinv (", ", (gchar **)array_of_bytestrings);
g_string_append_printf (str, "array_of_bytestrings: [%s] ", s);
g_free (s);
test_frobber_complete_test_non_primitive_types (object, invocation, str->str);
g_string_free (str, TRUE);
return TRUE;
}
static gboolean
on_handle_test_variant (TestFrobber *object,
GDBusMethodInvocation *invocation,
GVariant *v,
gpointer user_data)
{
test_frobber_complete_test_variant (object, invocation);
return TRUE;
}
static gboolean
on_handle_request_signal_emission (TestFrobber *object,
GDBusMethodInvocation *invocation,
gint which_one,
gpointer user_data)
{
if (which_one == 0)
{
const gchar *a_strv[] = {"foo", "frobber", NULL};
const gchar *a_objpath_array[] = {"/foo", "/foo/bar", NULL};
GVariant *a_variant = g_variant_new_parsed ("{'first': (42, 42), 'second': (43, 43)}");
test_frobber_emit_test_signal (object, 43, a_strv, a_objpath_array, a_variant);
test_frobber_complete_request_signal_emission (object, invocation);
}
return TRUE;
}
static gboolean
on_handle_request_property_mods (TestFrobber *object,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
test_frobber_set_y (object, test_frobber_get_y (object) + 1);
test_frobber_set_i (object, test_frobber_get_i (object) + 1);
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (object));
test_frobber_complete_request_multi_property_mods (object, invocation);
return TRUE;
}
static gboolean
on_handle_request_multi_property_mods (TestFrobber *object,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
test_frobber_set_y (object, test_frobber_get_y (object) + 1);
test_frobber_set_i (object, test_frobber_get_i (object) + 1);
test_frobber_set_y (object, test_frobber_get_y (object) + 1);
test_frobber_set_i (object, test_frobber_get_i (object) + 1);
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (object));
test_frobber_set_y (object, test_frobber_get_y (object) + 1);
test_frobber_set_i (object, test_frobber_get_i (object) + 1);
test_frobber_complete_request_multi_property_mods (object, invocation);
return TRUE;
}
static gboolean
on_handle_property_cancellation (TestFrobber *object,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
guint n;
n = test_frobber_get_n (object);
test_frobber_set_n (object, n + 1);
test_frobber_set_n (object, n);
* away, if any - there should not be any)
*/
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (object));
test_frobber_complete_property_cancellation (object, invocation);
return TRUE;
}
static gboolean
on_handle_create_object (TestFrobber *object,
GDBusMethodInvocation *invocation,
const gchar *at_path,
gpointer user_data)
{
MockData *data = user_data;
GDBusObject *previous;
previous = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (data->object_manager), at_path);
if (previous != NULL)
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Sorry, object already exists at %s",
at_path);
g_object_unref (previous);
}
else
{
TestObjectSkeleton *new_object;
TestFrobber *frobber;
new_object = test_object_skeleton_new (at_path);
frobber = test_frobber_skeleton_new ();
test_object_skeleton_set_frobber (new_object, frobber);
g_dbus_object_manager_server_export (data->object_manager, G_DBUS_OBJECT_SKELETON (new_object));
g_object_unref (frobber);
g_object_unref (new_object);
g_signal_connect (frobber,
"handle-request-property-mods",
G_CALLBACK (on_handle_request_property_mods),
NULL);
test_frobber_complete_create_object (object, invocation);
}
return TRUE;
}
static gboolean
on_handle_delete_object (TestFrobber *object,
GDBusMethodInvocation *invocation,
const gchar *path,
gpointer user_data)
{
MockData *data = user_data;
GDBusObject *previous;
previous = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (data->object_manager), path);
if (previous != NULL)
{
g_warn_if_fail (g_dbus_object_manager_server_unexport (data->object_manager, path));
test_frobber_complete_delete_object (object, invocation);
g_object_unref (previous);
}
else
{
g_dbus_method_invocation_return_error (invocation,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Sorry, there is no object at %s",
path);
}
return TRUE;
}
static gboolean
on_handle_delete_all_objects (TestFrobber *object,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
MockData *data = user_data;
const gchar *path;
GList *objects;
GList *l;
objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (data->object_manager));
for (l = objects; l != NULL; l = g_list_next (l))
{
path = g_dbus_object_get_object_path (l->data);
if (!g_str_has_suffix (path, "/frobber"))
g_warn_if_fail (g_dbus_object_manager_server_unexport (data->object_manager, path));
}
test_frobber_complete_delete_all_objects (object, invocation);
g_list_free_full (objects, g_object_unref);
return TRUE;
}
static gboolean
on_handle_test_asv (TestFrobber *object,
GDBusMethodInvocation *invocation,
GVariant *asv,
gpointer user_data)
{
gchar *s;
s = g_variant_print (asv, TRUE);
test_frobber_complete_test_asv (object, invocation, s);
g_free (s);
return TRUE;
}
static gboolean
on_handle_add_alpha (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
TestObjectSkeleton *enclosing;
enclosing = TEST_OBJECT_SKELETON (g_dbus_interface_get_object (G_DBUS_INTERFACE (frobber)));
if (test_object_peek_alpha (TEST_OBJECT (enclosing)) == NULL)
{
TestAlpha *iface = test_alpha_skeleton_new ();
test_object_skeleton_set_alpha (enclosing, iface);
g_object_unref (iface);
}
test_frobber_complete_add_alpha (frobber, invocation);
return TRUE;
}
static gboolean
on_handle_remove_alpha (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
TestObjectSkeleton *enclosing;
enclosing = TEST_OBJECT_SKELETON (g_dbus_interface_get_object (G_DBUS_INTERFACE (frobber)));
if (test_object_peek_alpha (TEST_OBJECT (enclosing)) != NULL)
test_object_skeleton_set_alpha (enclosing, NULL);
test_frobber_complete_add_alpha (frobber, invocation);
return TRUE;
}
typedef struct {
GDBusMethodInvocation *invocation;
} ClaimNameData;
static void
claim_name_data_free (gpointer data)
{
ClaimNameData *claim_data = data;
if (claim_data->invocation)
g_object_unref (claim_data->invocation);
g_free (claim_data);
}
static void
on_other_name_acquired (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
ClaimNameData *claim_data = user_data;
if (claim_data->invocation)
{
g_dbus_method_invocation_return_value (claim_data->invocation, NULL);
g_clear_object (&claim_data->invocation);
}
}
static void
on_other_name_lost (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
ClaimNameData *claim_data = user_data;
if (claim_data->invocation)
{
g_dbus_method_invocation_return_error (claim_data->invocation,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Couldn't claim name: %s", name);
g_message ("couldn't claim name: %s", name);
g_clear_object (&claim_data->invocation);
}
}
static gboolean
on_claim_other_name (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
const gchar *name,
gpointer user_data)
{
ClaimNameData *claim_data;
MockData *mock_data = user_data;
guint id;
g_return_val_if_fail (g_hash_table_lookup (mock_data->other_names, name) == NULL, FALSE);
claim_data = g_new0 (ClaimNameData, 1);
claim_data->invocation = g_object_ref (invocation);
id = g_bus_own_name_on_connection (mock_data->connection, name,
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_NONE,
on_other_name_acquired,
on_other_name_lost,
claim_data, claim_name_data_free);
g_hash_table_replace (mock_data->other_names, g_strdup (name), GUINT_TO_POINTER (id));
return TRUE;
}
static gboolean
on_release_other_name (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
const gchar *name,
gpointer user_data)
{
MockData *mock_data = user_data;
guint id;
g_return_val_if_fail (g_hash_table_lookup (mock_data->other_names, name) != NULL, FALSE);
id = GPOINTER_TO_UINT (g_hash_table_lookup (mock_data->other_names, name));
g_hash_table_remove (mock_data->other_names, name);
g_bus_unown_name (id);
g_dbus_method_invocation_return_value (invocation, NULL);
return TRUE;
}
static gboolean
on_tell_me_your_name (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
GDBusMessage *message = g_dbus_method_invocation_get_message (invocation);
GVariant *name = g_variant_new_string (g_dbus_message_get_destination (message));
g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&name, 1));
return TRUE;
}
static gboolean
test_fd_pipe_readable (gint fd,
GIOCondition condition,
gpointer user_data)
{
const char *expected = "Hello, fd";
size_t expected_len = strlen (expected);
char buffer[100];
size_t n;
g_assert (condition == G_IO_IN);
n = read (fd, buffer, 100);
g_assert (n == expected_len);
g_assert (strncmp (buffer, expected, expected_len) == 0);
close (fd);
return G_SOURCE_REMOVE;
}
static gboolean
test_fd_deferred_close (gpointer user_data)
{
gint fd = GPOINTER_TO_INT (user_data);
close (fd);
return G_SOURCE_REMOVE;
}
static gboolean
on_make_test_fd (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
const char *type,
gpointer user_data)
{
gint fds[2] = { -1, -1 };
GUnixFDList *fd_list;
const char *data = "Hello, fd";
size_t data_size = strlen (data);
size_t n;
GError *error = NULL;
g_unix_open_pipe (fds, FD_CLOEXEC, &error);
g_assert_no_error (error);
fd_list = g_unix_fd_list_new ();
if (g_str_equal (type, "readable"))
{
g_unix_fd_list_append (fd_list, fds[0], &error);
g_assert_no_error (error);
close (fds[0]);
n = write(fds[1], data, data_size);
g_assert (n == data_size);
* Close this fd a bit later, because closing the writing side
* makes CockpitPipe close itself immediately, which breaks
* testing that writing into this read-only pipe fails.
*/
g_timeout_add_seconds (5, test_fd_deferred_close, GINT_TO_POINTER (fds[1]));
}
else if (g_str_equal (type, "writable"))
{
g_unix_fd_list_append (fd_list, fds[1], &error);
g_assert_no_error (error);
close (fds[1]);
g_unix_fd_add (fds[0], G_IO_IN, test_fd_pipe_readable, NULL);
}
else
g_assert_not_reached ();
g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)", 0), fd_list);
return TRUE;
}
* Non object manager stuff
*/
static GVariant *
clique_get_property (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
gpointer user_data)
{
gchar *friend = user_data;
return g_variant_new_object_path (friend);
}
static gboolean
on_create_clique (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
const gchar *name,
gpointer user_data)
{
GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
GError *error = NULL;
gchar *path = NULL;
gchar *friend;
gint i;
static const GDBusInterfaceVTable vtable = {
.method_call = NULL,
.get_property = clique_get_property,
.set_property = NULL,
};
for (i = 0; i < 3; i++)
{
g_free (path);
path = g_strdup_printf ("/cliques/%s/%d", name, i);
friend = g_strdup_printf ("/cliques/%s/%d", name, (i + 1) % 3);
g_dbus_connection_register_object (connection, path,
test_clique_interface_info (),
&vtable, friend, g_free, &error);
if (error)
{
g_critical ("Couldn't register new clique: %s", error->message);
g_clear_error (&error);
}
}
test_frobber_complete_create_clique (frobber, invocation, path);
g_free (path);
return TRUE;
}
static GVariant *
hidden_get_property (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
gpointer user_data)
{
gchar *name = user_data;
return g_variant_new_string (name);
}
static gboolean
on_emit_hidden (TestFrobber *frobber,
GDBusMethodInvocation *invocation,
const gchar *name,
gpointer user_data)
{
GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
GError *error = NULL;
gchar *path = NULL;
static const GDBusInterfaceVTable vtable = {
.method_call = NULL,
.get_property = hidden_get_property,
.set_property = NULL,
};
path = g_strdup_printf ("/hidden/%s", name);
g_dbus_connection_register_object (connection, path,
test_hidden_interface_info (),
&vtable, g_strdup (name), g_free, &error);
if (error)
{
g_critical ("Couldn't register new hidden: %s", error->message);
g_clear_error (&error);
}
g_dbus_connection_emit_signal (connection, NULL, path,
"com.redhat.Cockpit.DBusTests.Hidden", "Yooohooo",
g_variant_new ("()"), &error);
if (error)
{
g_critical ("Couldn't emit signal on hidden: %s", error->message);
g_clear_error (&error);
}
test_frobber_complete_emit_hidden (frobber, invocation);
g_free (path);
return TRUE;
}
* An Introspect() that actually fails
*/
static void
introspect_fail_method_call (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
const gchar *dbus_error = user_data;
g_dbus_method_invocation_return_dbus_error (invocation, dbus_error, dbus_error);
}
static void
mock_service_create_introspect_fail (GDBusConnection *connection)
{
static const gchar introspectable_xml[] =
"<node>"
" <interface name=\"org.freedesktop.DBus.Introspectable\">"
" <method name=\"Introspect\">"
" <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>"
" </method>"
" </interface>"
"</node>";
const GDBusInterfaceVTable introspect_vtable = {
.method_call = introspect_fail_method_call,
};
GDBusNodeInfo *node_info;
GDBusInterfaceInfo *interface_info;
GError *error = NULL;
node_info = g_dbus_node_info_new_for_xml (introspectable_xml, &error);
g_assert_no_error (error);
interface_info = g_dbus_node_info_lookup_interface (node_info, "org.freedesktop.DBus.Introspectable");
g_assert (interface_info != NULL);
g_dbus_connection_register_object (connection, "/introspect/unknown", interface_info, &introspect_vtable,
"org.freedesktop.DBus.Error.UnknownObject", NULL, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
g_error_free (error);
else
g_assert_no_error (error);
g_dbus_node_info_unref (node_info);
}
static void
mock_data_free (gpointer data)
{
MockData *mock_data = data;
for (GList *elem = mock_data->never_return_invocations; elem; elem = elem->next)
g_dbus_method_invocation_return_value (elem->data, NULL);
g_list_free_full (mock_data->never_return_invocations, g_object_unref);
g_object_unref (mock_data->connection);
g_hash_table_destroy (mock_data->other_names);
g_free (mock_data);
}
GObject *
mock_service_create_and_export (GDBusConnection *connection,
const gchar *object_manager_path)
{
GError *error;
TestFrobber *exported_frobber;
TestObjectSkeleton *exported_object;
GDBusObjectManagerServer *object_manager;
MockData *mock_data;
gchar *path;
mock_data = g_new0 (MockData, 1);
mock_data->connection = g_object_ref (connection);
mock_data->other_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
* TestFrobberSkeleton subclass. Notes:
*
* 1. We handle methods by simply connecting to the appropriate
* GObject signal.
*
* 2. Property storage is taken care of by the class; we can
* use g_object_get()/g_object_set() (and the generated
* C bindings at will)
*/
error = NULL;
exported_frobber = test_frobber_skeleton_new ();
test_frobber_set_ay (exported_frobber, "ABCabc");
test_frobber_set_y (exported_frobber, 42);
test_frobber_set_d (exported_frobber, 43.0);
test_frobber_set_finally_normal_name (exported_frobber, "There aint no place like home");
test_frobber_set_writeonly_property (exported_frobber, "Mr. Burns");
test_frobber_set_readonly_property (exported_frobber, "blah");
object_manager = g_dbus_object_manager_server_new (object_manager_path);
mock_data->object_manager = object_manager;
g_object_set_data_full (G_OBJECT (object_manager), "mock-data", mock_data, mock_data_free);
path = g_strdup_printf ("%s/frobber", object_manager_path);
exported_object = test_object_skeleton_new (path);
g_free (path);
test_object_skeleton_set_frobber (exported_object, exported_frobber);
g_dbus_object_manager_server_export (object_manager, G_DBUS_OBJECT_SKELETON (exported_object));
g_object_unref (exported_object);
g_dbus_object_manager_server_set_connection (object_manager, connection);
g_assert_no_error (error);
g_signal_connect (exported_frobber, "handle-hello-world",
G_CALLBACK (on_handle_hello_world), NULL);
g_signal_connect (exported_frobber, "handle-never-return",
G_CALLBACK (on_handle_never_return), mock_data);
g_signal_connect (exported_frobber,
"handle-test-primitive-types",
G_CALLBACK (on_handle_test_primitive_types), NULL);
g_signal_connect (exported_frobber,
"handle-test-non-primitive-types",
G_CALLBACK (on_handle_test_non_primitive_types), NULL);
g_signal_connect (exported_frobber,
"handle-test-variant",
G_CALLBACK (on_handle_test_variant), NULL);
g_signal_connect (exported_frobber,
"handle-request-signal-emission",
G_CALLBACK (on_handle_request_signal_emission), NULL);
g_signal_connect (exported_frobber,
"handle-request-property-mods",
G_CALLBACK (on_handle_request_property_mods), NULL);
g_signal_connect (exported_frobber,
"handle-request-multi-property-mods",
G_CALLBACK (on_handle_request_multi_property_mods), NULL);
g_signal_connect (exported_frobber,
"handle-property-cancellation",
G_CALLBACK (on_handle_property_cancellation), NULL);
g_signal_connect (exported_frobber,
"handle-delete-all-objects",
G_CALLBACK (on_handle_delete_all_objects), mock_data);
g_signal_connect (exported_frobber,
"handle-create-object",
G_CALLBACK (on_handle_create_object), mock_data);
g_signal_connect (exported_frobber,
"handle-delete-object",
G_CALLBACK (on_handle_delete_object), mock_data);
g_signal_connect (exported_frobber,
"handle-test-asv",
G_CALLBACK (on_handle_test_asv), NULL);
g_signal_connect (exported_frobber,
"handle-add-alpha",
G_CALLBACK (on_handle_add_alpha), NULL);
g_signal_connect (exported_frobber,
"handle-remove-alpha",
G_CALLBACK (on_handle_remove_alpha), NULL);
g_signal_connect (exported_frobber, "handle-create-clique",
G_CALLBACK (on_create_clique), NULL);
g_signal_connect (exported_frobber, "handle-emit-hidden",
G_CALLBACK (on_emit_hidden), NULL);
g_signal_connect (exported_frobber, "handle-claim-other-name",
G_CALLBACK (on_claim_other_name), mock_data);
g_signal_connect (exported_frobber, "handle-release-other-name",
G_CALLBACK (on_release_other_name), mock_data);
g_signal_connect (exported_frobber, "handle-tell-me-your-name",
G_CALLBACK (on_tell_me_your_name), mock_data);
g_signal_connect (exported_frobber, "handle-make-test-fd",
G_CALLBACK (on_make_test_fd), mock_data);
g_object_unref (exported_frobber);
mock_service_create_introspect_fail (connection);
return G_OBJECT (object_manager);
}