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);