summaryrefslogtreecommitdiff
path: root/systemd.c
diff options
context:
space:
mode:
Diffstat (limited to 'systemd.c')
-rw-r--r--systemd.c321
1 files changed, 321 insertions, 0 deletions
diff --git a/systemd.c b/systemd.c
new file mode 100644
index 0000000..f556a55
--- /dev/null
+++ b/systemd.c
@@ -0,0 +1,321 @@
+#include <string.h>
+#include <stdlib.h>
+#include <systemd/sd-bus.h>
+
+#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_service_object_path(struct cfg *cfg, const char *service)
+{
+ char *r;
+ char *d;
+ const char *s;
+
+ if (empty_str(service)) {
+ fprintf(stderr, "%s: called with NULL arg\n", __func__);
+ return NULL;
+ }
+
+ r = malloc(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(struct cfg *cfg)
+{
+ if (!cfg->sd_bus)
+ return;
+
+ sd_bus_unref(cfg->sd_bus);
+ cfg->sd_bus = NULL;
+}
+
+static sd_bus *
+get_bus(struct cfg *cfg)
+{
+ int r;
+
+ if (cfg->sd_bus_failed)
+ return NULL;
+
+ if (!cfg->sd_bus) {
+ r = sd_bus_open_user(&cfg->sd_bus);
+ if (r < 0) {
+ fprintf(stderr, "Failed to connect to user system bus: %s\n", 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 cfg *cfg, struct server *server)
+{
+ sd_bus *bus = get_bus(cfg);
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ char *status = NULL;
+ int r;
+ bool running = false;
+
+ if (!bus || !server->systemd_service || !server->systemd_obj)
+ return false;
+
+ r = sd_bus_get_property_string(bus,
+ SYSTEMD_DBUS_SERVICE,
+ server->systemd_obj,
+ SYSTEMD_DBUS_INTERFACE,
+ "ActiveState",
+ &error,
+ &status);
+ if (r < 0) {
+ fprintf(stderr, "%s: failed to get status for service %s (%s): %s\n",
+ __func__, server->systemd_service, server->systemd_obj, error.message);
+ goto out;
+ }
+
+ if (!strcmp(status, "active")) {
+ running = true;
+ fprintf(stderr, "Systemd service %s (%s) is active\n",
+ server->systemd_service, server->systemd_obj);
+ } else
+ fprintf(stderr, "Systemd service %s (%s) is not active\n",
+ server->systemd_service, server->systemd_obj);
+
+out:
+ free(status);
+ sd_bus_error_free(&error);
+ return running;
+}
+
+static bool
+systemd_service_action(struct cfg *cfg, struct server *server, const char *action)
+{
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_message *m = NULL;
+ sd_bus *bus = get_bus(cfg);
+ const char *path;
+ bool performed = false;
+ int r;
+
+ if (!bus || !server->systemd_service || !server->systemd_obj || !action)
+ return false;
+
+ r = sd_bus_call_method(bus,
+ SYSTEMD_DBUS_SERVICE,
+ server->systemd_obj,
+ SYSTEMD_DBUS_INTERFACE,
+ action,
+ &error,
+ &m,
+ "s",
+ "fail");
+ if (r < 0) {
+ fprintf(stderr, "Failed to perform action %s on systemd service %s: %s\n",
+ action, server->systemd_service, error.message);
+ goto out;
+ }
+
+ r = sd_bus_message_read(m, "o", &path);
+ if (r < 0) {
+ fprintf(stderr, "Failed to parse response message: %s\n", strerror(-r));
+ goto out;
+ }
+
+ fprintf(stderr, "Queued %s on service %s as %s\n",
+ action, server->systemd_service, path);
+ 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 cfg *cfg, struct server *server)
+{
+ return systemd_service_action(cfg, 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 cfg *cfg, struct server *server)
+{
+ return systemd_service_action(cfg, server, "Start");
+}
+
+
+#if 0
+
+
+
+
+int main(int argc, char *argv[]) {
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_message *m = NULL;
+ sd_bus *bus = NULL;
+ const char *path;
+ int r;
+
+ /* Connect to the system bus */
+ r = sd_bus_open_user(&bus);
+ if (r < 0) {
+ fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
+ goto finish;
+ }
+
+ /* Issue the method call and store the respons message in m */
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.systemd1", /* service to contact */
+ "/org/freedesktop/systemd1", /* object path */
+ "org.freedesktop.systemd1.Manager", /* interface name */
+ "StartUnit", /* method name */
+ &error, /* object to return error in */
+ &m, /* return message on success */
+ "ss", /* input signature */
+ "apa.service", /* first argument */
+ "fail"); /* second argument */
+ if (r < 0) {
+ fprintf(stderr, "Failed to issue method call: %s\n", error.message);
+ goto finish;
+ }
+
+ /* Parse the response message */
+ r = sd_bus_message_read(m, "o", &path);
+ if (r < 0) {
+ fprintf(stderr, "Failed to parse response message: %s\n", strerror(-r));
+ goto finish;
+ }
+ printf("Queued service job as %s.\n", path);
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(m);
+ error = SD_BUS_ERROR_NULL;
+ m = NULL;
+
+ /*
+ gdbus call --session
+ --dest org.freedesktop.systemd1
+ --object-path /org/freedesktop/systemd1/unit/myapa_40local2_2eservice
+ --method org.freedesktop.systemd1.Unit.Start "fail"
+
+ gdbus introspect --session
+ --dest org.freedesktop.systemd1
+ --object-path /org/freedesktop/systemd1/unit/myapa_40local_2eservice
+
+ gdbus call --session
+ --dest org.freedesktop.systemd1
+ --object-path /org/freedesktop/systemd1/unit/myapa_40basker_2eservice
+ --method org.freedesktop.DBus.Properties.Get
+ "org.freedesktop.systemd1.Unit"
+ "ActiveState"
+ */
+ char *res;
+ r = sd_bus_get_property_string(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1/unit/apa_2eservice",
+ "org.freedesktop.systemd1.Unit",
+ "ActiveState",
+ &error,
+ &res);
+ if (r < 0) {
+ fprintf(stderr, "Failed to issue prop call: %s\n", error.message);
+ goto finish;
+ }
+
+ fprintf(stderr, "RESULT: %s\n", res);
+/*
+ int sd_bus_get_property_string(sd_bus *bus,
+ const char *destination,
+ const char *path,
+ const char *interface,
+ const char *member,
+ sd_bus_error *ret_error,
+ char **ret);
+ */
+/*
+finish:
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(m);
+ sd_bus_unref(bus);
+ */
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+#endif