From e87a8f19f31ad37ed35508bf9c5f548e738f0617 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Mon, 20 May 2019 19:59:54 +0200 Subject: [PATCH] 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. --- lib/client_impl.c | 208 +++++++++++++++++++++++++++++++++------------- lib/meson.build | 2 +- meson.build | 6 +- 3 files changed, 158 insertions(+), 58 deletions(-) diff --git a/lib/client_impl.c b/lib/client_impl.c index eea6329..66bdeab 100644 --- a/lib/client_impl.c +++ b/lib/client_impl.c @@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. #define _GNU_SOURCE +#include #include #include #include @@ -38,9 +39,11 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include +// 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 diff --git a/lib/meson.build b/lib/meson.build index 5d25cc8..7378e59 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -12,7 +12,7 @@ gamemode = shared_library( 'client_impl.c', ], dependencies: [ - dep_systemd, + dep_dbus, ], install: true, soversion: lt_current, diff --git a/meson.build b/meson.build index 8a1a728..b7bb874 100644 --- a/meson.build +++ b/meson.build @@ -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')