Browse Source

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.
Christian Kellner 5 years ago
parent
commit
e87a8f19f3
3 changed files with 162 additions and 62 deletions
  1. 156 60
      lib/client_impl.c
  2. 1 1
      lib/meson.build
  3. 5 1
      meson.build

+ 156 - 60
lib/client_impl.c

@@ -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, ...)
+{
+	va_list args;
+	int n;
+
+	va_start(args, fmt);
+	n = vsnprintf(error_string, sizeof(error_string), fmt, args);
+	va_end(args);
+
+	if (n < 0)
+		DEBUG("Failed to format error string");
+	else if ((size_t)n >= sizeof(error_string))
+		DEBUG("Error log overflow");
+
+	TRACE("ERROR: %s \n", error_string);
+
+	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 bus;
+}
+
+/* cleanup functions */
+static void cleanup_msg(DBusMessage **msg)
 {
-	sd_bus_message *msg = NULL;
-	sd_bus *bus = NULL;
-	sd_bus_error err;
-	memset(&err, 0, sizeof(err));
-
-	int result = -1;
-
-	// 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 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;
-
-		// 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);
+	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);
 	}
 
-	return result;
+	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

+ 1 - 1
lib/meson.build

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

+ 5 - 1
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')