From ea053d96f7e89e053d4af8d39b04c5428760345f Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 23 Jun 2020 20:56:22 +0200 Subject: Big renaming, move some more functionality to shared lib --- minecproxy/systemd.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 minecproxy/systemd.c (limited to 'minecproxy/systemd.c') diff --git a/minecproxy/systemd.c b/minecproxy/systemd.c new file mode 100644 index 0000000..a44b0d8 --- /dev/null +++ b/minecproxy/systemd.c @@ -0,0 +1,219 @@ +#include +#include +#include + +#include "main.h" +#include "server.h" +#include "systemd.h" + +#define SYSTEMD_DBUS_SERVICE "org.freedesktop.systemd1" +#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.systemd1.Unit" +#define SYSTEMD_DBUS_PATH_PREFIX "/org/freedesktop/systemd1/unit/" + +static inline char +tohex(uint8_t val) +{ + static const char hex[] = "0123456789abcdef"; + + return hex[val & 0x0f]; +} + +/* + * Creates an escaped D-Bus object path for a given systemd service + * + * Escaping rules are documented here: + * https://dbus.freedesktop.org/doc/dbus-specification.html + * + * Essentially, everyting but a-z, A-Z, 0-9 is replaced by _xx where xx is + * the hexadecimal value of the character. + * + * Example: minecraft@world1.service -> minecraft_40world1_2eservice + */ +char * +systemd_object_path(const char *service) +{ + char *r; + char *d; + const char *s; + + assert_return(service && !empty_str(service), NULL); + + r = zmalloc(strlen(SYSTEMD_DBUS_PATH_PREFIX) + strlen(service) * 3 + 1); + if (!r) + return NULL; + + memcpy(r, SYSTEMD_DBUS_PATH_PREFIX, strlen(SYSTEMD_DBUS_PATH_PREFIX)); + d = r + strlen(SYSTEMD_DBUS_PATH_PREFIX); + + for (s = service; *s; s++) { + if ((*s >= 'a' && *s <= 'z') || + (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9')) { + *(d++) = *s; + continue; + } + + *(d++) = '_'; + *(d++) = tohex(*s >> 4); + *(d++) = tohex(*s); + } + + *d = '\0'; + return r; +} + +void +systemd_delete() +{ + assert_return_silent(cfg->sd_bus); + + sd_bus_unref(cfg->sd_bus); + cfg->sd_bus = NULL; +} + +static sd_bus * +get_bus() +{ + int r; + + if (cfg->sd_bus_failed) + return NULL; + + if (!cfg->sd_bus) { + r = sd_bus_open_user(&cfg->sd_bus); + if (r < 0) { + error("failed to connect to user system bus: %s", strerror(-r)); + cfg->sd_bus_failed = true; + return NULL; + } + } + + return cfg->sd_bus; +} + +/* + * Check if a systemd service is running. + * + * This is equivalent to (assuming service minecraft@world1): + * gdbus call --session + * --dest org.freedesktop.systemd1 + * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice + * --method org.freedesktop.DBus.Properties.Get + * "org.freedesktop.systemd1.Unit" + * "ActiveState" + */ +bool +systemd_service_running(struct server *server) +{ + sd_bus *bus = get_bus(); + sd_bus_error error = SD_BUS_ERROR_NULL; + char *status = NULL; + bool running = false; + int r; + + assert_return(server && bus && server->systemd_service && server->systemd_obj, false); + + r = sd_bus_get_property_string(bus, + SYSTEMD_DBUS_SERVICE, + server->systemd_obj, + SYSTEMD_DBUS_INTERFACE, + "ActiveState", + &error, + &status); + if (r < 0) { + error("failed to get status for service %s (%s): %s", + server->systemd_service, server->systemd_obj, error.message); + goto out; + } + + if (streq(status, "active")) { + running = true; + debug(DBG_SYSD, "systemd service %s (%s) is active", + server->systemd_service, server->systemd_obj); + } else + debug(DBG_SYSD, "systemd service %s (%s) is not active", + server->systemd_service, server->systemd_obj); + +out: + free(status); + sd_bus_error_free(&error); + return running; +} + +static bool +systemd_service_action(struct server *server, const char *action) +{ + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL; + sd_bus *bus = get_bus(); + const char *path; + bool performed = false; + int r; + + assert_return(server && bus && server->systemd_service && server->systemd_obj && action, false); + + r = sd_bus_call_method(bus, + SYSTEMD_DBUS_SERVICE, + server->systemd_obj, + SYSTEMD_DBUS_INTERFACE, + action, + &error, + &m, + "s", + "fail"); + if (r < 0) { + error("failed to perform action %s on systemd service %s: %s", + action, server->systemd_service, error.message); + goto out; + } + + r = sd_bus_message_read(m, "o", &path); + if (r < 0) { + error("failed to parse response message: %s", strerror(-r)); + goto out; + } + + verbose("action %s queued for service %s", + action, server->systemd_service); + performed = true; + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + return performed; +} + +/* + * Stop systemd service. + * + * This is equivalent to (assuming service minecraft@world1): + * gdbus call --session + * --dest org.freedesktop.systemd1 + * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice + * --method org.freedesktop.systemd1.Unit.Stop "fail" + */ +bool +systemd_service_stop(struct server *server) +{ + assert_return(server, false); + + return systemd_service_action(server, "Stop"); +} + +/* + * Start systemd service. + * + * This is equivalent to (assuming service minecraft@world1): + * gdbus call --session + * --dest org.freedesktop.systemd1 + * --object-path /org/freedesktop/systemd1/unit/minecraft_40world1_2eservice + * --method org.freedesktop.systemd1.Unit.Start "fail" + */ +bool +systemd_service_start(struct server *server) +{ + assert_return(server, false); + + return systemd_service_action(server, "Start"); +} + -- cgit v1.2.3