lib: use libdbus for client's dbus messaging

Switch the dbus implementation for the client from systemd to
libdbus. The main reason is that, in flatpaks systemd is not easily
available. No phenomenological change for users of the library,
hopefully.
This commit is contained in:
Christian Kellner 2019-05-20 19:59:54 +02:00
parent 3a2ebd1cdf
commit e87a8f19f3
3 changed files with 158 additions and 58 deletions

View File

@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include <dbus/dbus.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdio.h>
@ -38,9 +39,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <systemd/sd-bus.h>
#include <unistd.h>
// For developmental purposes
#define DO_TRACE 0
// D-Bus name, path, iface
#define DAEMON_DBUS_NAME "com.feralinteractive.GameMode"
#define DAEMON_DBUS_PATH "/com/feralinteractive/GameMode"
@ -50,6 +53,29 @@ POSSIBILITY OF SUCH DAMAGE.
#define PORTAL_DBUS_PATH "/org/freedesktop/portal/desktop"
#define PORTAL_DBUS_IFACE "org.freedesktop.portal.GameMode"
// Cleanup macros
#define _cleanup_(x) __attribute__((cleanup(x)))
#define _cleanup_bus_ _cleanup_(hop_off_the_bus)
#define _cleanup_msg_ _cleanup_(cleanup_msg)
#define _cleanup_dpc_ _cleanup_(cleanup_pending_call)
#ifdef NDEBUG
#define DEBUG(...)
#define LOG_ERROR
#else
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define LOG_ERROR fprintf(stderr, "ERROR: %s \n", error_string)
#endif
#ifdef DO_TRACE
#define TRACE(...) fprintf(stderr, __VA_ARGS__)
#else
#define TRACE(...)
#endif
// Prototypes
static int log_error(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
// Storage for error strings
static char error_string[512] = { 0 };
@ -64,68 +90,138 @@ static int in_flatpak(void)
return r == 0 && sb.st_size > 0;
}
// Simple requestor function for a gamemode
static int gamemode_request(const char *function, int arg)
static int log_error(const char *fmt, ...)
{
sd_bus_message *msg = NULL;
sd_bus *bus = NULL;
sd_bus_error err;
memset(&err, 0, sizeof(err));
va_list args;
int n;
int result = -1;
va_start(args, fmt);
n = vsnprintf(error_string, sizeof(error_string), fmt, args);
va_end(args);
// Open the user bus
int ret = sd_bus_open_user(&bus);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
"Could not connect to bus: %s",
strerror(-ret));
} else {
int native = !in_flatpak();
if (n < 0)
DEBUG("Failed to format error string");
else if ((size_t)n >= sizeof(error_string))
DEBUG("Error log overflow");
// If we are inside a flatpak we need to talk to the portal instead
const char *dest = native ? DAEMON_DBUS_NAME : PORTAL_DBUS_NAME;
const char *path = native ? DAEMON_DBUS_PATH : PORTAL_DBUS_PATH;
const char *iface = native ? DAEMON_DBUS_IFACE : PORTAL_DBUS_IFACE;
TRACE("ERROR: %s \n", error_string);
// Attempt to send the requested function
ret = sd_bus_call_method(bus,
dest,
path,
iface,
function,
&err,
&msg,
arg ? "ii" : "i",
getpid(),
arg);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
"Could not call method %s on %s\n"
"\t%s\n"
"\t%s\n"
"\t%s\n",
function,
dest,
err.name,
err.message,
strerror(-ret));
} else {
// Read the reply
ret = sd_bus_message_read(msg, "i", &result);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
"Failure to parse response: %s",
strerror(-ret));
}
}
sd_bus_unref(bus);
return -1;
}
static void hop_off_the_bus(DBusConnection **bus)
{
if (bus == NULL)
return;
dbus_connection_unref(*bus);
}
static DBusConnection *hop_on_the_bus(void)
{
DBusConnection *bus;
DBusError err;
dbus_error_init(&err);
bus = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (bus == NULL) {
log_error("Could not connect to bus: %s", err.message);
dbus_error_free(&err);
}
return result;
return bus;
}
/* cleanup functions */
static void cleanup_msg(DBusMessage **msg)
{
if (msg == NULL)
return;
dbus_message_unref(*msg);
}
static void cleanup_pending_call(DBusPendingCall **call)
{
if (call == NULL)
return;
dbus_pending_call_unref(*call);
}
/* internal API */
static int gamemode_request(const char *method, pid_t for_pid)
{
_cleanup_bus_ DBusConnection *bus = NULL;
_cleanup_msg_ DBusMessage *msg = NULL;
_cleanup_dpc_ DBusPendingCall *call = NULL;
DBusMessageIter iter;
DBusError err;
dbus_int32_t pid;
int native;
int res = -1;
native = !in_flatpak();
pid = (dbus_int32_t)getpid();
TRACE("GM: [%d] request '%s' received (for pid: %d) [portal: %s]\n",
(int)pid,
method,
(int)for_pid,
(native ? "n" : "y"));
bus = hop_on_the_bus();
if (bus == NULL)
return -1;
// If we are inside a flatpak we need to talk to the portal instead
const char *dest = native ? DAEMON_DBUS_NAME : PORTAL_DBUS_NAME;
const char *path = native ? DAEMON_DBUS_PATH : PORTAL_DBUS_PATH;
const char *iface = native ? DAEMON_DBUS_IFACE : PORTAL_DBUS_IFACE;
msg = dbus_message_new_method_call(dest, path, iface, method);
if (!msg)
return log_error("Could not create dbus message");
dbus_message_iter_init_append(msg, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &pid);
if (for_pid != 0) {
dbus_int32_t p = (dbus_int32_t)for_pid;
dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &p);
}
dbus_connection_send_with_reply(bus, msg, &call, -1);
dbus_connection_flush(bus);
dbus_message_unref(msg);
msg = NULL;
dbus_pending_call_block(call);
msg = dbus_pending_call_steal_reply(call);
if (msg == NULL)
return log_error("Did not receive a reply");
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, msg))
log_error("Could not call method '%s' on '%s': %s", method, dest, err.message);
else if (!dbus_message_iter_init(msg, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32)
log_error("Failed to parse response");
else
dbus_message_iter_get_basic(&iter, &res);
TRACE("GM: [%d] request '%s' done: %d\n", (int)pid, method, res);
if (dbus_error_is_set(&err))
dbus_error_free(&err);
return res;
}
// Get the error string

View File

@ -12,7 +12,7 @@ gamemode = shared_library(
'client_impl.c',
],
dependencies: [
dep_systemd,
dep_dbus,
],
install: true,
soversion: lt_current,

View File

@ -79,15 +79,19 @@ path_libdir = join_paths(path_prefix, get_option('libdir'))
path_libexecdir = join_paths(path_prefix, get_option('libexecdir'))
# Find systemd via pkgconfig
with_systemd = get_option('with-systemd')
dep_systemd = dependency('libsystemd')
# For the client, libdbus is used
dep_dbus = dependency('dbus-1')
# Allow meson to figure out how the compiler sets up threading
dep_threads = dependency('threads')
# On non glibc systems this might be a stub, i.e. for musl
libdl = cc.find_library('dl', required: false)
with_systemd = get_option('with-systemd')
# Determine the location for the systemd unit
if with_systemd == true
# If the path isn't explicitly set, ask systemd for the systemd user unit directory
path_systemd_unit_dir = get_option('with-systemd-user-unit-dir')