summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-10 11:22:29 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-10 11:22:29 +0200
commitfae819296598100e41646e0bebc7d0bce45614f7 (patch)
tree4680a7afdc64e278fe2018f6515ad88b8a54f80e
parenta270b41d55e3ab867e7c9aabf301ce9d9c48929f (diff)
Add initial systemd integration
-rw-r--r--cfgdir.c17
-rw-r--r--main.c2
-rw-r--r--main.h2
-rw-r--r--meson.build31
-rw-r--r--server.c134
-rw-r--r--server.h17
-rw-r--r--systemd.c321
-rw-r--r--systemd.h22
8 files changed, 508 insertions, 38 deletions
diff --git a/cfgdir.c b/cfgdir.c
index f961011..9fa0c78 100644
--- a/cfgdir.c
+++ b/cfgdir.c
@@ -32,9 +32,7 @@ enum scfg_keys {
SCFG_KEY_START_EXEC,
SCFG_KEY_RCON,
SCFG_KEY_RCON_PASSWORD,
- /*
SCFG_KEY_SYSTEMD_SERVICE,
- */
};
struct cfg_key_value_map scfg_key_map[] = {
@@ -87,6 +85,10 @@ struct cfg_key_value_map scfg_key_map[] = {
.key_value = SCFG_KEY_RCON_PASSWORD,
.value_type = CFG_VAL_TYPE_STRING,
}, {
+ .key_name = "systemd_service",
+ .key_value = SCFG_KEY_SYSTEMD_SERVICE,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
.key_name = NULL,
.key_value = SCFG_KEY_INVALID,
.value_type = CFG_VAL_TYPE_INVALID,
@@ -166,6 +168,9 @@ scfg_parse(struct cfg *cfg, struct server *scfg)
} else if (!strcmp(value.str, "rcon")) {
if (server_set_stop_method(cfg, scfg, SERVER_STOP_METHOD_RCON))
break;
+ } else if (!strcmp(value.str, "systemd")) {
+ if (server_set_stop_method(cfg, scfg, SERVER_STOP_METHOD_SYSTEMD))
+ break;
}
return;
@@ -173,6 +178,9 @@ scfg_parse(struct cfg *cfg, struct server *scfg)
if (!strcmp(value.str, "exec")) {
if (server_set_start_method(cfg, scfg, SERVER_START_METHOD_EXEC))
break;
+ } else if (!strcmp(value.str, "systemd")) {
+ if (server_set_start_method(cfg, scfg, SERVER_START_METHOD_SYSTEMD))
+ break;
}
return;
@@ -201,6 +209,11 @@ scfg_parse(struct cfg *cfg, struct server *scfg)
return;
break;
+ case SCFG_KEY_SYSTEMD_SERVICE:
+ if (!server_set_systemd_service(cfg, scfg, value.str))
+ return;
+ break;
+
case SCFG_KEY_INVALID:
default:
break;
diff --git a/main.c b/main.c
index da962b4..eace2d0 100644
--- a/main.c
+++ b/main.c
@@ -19,6 +19,7 @@
#include "server.h"
#include "cfgdir.h"
#include "announce.h"
+#include "systemd.h"
int debuglvl = 0;
@@ -62,6 +63,7 @@ cfg_free(struct uring_task *task)
struct cfg *cfg = container_of(task, struct cfg, task);
fprintf(stderr, "%s: called\n", __func__);
+ systemd_delete(cfg);
free(cfg);
fprintf(stderr, "All resources free, exiting\n");
exiting = true;
diff --git a/main.h b/main.h
index e6ced6f..086cd92 100644
--- a/main.h
+++ b/main.h
@@ -41,6 +41,8 @@ struct cfg {
struct inotify_ev *iev;
struct signalfd_ev *sev;
struct announce *aev;
+ struct sd_bus *sd_bus;
+ bool sd_bus_failed;
struct uring_task task;
struct list_head servers;
};
diff --git a/meson.build b/meson.build
index 7a89f88..1a6986d 100644
--- a/meson.build
+++ b/meson.build
@@ -1,18 +1,25 @@
project('mcproxy', 'c', default_options : ['c_std=gnu18'])
-uring = dependency('liburing')
+
+liburing = dependency('liburing')
+libsystemd = dependency('libsystemd')
+
+mcproxy_sources = [
+ 'main.c',
+ 'uring.c',
+ 'server.c',
+ 'proxy.c',
+ 'announce.c',
+ 'cfgdir.c',
+ 'config.c',
+ 'rcon.c',
+ 'idle.c',
+ 'systemd.c',
+ 'utils.c']
executable('ctest', 'ctest.c')
executable('stest', 'stest.c')
executable('mcproxy',
- ['main.c',
- 'uring.c',
- 'server.c',
- 'proxy.c',
- 'announce.c',
- 'cfgdir.c',
- 'config.c',
- 'rcon.c',
- 'idle.c',
- 'utils.c'],
- dependencies: uring)
+ mcproxy_sources,
+ dependencies : [liburing,
+ libsystemd])
diff --git a/server.c b/server.c
index 7ec0804..ee93a24 100644
--- a/server.c
+++ b/server.c
@@ -18,6 +18,7 @@
#include "utils.h"
#include "idle.h"
#include "rcon.h"
+#include "systemd.h"
struct server_local {
struct sockaddr_in46 addr;
@@ -67,6 +68,8 @@ server_free(struct uring_task *task)
free(scfg->pretty_name);
free(scfg->start_exec);
free(scfg->stop_exec);
+ free(scfg->systemd_service);
+ free(scfg->systemd_obj);
free(scfg->name);
free(scfg);
}
@@ -323,6 +326,9 @@ server_start(struct cfg *cfg, struct server *scfg)
case SERVER_START_METHOD_EXEC:
return server_exec(cfg, scfg, scfg->start_exec);
+ case SERVER_START_METHOD_SYSTEMD:
+ return systemd_service_start(cfg, scfg);
+
case SERVER_START_METHOD_UNDEFINED:
default:
break;
@@ -342,6 +348,9 @@ server_stop(struct cfg *cfg, struct server *scfg)
case SERVER_STOP_METHOD_EXEC:
return server_exec(cfg, scfg, scfg->stop_exec);
+ case SERVER_STOP_METHOD_SYSTEMD:
+ return systemd_service_stop(cfg, scfg);
+
case SERVER_STOP_METHOD_RCON:
rcon_init(cfg, scfg);
return true;
@@ -360,65 +369,112 @@ server_commit(struct cfg *cfg, struct server *scfg)
struct server_local *local;
uint16_t port;
- if (!scfg || !scfg->name)
+ if (!scfg || !scfg->name) {
+ fprintf(stderr, "%s: called with invalid parameters\n", __func__);
return false;
+ }
- if (!list_empty(&scfg->proxys))
+ if (!list_empty(&scfg->proxys)) {
+ fprintf(stderr, "%s(%s): proxys not empty?\n", __func__, scfg->name);
return false;
+ }
/* FIXME: running? */
if (scfg->stop_method == SERVER_STOP_METHOD_RCON &&
- (list_empty(&scfg->rcons) || !scfg->rcon_password))
+ (list_empty(&scfg->rcons) || !scfg->rcon_password)) {
+ fprintf(stderr, "%s(%s): rcon stop method but missing rcon password\n", __func__, scfg->name);
+ return false;
+ }
+
+ if ((scfg->start_method == SERVER_START_METHOD_SYSTEMD ||
+ scfg->stop_method == SERVER_STOP_METHOD_SYSTEMD) &&
+ !scfg->systemd_service) {
+ fprintf(stderr, "%s(%s): systemd start/stop method but missing systemd service\n", __func__, scfg->name);
+ return false;
+ }
+
+ if (scfg->systemd_service && !scfg->systemd_obj) {
+ scfg->systemd_obj = systemd_service_object_path(cfg,
+ scfg->systemd_service);
+ if (!scfg->systemd_obj) {
+ fprintf(stderr, "%s(%s): failed to create systemd object path (%s)\n", __func__, scfg->name, scfg->systemd_service);
+ return false;
+ }
+ }
+
+ if (scfg->idle_timeout > 0 &&
+ scfg->stop_method == SERVER_STOP_METHOD_UNDEFINED) {
+ fprintf(stderr, "%s(%s): idle_timeout set but missing stop method\n", __func__, scfg->name);
return false;
+ }
switch (scfg->type) {
case SERVER_TYPE_ANNOUNCE:
- if (scfg->announce_port < 1)
+ if (scfg->announce_port < 1) {
+ fprintf(stderr, "%s(%s): missing announce port\n", __func__, scfg->name);
return false;
- if (scfg->idle_timeout > 0 &&
- scfg->stop_method == SERVER_STOP_METHOD_UNDEFINED)
- return false;
- if (scfg->start_method != SERVER_START_METHOD_UNDEFINED)
+ }
+
+ if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) {
+ fprintf(stderr, "%s(%s): can't set start_method for announce server\n", __func__, scfg->name);
return false;
- if (!list_empty(&scfg->locals))
+ }
+
+ if (!list_empty(&scfg->locals)) {
+ fprintf(stderr, "%s(%s): can't set local addresses for announce server\n", __func__, scfg->name);
return false;
- if (!list_empty(&scfg->remotes))
+ }
+
+ if (!list_empty(&scfg->remotes)) {
+ fprintf(stderr, "%s(%s): can't set remote addresses for announce server\n", __func__, scfg->name);
return false;
+ }
+
break;
case SERVER_TYPE_PROXY:
- if (scfg->announce_port >= 1)
+ if (scfg->announce_port >= 1) {
+ fprintf(stderr, "%s(%s): can't set announce port for proxy server\n", __func__, scfg->name);
return false;
- if (scfg->idle_timeout > 0 &&
- scfg->stop_method == SERVER_STOP_METHOD_UNDEFINED)
- return false;
- if (list_empty(&scfg->locals))
+ }
+
+ if (list_empty(&scfg->locals)) {
+ fprintf(stderr, "%s(%s): missing local addresses for proxy server\n", __func__, scfg->name);
return false;
- if (list_empty(&scfg->remotes))
+ }
+
+ if (list_empty(&scfg->remotes)) {
+ fprintf(stderr, "%s(%s): missing remote addresses for proxy server\n", __func__, scfg->name);
return false;
+ }
list_for_each_entry(local, &scfg->locals, list) {
port = sockaddr_port(&local->addr);
- if (port == 0)
+ if (port == 0) {
+ fprintf(stderr, "%s(%s): invalid local port\n", __func__, scfg->name);
return false;
+ }
if (scfg->announce_port < 1)
scfg->announce_port = port;
if (scfg->announce_port != port) {
- fprintf(stderr, "Multiple announce ports!?\n");
+ fprintf(stderr, "%s(%s): multiple local ports\n", __func__, scfg->name);
return false;
}
}
- if (scfg->announce_port < 1)
+ if (scfg->announce_port < 1) {
+ fprintf(stderr, "%s(%s): can't determine which port to announce\n", __func__, scfg->name);
return false;
+ }
break;
default:
+ fprintf(stderr, "%s(%s): can't determine server type\n", __func__, scfg->name);
return false;
}
@@ -426,12 +482,16 @@ server_commit(struct cfg *cfg, struct server *scfg)
char *suffix;
suffix = strrchr(scfg->name, '.');
- if (!suffix || suffix == scfg->name)
+ if (!suffix || suffix == scfg->name) {
+ fprintf(stderr, "%s(%s): invalid server name\n", __func__, scfg->name);
return false;
+ }
scfg->pretty_name = strndup(scfg->name, suffix - scfg->name);
- if (!scfg->pretty_name)
+ if (!scfg->pretty_name) {
+ fprintf(stderr, "%s(%s): failed to create display name\n", __func__, scfg->name);
return false;
+ }
}
/* FIXME: config, dont reread config if server running, make sure fd is available before this is called */
@@ -443,6 +503,11 @@ server_commit(struct cfg *cfg, struct server *scfg)
idle_init(cfg, scfg);
+ if (scfg->systemd_service) {
+ fprintf(stderr, "Checking if systemd service is running\n");
+ systemd_service_running(cfg, scfg);
+ }
+
return true;
}
@@ -497,6 +562,33 @@ server_set_rcon_password(struct cfg *cfg, struct server *scfg,
}
bool
+server_set_systemd_service(struct cfg *cfg, struct server *scfg,
+ const char *service)
+{
+ const char *suffix;
+ char *tmp;
+
+ if (!cfg || !scfg || empty_str(service) || scfg->systemd_service)
+ return false;
+
+ suffix = strrchr(service, '.');
+ if (!suffix || strcmp(suffix, ".service")) {
+ tmp = malloc(strlen(service) + strlen(".service") + 1);
+ if (tmp)
+ sprintf(tmp, "%s.service", service);
+ } else
+ tmp = strdup(service);
+
+ if (!tmp) {
+ perror("malloc/strdup");
+ return false;
+ }
+
+ scfg->systemd_service = tmp;
+ return true;
+}
+
+bool
server_set_stop_method(struct cfg *cfg, struct server *scfg,
enum server_stop_method stop_method)
{
diff --git a/server.h b/server.h
index 495f7c4..8e0b15f 100644
--- a/server.h
+++ b/server.h
@@ -10,11 +10,13 @@ enum server_type {
enum server_stop_method {
SERVER_STOP_METHOD_UNDEFINED,
SERVER_STOP_METHOD_RCON,
+ SERVER_STOP_METHOD_SYSTEMD,
SERVER_STOP_METHOD_EXEC
};
enum server_start_method {
SERVER_START_METHOD_UNDEFINED,
+ SERVER_START_METHOD_SYSTEMD,
SERVER_START_METHOD_EXEC
};
@@ -31,12 +33,14 @@ struct server {
enum server_stop_method stop_method;
enum server_start_method start_method;
+
+ /* For calling external start/stop executables */
char *stop_exec;
char *start_exec;
- /* For config files */
- char buf[4096];
- size_t len;
+ /* For systemd services */
+ char *systemd_service;
+ char *systemd_obj;
/* For rcon connections */
struct rcon *rcon;
@@ -52,6 +56,10 @@ struct server {
unsigned idle_timeout;
unsigned idle_count;
+ /* For config files */
+ char buf[4096];
+ size_t len;
+
struct uring_task exec_task;
struct uring_task task;
struct list_head list;
@@ -81,6 +89,9 @@ bool server_add_rcon(struct cfg *cfg, struct server *scfg,
bool server_set_rcon_password(struct cfg *cfg, struct server *scfg,
const char *password);
+bool server_set_systemd_service(struct cfg *cfg, struct server *scfg,
+ const char *service);
+
bool server_set_stop_method(struct cfg *cfg, struct server *scfg,
enum server_stop_method stop_method);
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
diff --git a/systemd.h b/systemd.h
new file mode 100644
index 0000000..ccf63dd
--- /dev/null
+++ b/systemd.h
@@ -0,0 +1,22 @@
+#ifndef foosystemdhfoo
+#define foosystemdhfoo
+
+char *systemd_service_object_path(struct cfg *cfg, const char *service);
+
+void systemd_delete(struct cfg *cfg);
+
+bool systemd_service_running(struct cfg *cfg, struct server *server);
+
+bool systemd_service_stop(struct cfg *cfg, struct server *server);
+
+bool systemd_service_start(struct cfg *cfg, struct server *server);
+
+/*
+void idle_refdump(struct idle *idle);
+
+void idle_delete(struct cfg *cfg, struct server *server);
+
+void idle_init(struct cfg *cfg, struct server *server);
+*/
+
+#endif