mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-01 02:25:24 +00:00
43ee7c6db1
We need to pass some flags in order to properly initialize the connection otherwise it will not work. This copies what GLib does for g_bus_get_sync() internally. This fixes an issue with LXC driver where libvirt was not able to register any VM with machined. Reported-by: Matthias Maier <tamiko@gentoo.org> Signed-off-by: Pavel Hrdina <phrdina@redhat.com> Reviewed-by: Ján Tomko <jtomko@redhat.com>
437 lines
12 KiB
C
437 lines
12 KiB
C
/*
|
|
* 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 {
|
|
GDBusConnectionFlags dbusFlags =
|
|
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
|
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION;
|
|
|
|
address = g_dbus_address_get_for_bus_sync(type, NULL, error);
|
|
if (*error)
|
|
return NULL;
|
|
return g_dbus_connection_new_for_address_sync(address,
|
|
dbusFlags,
|
|
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
|
|
* @replyType: pointer to GVariantType to validate reply data, 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,
|
|
const GVariantType *replyType,
|
|
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,
|
|
replyType,
|
|
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,
|
|
const GVariantType *replyType,
|
|
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,
|
|
replyType,
|
|
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,
|
|
const GVariantType *replyType 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,
|
|
G_VARIANT_TYPE("(as)"),
|
|
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)) {
|
|
g_free(str);
|
|
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;
|
|
}
|