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] <https://developer.gnome.org/glib/stable/gvariant-format-strings.html>

Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
This commit is contained in:
Pavel Hrdina 2020-09-08 13:55:24 +02:00
parent 1db95f72e1
commit 2ef84f000f
6 changed files with 525 additions and 1 deletions

View File

@ -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 ],
)

View File

@ -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

View File

@ -2190,6 +2190,20 @@ virFirmwareParse;
virFirmwareParseList;
# util/virgdbus.h
virGDBusCallMethod;
virGDBusCallMethodWithFD;
virGDBusCloseSystemBus;
virGDBusErrorIsUnknownMethod;
virGDBusGetSessionBus;
virGDBusGetSystemBus;
virGDBusHasSystemBus;
virGDBusIsServiceEnabled;
virGDBusIsServiceRegistered;
virGDBusMessageIsSignal;
virGDBusSetSharedBus;
# util/virgettext.h
virGettextInitialize;

View File

@ -33,6 +33,7 @@ util_sources = [
'virfirewall.c',
'virfirewalld.c',
'virfirmware.c',
'virgdbus.c',
'virgettext.c',
'virgic.c',
'virhash.c',

425
src/util/virgdbus.c Normal file
View File

@ -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
* <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#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;
}

79
src/util/virgdbus.h Normal file
View File

@ -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
* <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <gio/gio.h>
#ifdef G_OS_UNIX
# include <gio/gunixfdlist.h>
#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);