From 2ef84f000f95be427cf0b941a7456298025955d8 Mon Sep 17 00:00:00 2001 From: Pavel Hrdina Date: Tue, 8 Sep 2020 13:55:24 +0200 Subject: [PATCH] util: introduce helpers for GLib DBus implementation With libdbus our wrappers had a special syntax to create the DBus messages by defining the DBus message signature followed by list of arguments providing data based on the signature. There will be no similar helper with GLib implementation as they provide same functionality via GVariant APIs. The syntax is slightly different mostly for how arrays, variadic types and dictionaries are created/parsed. Additional difference is that with GLib DBus everything is wrapped in extra tuple (struct). For more details refer to the documentation [1]. [1] Signed-off-by: Pavel Hrdina Reviewed-by: Michal Privoznik --- meson.build | 6 +- po/POTFILES.in | 1 + src/libvirt_private.syms | 14 ++ src/util/meson.build | 1 + src/util/virgdbus.c | 425 +++++++++++++++++++++++++++++++++++++++ src/util/virgdbus.h | 79 ++++++++ 6 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 src/util/virgdbus.c create mode 100644 src/util/virgdbus.h diff --git a/meson.build b/meson.build index 3d15b4ee34..b5b1223227 100644 --- a/meson.build +++ b/meson.build @@ -1066,7 +1066,11 @@ endif glib_version = '2.48.0' glib_dep = dependency('glib-2.0', version: '>=' + glib_version) gobject_dep = dependency('gobject-2.0', version: '>=' + glib_version) -gio_dep = dependency('gio-2.0', version: '>=' + glib_version) +if host_machine.system() == 'windows' + gio_dep = dependency('gio-2.0', version: '>=' + glib_version) +else + gio_dep = dependency('gio-unix-2.0', version: '>=' + glib_version) +endif glib_dep = declare_dependency( dependencies: [ glib_dep, gobject_dep, gio_dep ], ) diff --git a/po/POTFILES.in b/po/POTFILES.in index 4ab8832b37..d87425a64c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -257,6 +257,7 @@ @SRCDIR@src/util/virfirewall.c @SRCDIR@src/util/virfirewalld.c @SRCDIR@src/util/virfirmware.c +@SRCDIR@src/util/virgdbus.c @SRCDIR@src/util/virhash.c @SRCDIR@src/util/virhook.c @SRCDIR@src/util/virhostcpu.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 5842b8d23d..fea5a49e55 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2190,6 +2190,20 @@ virFirmwareParse; virFirmwareParseList; +# util/virgdbus.h +virGDBusCallMethod; +virGDBusCallMethodWithFD; +virGDBusCloseSystemBus; +virGDBusErrorIsUnknownMethod; +virGDBusGetSessionBus; +virGDBusGetSystemBus; +virGDBusHasSystemBus; +virGDBusIsServiceEnabled; +virGDBusIsServiceRegistered; +virGDBusMessageIsSignal; +virGDBusSetSharedBus; + + # util/virgettext.h virGettextInitialize; diff --git a/src/util/meson.build b/src/util/meson.build index bf556e7ae6..8a9dcac053 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -33,6 +33,7 @@ util_sources = [ 'virfirewall.c', 'virfirewalld.c', 'virfirmware.c', + 'virgdbus.c', 'virgettext.c', 'virgic.c', 'virhash.c', diff --git a/src/util/virgdbus.c b/src/util/virgdbus.c new file mode 100644 index 0000000000..535b19f0a4 --- /dev/null +++ b/src/util/virgdbus.c @@ -0,0 +1,425 @@ +/* + * virgdbus.c: helper for using GDBus + * + * This library 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. + * + * This library 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 this library. If not, see + * . + * + */ + +#include + +#include "virerror.h" +#include "virlog.h" +#include "virgdbus.h" +#include "virthread.h" + + +#define VIR_FROM_THIS VIR_FROM_DBUS + +VIR_LOG_INIT("util.dbus"); + + +static bool sharedBus = true; +static GDBusConnection *systemBus; +static GDBusConnection *sessionBus; +static virOnceControl systemOnce = VIR_ONCE_CONTROL_INITIALIZER; +static virOnceControl sessionOnce = VIR_ONCE_CONTROL_INITIALIZER; +static GError *systemError; +static GError *sessionError; + + +void +virGDBusSetSharedBus(bool shared) +{ + sharedBus = shared; +} + + +static GDBusConnection * +virGDBusBusInit(GBusType type, GError **error) +{ + g_autofree char *address = NULL; + + if (sharedBus) { + return g_bus_get_sync(type, NULL, error); + } else { + address = g_dbus_address_get_for_bus_sync(type, NULL, error); + if (error) + return NULL; + return g_dbus_connection_new_for_address_sync(address, + G_DBUS_CONNECTION_FLAGS_NONE, + NULL, + NULL, + error); + } +} + + +static void +virGDBusSystemBusInit(void) +{ + systemBus = virGDBusBusInit(G_BUS_TYPE_SYSTEM, &systemError); +} + + +static GDBusConnection * +virGDBusGetSystemBusInternal(void) +{ + if (virOnce(&systemOnce, virGDBusSystemBusInit) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to run one time GDBus initializer")); + return NULL; + } + + return systemBus; +} + + +GDBusConnection * +virGDBusGetSystemBus(void) +{ + GDBusConnection *bus = virGDBusGetSystemBusInternal(); + + if (!bus) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to get system bus connection: %s"), + systemError->message); + return NULL; + } + + return bus; +} + + +static void +virGDBusSessionBusInit(void) +{ + sessionBus = virGDBusBusInit(G_BUS_TYPE_SESSION, &sessionError); +} + + +GDBusConnection * +virGDBusGetSessionBus(void) +{ + if (virOnce(&sessionOnce, virGDBusSessionBusInit) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to run one time GDBus initializer")); + return NULL; + } + + if (!sessionBus) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to get session bus connection: %s"), + sessionError->message); + return NULL; + } + + return sessionBus; +} + + +/** + * virGDBusHasSystemBus: + * + * Check if DBus system bus is running. This does not imply that we have + * a connection. DBus might be running and refusing connections due to its + * client limit. The latter must be treated as a fatal error. + * + * Return false if dbus is not available, true if probably available. + */ +bool +virGDBusHasSystemBus(void) +{ + g_autofree char *name = NULL; + + if (virGDBusGetSystemBusInternal()) + return true; + + if (!g_dbus_error_is_remote_error(systemError)) + return false; + + name = g_dbus_error_get_remote_error(systemError); + + if (name && + (STREQ(name, "org.freedesktop.DBus.Error.FileNotFound") || + STREQ(name, "org.freedesktop.DBus.Error.NoServer"))) { + VIR_DEBUG("System bus not available: %s", NULLSTR(systemError->message)); + return false; + } + + return true; +} + + +void +virGDBusCloseSystemBus(void) +{ + if (!systemBus || sharedBus) + return; + + g_dbus_connection_flush_sync(systemBus, NULL, NULL); + g_dbus_connection_close_sync(systemBus, NULL, NULL); + g_object_unref(systemBus); + systemBus = NULL; +} + + +#define VIR_DBUS_METHOD_CALL_TIMEOUT_MILIS 30 * 1000 + +/** + * virGDBusCallMethod: + * @conn: a DBus connection + * @reply: pointer to receive reply message, or NULL + * @error: libvirt error pointer or NULL + * @busName: bus identifier of the target service + * @objectPath: object path of the target service + * @ifaceName: the interface of the object + * @method: the name of the method in the interface + * @data: pointer to data passed to DBus method + * + * If @error is NULL then a libvirt error will be raised when a DBus error + * is received and the return value will be -1. If @error is non-NULL then + * any DBus error will be saved into that object and the return value will + * be 0. + * + * Returns 0 on success, or -1 upon error. + */ +int +virGDBusCallMethod(GDBusConnection *conn, + GVariant **reply, + virErrorPtr error, + const char *busName, + const char *objectPath, + const char *ifaceName, + const char *method, + GVariant *data) +{ + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) gerror = NULL; + + if (error) + memset(error, 0, sizeof(*error)); + + if (data) + g_variant_ref_sink(data); + + ret = g_dbus_connection_call_sync(conn, + busName, + objectPath, + ifaceName, + method, + data, + NULL, + G_DBUS_CALL_FLAGS_NONE, + VIR_DBUS_METHOD_CALL_TIMEOUT_MILIS, + NULL, + &gerror); + + if (!ret) { + if (error && g_dbus_error_is_remote_error(gerror)) { + error->level = VIR_ERR_ERROR; + error->code = VIR_ERR_DBUS_SERVICE; + error->domain = VIR_FROM_DBUS; + error->str1 = g_dbus_error_get_remote_error(gerror); + error->message = g_strdup(gerror->message); + } else { + virReportError(VIR_ERR_DBUS_SERVICE, "%s", gerror->message); + return -1; + } + } + + if (reply) + *reply = g_steal_pointer(&ret); + + return 0; +} + + +#ifdef G_OS_UNIX +int +virGDBusCallMethodWithFD(GDBusConnection *conn, + GVariant **reply, + GUnixFDList **replyFD, + virErrorPtr error, + const char *busName, + const char *objectPath, + const char *ifaceName, + const char *method, + GVariant *data, + GUnixFDList *dataFD) +{ + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) gerror = NULL; + + if (error) + memset(error, 0, sizeof(*error)); + + if (data) + g_variant_ref_sink(data); + + ret = g_dbus_connection_call_with_unix_fd_list_sync(conn, + busName, + objectPath, + ifaceName, + method, + data, + NULL, + G_DBUS_CALL_FLAGS_NONE, + VIR_DBUS_METHOD_CALL_TIMEOUT_MILIS, + dataFD, + replyFD, + NULL, + &gerror); + + if (!ret) { + if (error && g_dbus_error_is_remote_error(gerror)) { + error->level = VIR_ERR_ERROR; + error->code = VIR_ERR_DBUS_SERVICE; + error->domain = VIR_FROM_DBUS; + error->str1 = g_dbus_error_get_remote_error(gerror); + error->message = g_strdup(gerror->message); + + if (!error->str1 || !error->message) + return -1; + } else { + virReportError(VIR_ERR_DBUS_SERVICE, "%s", gerror->message); + return -1; + } + } + + if (reply) + *reply = g_steal_pointer(&ret); + + return 0; +} +#else +int +virGDBusCallMethodWithFD(GDBusConnection *conn G_GNUC_UNUSED, + GVariant **reply G_GNUC_UNUSED, + GUnixFDList **replyFD G_GNUC_UNUSED, + virErrorPtr error G_GNUC_UNUSED, + const char *busName G_GNUC_UNUSED, + const char *objectPath G_GNUC_UNUSED, + const char *ifaceName G_GNUC_UNUSED, + const char *method G_GNUC_UNUSED, + GVariant *data G_GNUC_UNUSED, + GUnixFDList *dataFD G_GNUC_UNUSED) +{ + virReportSystemError(ENOSYS, "%s", + _("Unix file descriptors not supported on this platform")); + return -1; +} +#endif + + +static int +virGDBusIsServiceInList(const char *listMethod, + const char *name) +{ + GDBusConnection *conn; + g_autoptr(GVariant) reply = NULL; + g_autoptr(GVariantIter) iter = NULL; + char *str; + int rc; + + if (!virGDBusHasSystemBus()) + return -2; + + conn = virGDBusGetSystemBus(); + if (!conn) + return -1; + + rc = virGDBusCallMethod(conn, + &reply, + NULL, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + listMethod, + NULL); + + if (rc < 0) + return -1; + + g_variant_get(reply, "(as)", &iter); + while (g_variant_iter_loop(iter, "s", &str)) { + if (STREQ(str, name)) + return 0; + } + + return -2; +} + + +/** + * virGDBusIsServiceEnabled: + * @name: service name + * + * Returns 0 if service is available, -1 on fatal error, or -2 if service is not available + */ +int +virGDBusIsServiceEnabled(const char *name) +{ + int ret = virGDBusIsServiceInList("ListActivatableNames", name); + + VIR_DEBUG("Service %s is %s", name, ret ? "unavailable" : "available"); + + return ret; +} + + +/** + * virGDBusIsServiceRegistered: + * @name: service name + * + * Returns 0 if service is registered, -1 on fatal error, or -2 if service is not registered + */ +int +virGDBusIsServiceRegistered(const char *name) +{ + int ret = virGDBusIsServiceInList("ListNames", name); + + VIR_DEBUG("Service %s is %s", name, ret ? "not registered" : "registered"); + + return ret; +} + + +bool +virGDBusErrorIsUnknownMethod(virErrorPtr err) +{ + return err->domain == VIR_FROM_DBUS && + err->code == VIR_ERR_DBUS_SERVICE && + err->level == VIR_ERR_ERROR && + STREQ_NULLABLE("org.freedesktop.DBus.Error.UnknownMethod", + err->str1); +} + + +bool +virGDBusMessageIsSignal(GDBusMessage *message, + const char *iface, + const char *signal) +{ + GDBusMessageType type = g_dbus_message_get_message_type(message); + + if (type == G_DBUS_MESSAGE_TYPE_SIGNAL) { + const char *interface = g_dbus_message_get_interface(message); + const char *member = g_dbus_message_get_member(message); + + return STREQ(interface, iface) && STREQ(member, signal); + } + + return false; +} diff --git a/src/util/virgdbus.h b/src/util/virgdbus.h new file mode 100644 index 0000000000..6ea717eea2 --- /dev/null +++ b/src/util/virgdbus.h @@ -0,0 +1,79 @@ +/* + * virgdbus.h: helper for using GDBus + * + * This library 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. + * + * This library 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 this library. If not, see + * . + * + */ + +#pragma once + +#include + +#ifdef G_OS_UNIX +# include +#endif + +#include "internal.h" + +void +virGDBusSetSharedBus(bool shared); + +GDBusConnection * +virGDBusGetSystemBus(void); + +GDBusConnection * +virGDBusGetSessionBus(void); + +bool +virGDBusHasSystemBus(void); + +void +virGDBusCloseSystemBus(void); + +int +virGDBusCallMethod(GDBusConnection *conn, + GVariant **reply, + virErrorPtr error, + const char *busName, + const char *objectPath, + const char *ifaceName, + const char *method, + GVariant *data); + +int +virGDBusCallMethodWithFD(GDBusConnection *conn, + GVariant **reply, + GUnixFDList **replyFD, + virErrorPtr error, + const char *busName, + const char *objectPath, + const char *ifaceName, + const char *method, + GVariant *data, + GUnixFDList *dataFD); + +int +virGDBusIsServiceEnabled(const char *name); + +int +virGDBusIsServiceRegistered(const char *name); + +bool +virGDBusErrorIsUnknownMethod(virErrorPtr err); + +bool +virGDBusMessageIsSignal(GDBusMessage *message, + const char *iface, + const char *signal);