#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_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