sin: fix segfault; improve D-Bus service error handling

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I35ea89fb5ffecd42f644eac4c87ef9af6a6a6964
This commit is contained in:
raf 2026-04-13 10:30:21 +03:00
commit 813bb6ecfd
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 407 additions and 0 deletions

231
src/dbus-common.c Normal file
View file

@ -0,0 +1,231 @@
#include "dbus-common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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;
}

176
src/dbus-systemd-login1.c Normal file
View file

@ -0,0 +1,176 @@
#include "dbus-systemd-login1.h"
#include "dbus-common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <systemd/sd-bus.h>
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;
}