From 813bb6ecfd2c0ab820ba784337f107a9166472b4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 13 Apr 2026 10:30:21 +0300 Subject: [PATCH] sin: fix segfault; improve D-Bus service error handling Signed-off-by: NotAShelf Change-Id: I35ea89fb5ffecd42f644eac4c87ef9af6a6a6964 --- src/dbus-common.c | 231 ++++++++++++++++++++++++++++++++++++++ src/dbus-systemd-login1.c | 176 +++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 src/dbus-common.c create mode 100644 src/dbus-systemd-login1.c diff --git a/src/dbus-common.c b/src/dbus-common.c new file mode 100644 index 0000000..4d2f95c --- /dev/null +++ b/src/dbus-common.c @@ -0,0 +1,231 @@ +#include "dbus-common.h" +#include +#include +#include +#include + +int dbus_service_init(DBusService* svc, const char* bus_name, const char* object_path, + const char* interface_name) { + if (!svc || !bus_name || !object_path || !interface_name) { + return -1; + } + + memset(svc, 0, sizeof(*svc)); + svc->bus_name = strdup(bus_name); + svc->object_path = strdup(object_path); + svc->interface_name = strdup(interface_name); + svc->mode = DBUS_MODE_NONE; + + if (!svc->bus_name || !svc->object_path || !svc->interface_name) { + free(svc->bus_name); + free(svc->object_path); + free(svc->interface_name); + memset(svc, 0, sizeof(*svc)); + return -1; + } + + return 0; +} + +void dbus_service_cleanup(DBusService* svc) { + if (!svc) + return; + + if (svc->slot) { + sd_bus_slot_unref(svc->slot); + svc->slot = NULL; + } + + if (svc->bus) { + sd_bus_flush_close_unref(svc->bus); + svc->bus = NULL; + } + + free(svc->bus_name); + free(svc->object_path); + free(svc->interface_name); + + memset(svc, 0, sizeof(*svc)); +} + +int dbus_service_detect_mode(DBusService* svc) { + if (!svc || !svc->bus) + return -1; + + sd_bus_message* reply = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + int r = sd_bus_call_method(svc->bus, "org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus", "ListNames", &error, &reply, ""); + + if (r < 0) { + fprintf(stderr, "Failed to list D-Bus names: %s\n", error.message); + sd_bus_error_free(&error); + return -1; + } + + sd_bus_error_free(&error); + + char** names = NULL; + r = sd_bus_message_read_strv(reply, &names); + if (r < 0) { + fprintf(stderr, "Failed to read name list: %s\n", strerror(-r)); + sd_bus_message_unref(reply); + return -1; + } + + int name_owned = 0; + if (names) { + for (int i = 0; names[i]; i++) { + if (strcmp(names[i], svc->bus_name) == 0) { + name_owned = 1; + break; + } + } + free(names); + } + + sd_bus_message_unref(reply); + + svc->mode = name_owned ? DBUS_MODE_MONITOR : DBUS_MODE_IMPLEMENT; + return 0; +} + +int dbus_service_claim_name(DBusService* svc) { + if (!svc || !svc->bus) + return -1; + + int r = sd_bus_request_name(svc->bus, svc->bus_name, + SD_BUS_NAME_ALLOW_REPLACEMENT | SD_BUS_NAME_REPLACE_EXISTING); + + if (r < 0) { + fprintf(stderr, "Failed to claim D-Bus name %s: %s\n", svc->bus_name, strerror(-r)); + return -1; + } + + svc->mode = DBUS_MODE_IMPLEMENT; + return 0; +} + +int dbus_service_become_monitor(DBusService* svc, const char** match_rules, int num_rules) { + if (!svc || !svc->bus) + return -1; + + sd_bus_message* reply = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + sd_bus_message* m = NULL; + int r = + sd_bus_message_new_method_call(svc->bus, &m, "org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus.Monitoring", "BecomeMonitor"); + if (r < 0) { + fprintf(stderr, "Failed to create BecomeMonitor message: %s\n", strerror(-r)); + return -1; + } + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + goto fail; + + for (int i = 0; i < num_rules; i++) { + r = sd_bus_message_append_basic(m, 's', match_rules[i]); + if (r < 0) + goto fail; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + goto fail; + + uint32_t flags = 0; + r = sd_bus_message_append_basic(m, 'u', &flags); + if (r < 0) + goto fail; + + r = sd_bus_call(svc->bus, m, 0, &error, &reply); + sd_bus_message_unref(m); + + if (r < 0) { + fprintf(stderr, "BecomeMonitor failed: %s\n", error.message); + sd_bus_error_free(&error); + return -1; + } + + sd_bus_error_free(&error); + sd_bus_message_unref(reply); + + svc->mode = DBUS_MODE_MONITOR; + return 0; + +fail: + sd_bus_message_unref(m); + return -1; +} + +int dbus_service_poll(DBusService* svc) { + if (!svc || !svc->bus) + return -1; + + int r = sd_bus_process(svc->bus, NULL); + if (r < 0) { + fprintf(stderr, "D-Bus process error: %s\n", strerror(-r)); + return -1; + } + + if (r == 0) { + r = sd_bus_wait(svc->bus, 0); + if (r < 0) { + fprintf(stderr, "D-Bus wait error: %s\n", strerror(-r)); + return -1; + } + } + + return 0; +} + +int dbus_parse_inhibit_request(sd_bus_message* m, char** app_name, char** reason) { + if (!m || !app_name || !reason) + return -1; + + *app_name = NULL; + *reason = NULL; + + int r = sd_bus_message_read(m, "ss", app_name, reason); + if (r < 0) { + return -1; + } + + if (*app_name) + *app_name = strdup(*app_name); + if (*reason) + *reason = strdup(*reason); + + return 0; +} + +int dbus_send_signal(DBusService* svc, const char* signal_name, const char* signature, ...) { + if (!svc || !svc->bus) + return -1; + + sd_bus_message* m = NULL; + int r = + sd_bus_message_new_signal(svc->bus, &m, svc->object_path, svc->interface_name, signal_name); + if (r < 0) + return -1; + + if (signature && signature[0]) { + va_list args; + va_start(args, signature); + r = sd_bus_message_appendv(m, signature, args); + va_end(args); + if (r < 0) { + sd_bus_message_unref(m); + return -1; + } + } + + r = sd_bus_send(svc->bus, m, NULL); + sd_bus_message_unref(m); + + return r < 0 ? -1 : 0; +} diff --git a/src/dbus-systemd-login1.c b/src/dbus-systemd-login1.c new file mode 100644 index 0000000..50ebe2e --- /dev/null +++ b/src/dbus-systemd-login1.c @@ -0,0 +1,176 @@ +#include "dbus-systemd-login1.h" +#include "dbus-common.h" +#include +#include +#include +#include + +typedef struct { + DBusService service; + InhibitEventCallback callback; + uint32_t next_cookie; +} SystemdLogin1Data; + +static int login1_method_inhibit(sd_bus_message* m, void* userdata, sd_bus_error* ret_error) { + SystemdLogin1Data* data = userdata; + + char *what = NULL, *who = NULL, *why = NULL, *mode = NULL; + int r = sd_bus_message_read(m, "ssss", &what, &who, &why, &mode); + if (r < 0) { + return r; + } + + sd_bus_message* reply = NULL; + r = sd_bus_call_method(data->service.bus, "org.freedesktop.login1", "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", "Inhibit", ret_error, &reply, "ssss", + what ? what : "", who ? who : "sin", why ? why : "inhibited", + mode ? mode : "block"); + if (r < 0) { + sd_bus_message_unref(reply); + return sd_bus_reply_method_error(m, ret_error); + } + + int fd = -1; + r = sd_bus_message_read(reply, "h", &fd); + if (r < 0) { + fd = -1; + } + sd_bus_message_unref(reply); + + uint32_t cookie = data->next_cookie++; + + InhibitType type = INHIBIT_TYPE_SUSPEND; + if (what && strstr(what, "idle")) { + type = INHIBIT_TYPE_IDLE; + } else if (what && strstr(what, "sleep")) { + type = INHIBIT_TYPE_SUSPEND; + } + + InhibitEntry entry = {.type = type, .cookie = cookie}; + snprintf(entry.id, sizeof(entry.id), "login1_%u", cookie); + strncpy(entry.app_name, who ? who : "unknown", sizeof(entry.app_name) - 1); + strncpy(entry.reason, why ? why : "", sizeof(entry.reason) - 1); + + if (data->callback) { + data->callback((InhibitInterface*) data->service.parent, &entry, 1); + } + + return sd_bus_reply_method_return(m, "h", fd); +} + +static const sd_bus_vtable login1_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Inhibit", "ssss", "h", login1_method_inhibit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END}; + +static int login1_init(InhibitInterface* iface) { + if (!iface || !iface->private_data) + return -1; + + SystemdLogin1Data* data = iface->private_data; + + int r = dbus_service_init(&data->service, "org.freedesktop.login1", "/org/freedesktop/login1", + "org.freedesktop.login1.Manager"); + if (r < 0) + return -1; + + r = sd_bus_open_system(&data->service.bus); + if (r < 0) { + fprintf(stderr, "Failed to open system bus for login1 (may need root): %s\n", strerror(-r)); + dbus_service_cleanup(&data->service); + return -1; + } + + data->service.parent = iface; + + r = dbus_service_detect_mode(&data->service); + if (r < 0) { + dbus_service_cleanup(&data->service); + return -1; + } + + if (data->service.mode == DBUS_MODE_IMPLEMENT) { + r = dbus_service_claim_name(&data->service); + if (r < 0) { + dbus_service_cleanup(&data->service); + return -1; + } + + r = sd_bus_add_object_vtable(data->service.bus, &data->service.slot, data->service.object_path, + data->service.interface_name, login1_vtable, data); + if (r < 0) { + fprintf(stderr, "Failed to add login1 vtable: %s\n", strerror(-r)); + dbus_service_cleanup(&data->service); + return -1; + } + } else { + const char* rules[] = {"interface='org.freedesktop.login1.Manager'"}; + r = dbus_service_become_monitor(&data->service, rules, 1); + if (r < 0) { + dbus_service_cleanup(&data->service); + return -1; + } + } + + return 0; +} + +static void login1_cleanup(InhibitInterface* iface) { + if (!iface || !iface->private_data) + return; + SystemdLogin1Data* data = iface->private_data; + dbus_service_cleanup(&data->service); +} + +static int login1_inhibit(InhibitInterface* iface, InhibitType type, const char* app_name, + const char* reason, InhibitEntry* out_entry) { + (void) iface; + (void) type; + (void) app_name; + (void) reason; + (void) out_entry; + return 0; +} + +static int login1_uninhibit(InhibitInterface* iface, const InhibitEntry* entry) { + (void) iface; + (void) entry; + return 0; +} + +static int login1_poll(InhibitInterface* iface) { + if (!iface || !iface->private_data) + return -1; + SystemdLogin1Data* data = iface->private_data; + return dbus_service_poll(&data->service); +} + +static void login1_set_callback(InhibitInterface* iface, InhibitEventCallback cb) { + if (!iface || !iface->private_data) + return; + SystemdLogin1Data* data = iface->private_data; + data->callback = cb; +} + +InhibitInterface* dbus_systemd_login1_create(void) { + InhibitInterface* iface = calloc(1, sizeof(InhibitInterface)); + if (!iface) + return NULL; + + SystemdLogin1Data* data = calloc(1, sizeof(SystemdLogin1Data)); + if (!data) { + free(iface); + return NULL; + } + + iface->name = "systemd-login1"; + iface->init = login1_init; + iface->cleanup = login1_cleanup; + iface->inhibit = login1_inhibit; + iface->uninhibit = login1_uninhibit; + iface->poll = login1_poll; + iface->set_callback = login1_set_callback; + iface->private_data = data; + + return iface; +}