/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2015,2019 Red Hat, Inc. */ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-dbus-aux.h" /*****************************************************************************/ static void _nm_dbus_connection_call_get_name_owner_cb(GObject *source, GAsyncResult *res, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; const char *owner = NULL; gpointer orig_user_data; NMDBusConnectionCallGetNameOwnerCb callback; nm_utils_user_data_unpack(user_data, &orig_user_data, &callback); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); if (ret) g_variant_get(ret, "(&s)", &owner); callback(owner, error, orig_user_data); } void nm_dbus_connection_call_get_name_owner(GDBusConnection *dbus_connection, const char *service_name, int timeout_msec, GCancellable *cancellable, NMDBusConnectionCallGetNameOwnerCb callback, gpointer user_data) { nm_assert(callback); g_dbus_connection_call(dbus_connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner", g_variant_new("(s)", service_name), G_VARIANT_TYPE("(s)"), G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, _nm_dbus_connection_call_get_name_owner_cb, nm_utils_user_data_pack(user_data, callback)); } /*****************************************************************************/ static void _nm_dbus_connection_call_default_cb(GObject *source, GAsyncResult *res, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; gpointer orig_user_data; NMDBusConnectionCallDefaultCb callback; nm_utils_user_data_unpack(user_data, &orig_user_data, &callback); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); nm_assert((!!ret) != (!!error)); callback(ret, error, orig_user_data); } void nm_dbus_connection_call_get_all(GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, const char *interface_name, int timeout_msec, GCancellable *cancellable, NMDBusConnectionCallDefaultCb callback, gpointer user_data) { nm_assert(callback); g_dbus_connection_call(dbus_connection, bus_name, object_path, DBUS_INTERFACE_PROPERTIES, "GetAll", g_variant_new("(s)", interface_name), G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, _nm_dbus_connection_call_default_cb, nm_utils_user_data_pack(user_data, callback)); } void nm_dbus_connection_call_get(GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, const char *interface_name, const char *property_name, int timeout_msec, GCancellable *cancellable, NMDBusConnectionCallDefaultCb callback, gpointer user_data) { nm_assert(callback); g_dbus_connection_call(dbus_connection, bus_name, object_path, DBUS_INTERFACE_PROPERTIES, "Get", g_variant_new("(s)", interface_name, property_name), G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, _nm_dbus_connection_call_default_cb, nm_utils_user_data_pack(user_data, callback)); } void nm_dbus_connection_call_set(GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, const char *interface_name, const char *property_name, GVariant *value, int timeout_msec, GCancellable *cancellable, NMDBusConnectionCallDefaultCb callback, gpointer user_data) { g_dbus_connection_call(dbus_connection, bus_name, object_path, DBUS_INTERFACE_PROPERTIES, "Set", g_variant_new("(ssv)", interface_name, property_name, value), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, callback ? _nm_dbus_connection_call_default_cb : NULL, callback ? nm_utils_user_data_pack(user_data, callback) : NULL); } /*****************************************************************************/ static void _nm_dbus_connection_call_get_managed_objects_cb(GObject *source, GAsyncResult *res, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_unref_variant GVariant *arg = NULL; gs_free_error GError *error = NULL; gpointer orig_user_data; NMDBusConnectionCallDefaultCb callback; nm_utils_user_data_unpack(user_data, &orig_user_data, &callback); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); nm_assert((!!ret) != (!!error)); if (ret) { nm_assert(g_variant_is_of_type(ret, G_VARIANT_TYPE("(a{oa{sa{sv}}})"))); arg = g_variant_get_child_value(ret, 0); } callback(arg, error, orig_user_data); } void nm_dbus_connection_call_get_managed_objects(GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, GDBusCallFlags flags, int timeout_msec, GCancellable *cancellable, NMDBusConnectionCallDefaultCb callback, gpointer user_data) { nm_assert(callback); g_dbus_connection_call(dbus_connection, bus_name, object_path, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects", NULL, G_VARIANT_TYPE("(a{oa{sa{sv}}})"), flags, timeout_msec, cancellable, _nm_dbus_connection_call_get_managed_objects_cb, nm_utils_user_data_pack(user_data, callback)); } /*****************************************************************************/ static void _call_finish_cb(GObject *source, GAsyncResult *result, gpointer user_data, gboolean return_void, gboolean strip_dbus_error) { gs_unref_object GTask *task = user_data; gs_unref_variant GVariant *ret = NULL; GError *error = NULL; nm_assert(G_IS_DBUS_CONNECTION(source)); nm_assert(G_IS_TASK(user_data)); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret) { if (strip_dbus_error) g_dbus_error_strip_remote_error(error); g_task_return_error(task, error); return; } if (!return_void) g_task_return_pointer(task, g_steal_pointer(&ret), (GDestroyNotify) g_variant_unref); else { nm_assert(g_variant_is_of_type(ret, G_VARIANT_TYPE("()"))); g_task_return_boolean(task, TRUE); } } /** * nm_dbus_connection_call_finish_void_cb: * * A default callback to pass as callback to g_dbus_connection_call(). * * - user_data must be a GTask, whose reference will be consumed by the * callback. * - the return GVariant must be a empty tuple "()". * - the GTask is returned either with error or TRUE boolean. */ void nm_dbus_connection_call_finish_void_cb(GObject *source, GAsyncResult *result, gpointer user_data) { _call_finish_cb(source, result, user_data, TRUE, FALSE); } /** * nm_dbus_connection_call_finish_void_strip_dbus_error_cb: * * Like nm_dbus_connection_call_finish_void_cb(). The difference * is that on error this will first call g_dbus_error_strip_remote_error() on the error. */ void nm_dbus_connection_call_finish_void_strip_dbus_error_cb(GObject *source, GAsyncResult *result, gpointer user_data) { _call_finish_cb(source, result, user_data, TRUE, TRUE); } /** * nm_dbus_connection_call_finish_variant_cb: * * A default callback to pass as callback to g_dbus_connection_call(). * * - user_data must be a GTask, whose reference will be consumed by the * callback. * - the GTask is returned either with error or with a pointer containing the GVariant. */ void nm_dbus_connection_call_finish_variant_cb(GObject *source, GAsyncResult *result, gpointer user_data) { _call_finish_cb(source, result, user_data, FALSE, FALSE); } /** * nm_dbus_connection_call_finish_variant_strip_dbus_error_cb: * * Like nm_dbus_connection_call_finish_variant_strip_dbus_error_cb(). The difference * is that on error this will first call g_dbus_error_strip_remote_error() on the error. */ void nm_dbus_connection_call_finish_variant_strip_dbus_error_cb(GObject *source, GAsyncResult *result, gpointer user_data) { _call_finish_cb(source, result, user_data, FALSE, TRUE); } /*****************************************************************************/ typedef struct { char *bus_name; char *object_path; char *interface_name; char *method_name; GVariant *parameters; GDBusConnection *connection; const GVariantType *reply_type; int timeout_msec; } CallAsyncInfo; static void call_async_info_destroy(CallAsyncInfo *info) { g_free(info->bus_name); g_free(info->object_path); g_free(info->interface_name); g_free(info->method_name); g_variant_unref(info->parameters); nm_g_object_unref(info->connection); g_free(info); } static void call_cb(GObject *source, GAsyncResult *result, gpointer user_data) { gs_unref_object GTask *task = user_data; GError *error = NULL; GVariant *ret; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret) { g_task_return_error(task, error); return; } g_task_return_pointer(task, ret, (GDestroyNotify) g_variant_unref); } static void call_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data) { gs_unref_object GTask *task = user_data; GCancellable *cancellable; CallAsyncInfo *info; GError *error = NULL; info = g_task_get_task_data(task); info->connection = g_bus_get_finish(result, &error); cancellable = g_task_get_cancellable(task); if (!info->connection) { g_task_return_error(task, g_steal_pointer(&error)); return; } g_dbus_connection_call(info->connection, info->bus_name, info->object_path, info->interface_name, info->method_name, info->parameters, info->reply_type, G_DBUS_CALL_FLAGS_NONE, info->timeout_msec, cancellable, call_cb, g_steal_pointer(&task)); } void nm_dbus_call(GBusType bus_type, const char *bus_name, const char *object_path, const char *interface_name, const char *method_name, GVariant *parameters, const GVariantType *reply_type, GCancellable *cancellable, int timeout_msec, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; CallAsyncInfo *info; info = g_new(CallAsyncInfo, 1); *info = (CallAsyncInfo) { .bus_name = g_strdup(bus_name), .object_path = g_strdup(object_path), .interface_name = g_strdup(interface_name), .method_name = g_strdup(method_name), .parameters = g_variant_ref_sink(parameters), .reply_type = reply_type, .timeout_msec = timeout_msec, }; task = nm_g_task_new(NULL, cancellable, nm_dbus_call, callback, user_data); g_task_set_task_data(task, info, (GDestroyNotify) call_async_info_destroy); g_bus_get(bus_type, cancellable, call_bus_get_cb, task); } GVariant * nm_dbus_call_finish(GAsyncResult *result, GError **error) { nm_assert(nm_g_task_is_valid(result, NULL, nm_dbus_call)); return g_task_propagate_pointer(G_TASK(result), error); } /*****************************************************************************/ gboolean _nm_dbus_error_is(GError *error, ...) { gs_free char *dbus_error = NULL; const char *name; va_list ap; gboolean found = FALSE; /* This should only be used for "foreign" D-Bus errors (eg, errors * from BlueZ or wpa_supplicant). All NetworkManager D-Bus errors * should be properly mapped by gdbus to one of the domains/codes in * nm-errors.h. */ dbus_error = g_dbus_error_get_remote_error(error); if (!dbus_error) return FALSE; va_start(ap, error); while ((name = va_arg(ap, const char *))) { if (nm_streq(dbus_error, name)) { found = TRUE; break; } } va_end(ap); return found; } /*****************************************************************************/ typedef struct { GDBusConnection **p_dbus_connection; GError **p_error; } BusGetData; static void _bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data) { BusGetData *data = user_data; *data->p_dbus_connection = g_bus_get_finish(result, data->p_error); } /** * nm_g_bus_get_blocking: * @cancellable: a #GCancellable to abort the operation. * @error: the error. * * This calls g_bus_get(), but iterates the current (thread-default) GMainContext * until the response is ready. As such, it's similar to g_bus_get_sync(), * but it allows one to cancel the operation (without having multiple threads). * * Returns: (transfer full): the new #GDBusConnection or %NULL on error. */ GDBusConnection * nm_g_bus_get_blocking(GCancellable *cancellable, GError **error) { gs_free_error GError *local_error = NULL; gs_unref_object GDBusConnection *dbus_connection = NULL; GMainContext *main_context = g_main_context_get_thread_default(); BusGetData data = { .p_dbus_connection = &dbus_connection, .p_error = &local_error, }; g_bus_get(G_BUS_TYPE_SYSTEM, cancellable, _bus_get_cb, &data); while (!dbus_connection && !local_error) g_main_context_iteration(main_context, TRUE); if (!dbus_connection) { g_propagate_error(error, g_steal_pointer(&local_error)); return NULL; } return g_steal_pointer(&dbus_connection); } /*****************************************************************************/ void nm_dbus_connection_call_blocking_callback(GObject *source, GAsyncResult *res, gpointer user_data) { NMDBusConnectionCallBlockingData *data = user_data; nm_assert(data); nm_assert(!data->result); nm_assert(!data->error); data->result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &data->error); } GVariant * nm_dbus_connection_call_blocking(NMDBusConnectionCallBlockingData *data, GError **error) { GMainContext *main_context = g_main_context_get_thread_default(); gs_free_error GError *local_error = NULL; gs_unref_variant GVariant *result = NULL; nm_assert(data); while (!data->result && !data->error) g_main_context_iteration(main_context, TRUE); local_error = g_steal_pointer(&data->error); result = g_steal_pointer(&data->result); if (!result) { g_propagate_error(error, g_steal_pointer(&local_error)); return NULL; } return g_steal_pointer(&result); } /*****************************************************************************/ typedef struct { char *signal_name; const GVariantType *signature; } NMDBusSignalData; static void dbus_signal_data_free(gpointer data, GClosure *closure) { NMDBusSignalData *sd = data; g_free(sd->signal_name); g_slice_free(NMDBusSignalData, sd); } static void dbus_signal_meta_marshal(GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { NMDBusSignalData *sd = marshal_data; const char *signal_name; GVariant *parameters; gs_free GValue *closure_params_free = NULL; GValue *closure_params; gsize n_params; gsize i; g_return_if_fail(n_param_values == 4); signal_name = g_value_get_string(¶m_values[2]); parameters = g_value_get_variant(¶m_values[3]); if (!nm_streq(signal_name, sd->signal_name)) return; if (sd->signature) { if (!g_variant_is_of_type(parameters, sd->signature)) { g_warning("%p: got signal '%s' but parameters were of type '%s', not '%s'", g_value_get_object(¶m_values[0]), signal_name, g_variant_get_type_string(parameters), g_variant_type_peek_string(sd->signature)); return; } n_params = g_variant_n_children(parameters) + 1; } else n_params = 1; closure_params = nm_malloc0_maybe_a(240, sizeof(GValue) * n_params, &closure_params_free); g_value_init(&closure_params[0], G_TYPE_OBJECT); g_value_copy(¶m_values[0], &closure_params[0]); for (i = 1; i < n_params; i++) { gs_unref_variant GVariant *param = NULL; param = g_variant_get_child_value(parameters, i - 1); if (g_variant_is_of_type(param, G_VARIANT_TYPE("ay")) || g_variant_is_of_type(param, G_VARIANT_TYPE("aay"))) { /* g_dbus_gvariant_to_gvalue() thinks 'ay' means "non-UTF-8 NUL-terminated string" */ g_value_init(&closure_params[i], G_TYPE_VARIANT); g_value_set_variant(&closure_params[i], param); } else g_dbus_gvariant_to_gvalue(param, &closure_params[i]); } g_cclosure_marshal_generic(closure, NULL, n_params, closure_params, invocation_hint, NULL); for (i = 0; i < n_params; i++) g_value_unset(&closure_params[i]); } /** * _nm_dbus_proxy_signal_connect_data: * @proxy: a #GDBusProxy * @signal_name: the D-Bus signal to connect to * @signature: (nullable): the signal's type signature (must be a tuple) * @c_handler: the signal handler function * @data: (nullable): data to pass to @c_handler * @destroy_data: (nullable): closure destroy notify for @data * @connect_flags: connection flags * * Connects to the D-Bus signal @signal_name on @proxy. @c_handler must be a * void function whose first argument is a #GDBusProxy, followed by arguments * for each element of @signature, ending with a #gpointer argument for @data. * * The argument types in @c_handler correspond to the types output by * g_dbus_gvariant_to_gvalue(), except for 'ay' and 'aay'. In particular: * - both 16-bit and 32-bit integers are passed as #int/#guint * - 'as' values are passed as #GStrv (char **) * - all other array, tuple, and dict types are passed as #GVariant * * If @signature is %NULL, then the signal's parameters will be ignored, and * @c_handler should take only the #GDBusProxy and #gpointer arguments. * * Returns: the signal handler ID, which can be used with * g_signal_handler_remove(). Beware that because of the way the signal is * connected, you will not be able to remove it with * g_signal_handlers_disconnect_by_func(), although * g_signal_handlers_disconnect_by_data() will work correctly. */ gulong _nm_dbus_proxy_signal_connect_data(GDBusProxy *proxy, const char *signal_name, const GVariantType *signature, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags) { NMDBusSignalData *sd; GClosure *closure; gboolean swapped = !!(connect_flags & G_CONNECT_SWAPPED); gboolean after = !!(connect_flags & G_CONNECT_AFTER); g_return_val_if_fail(G_IS_DBUS_PROXY(proxy), 0); g_return_val_if_fail(signal_name != NULL, 0); g_return_val_if_fail(signature == NULL || g_variant_type_is_tuple(signature), 0); g_return_val_if_fail(c_handler != NULL, 0); sd = g_slice_new(NMDBusSignalData); sd->signal_name = g_strdup(signal_name); sd->signature = signature; closure = (swapped ? g_cclosure_new_swap : g_cclosure_new)(c_handler, data, destroy_data); g_closure_set_marshal(closure, g_cclosure_marshal_generic); g_closure_set_meta_marshal(closure, sd, dbus_signal_meta_marshal); g_closure_add_finalize_notifier(closure, sd, dbus_signal_data_free); return g_signal_connect_closure(proxy, "g-signal", closure, after); } /*****************************************************************************/ static gboolean _nm_dbus_typecheck_response(GVariant *response, const GVariantType *reply_type, GError **error) { g_return_val_if_fail(response, FALSE); if (!reply_type) return TRUE; if (g_variant_is_of_type(response, reply_type)) return TRUE; /* This is the same error code that g_dbus_connection_call() returns if * @reply_type doesn't match. */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Method returned type '%s', but expected '%s'"), g_variant_get_type_string(response), g_variant_type_peek_string(reply_type)); return FALSE; } /** * _nm_dbus_proxy_call_finish: * @proxy: A #GDBusProxy. * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to * g_dbus_proxy_call(). * @reply_type: (nullable): the expected type of the reply, or %NULL * @error: Return location for error or %NULL. * * Finishes an operation started with g_dbus_proxy_call(), as with * g_dbus_proxy_call_finish(), except thatif @reply_type is non-%NULL, then it * will also check that the response matches that type signature, and return * an error if not. * * Returns: %NULL if @error is set. Otherwise, a #GVariant tuple with * return values. Free with g_variant_unref(). */ GVariant * _nm_dbus_proxy_call_finish(GDBusProxy *proxy, GAsyncResult *res, const GVariantType *reply_type, GError **error) { GVariant *variant; variant = g_dbus_proxy_call_finish(proxy, res, error); if (variant && !_nm_dbus_typecheck_response(variant, reply_type, error)) nm_clear_pointer(&variant, g_variant_unref); return variant; }