Add basic unit control dbus api 12/203412/5
authorMaciej Slodczyk <m.slodczyk2@partner.samsung.com>
Thu, 11 Apr 2019 15:13:28 +0000 (17:13 +0200)
committerMaciej Slodczyk <m.slodczyk2@partner.samsung.com>
Mon, 29 Apr 2019 14:44:33 +0000 (16:44 +0200)
Change-Id: Ie6280423f312a0ca58e2464fce437cfab85355d9
Signed-off-by: Maciej Slodczyk <m.slodczyk2@partner.samsung.com>
Makefile.am
include/unit_control_event.h [new file with mode: 0644]
modules.conf.d/unit_control_eh.conf.d/unit_control_eh.conf [new file with mode: 0644]
org.tizen.Activationd.conf [new file with mode: 0644]
packaging/activationd.spec
src/decision_makers/unit_control_dm.c [new file with mode: 0644]
src/event_types/unit_control_event.c [new file with mode: 0644]
src/listeners/unit_control_api.c [new file with mode: 0644]

index ca36ab2a24daba9e0ba8e0dfd6cf1db2e8d69563..42632a974735f20f2e45ce06b302ab6490dbdbfa 100644 (file)
@@ -125,18 +125,26 @@ modules_LTLIBRARIES = \
                  unit_start_action.la \
                  vconf_key_changed_event.la \
                  dbus_signal_event.la \
+                 unit_control_event.la \
                  activation_dm.la \
                  vconf_listener.la \
-                 dbus_listener.la
+                 dbus_listener.la \
+                 unit_control_api.la \
+                 unit_control_dm.la
+
 
 service_restart_action_la_SOURCES = src/action/service_restart.c
 unit_start_action_la_SOURCES = src/action/unit_start.c
 vconf_key_changed_event_la_SOURCES = src/event_types/vconf_key_changed_event.c
 dbus_signal_event_la_SOURCES = src/event_types/dbus_signal_event.c
+unit_control_event_la_SOURCES = src/event_types/unit_control_event.c
 activation_dm_la_SOURCES = src/decision_makers/activation_dm.c
 activation_dm_config_DATA = modules.conf.d/activation_eh.conf.d/activation_eh.conf
 activation_dm_config_DATA += modules.conf.d/activation_eh.conf.d/99-acceptance-test.conf
 activation_dm_configdir = $(modulesconfigdir)/activation_eh.conf.d
+unit_control_dm_la_SOURCES = src/decision_makers/unit_control_dm.c
+unit_control_dm_config_DATA = modules.conf.d/unit_control_eh.conf.d/unit_control_eh.conf
+unit_control_dm_configdir = $(modulesconfigdir)/unit_control_eh.conf.d
 vconf_listener_la_SOURCES = src/listeners/vconf.c
 vconf_listener_config_DATA = modules.conf.d/vconf_listener.conf.d/50-default.conf
 vconf_listener_config_DATA += modules.conf.d/vconf_listener.conf.d/99-acceptance-test.conf
@@ -145,6 +153,9 @@ dbus_listener_la_SOURCES = src/listeners/dbus.c
 dbus_listener_config_DATA = modules.conf.d/dbus_listener.conf.d/50-default.conf
 dbus_listener_config_DATA += modules.conf.d/dbus_listener.conf.d/99-acceptance-test.conf
 dbus_listener_configdir = $(modulesconfigdir)/dbus_listener.conf.d
+unit_control_api_la_SOURCES = src/listeners/unit_control_api.c
+unit_control_api_config_DATA = org.tizen.Activationd.conf
+unit_control_api_configdir = /etc/dbus-1/system.d/
 
 actd_LDADD = $(LIBSYSTEMD_LIBS) $(GLIB_LIBS) $(JSON_C_LIBS)
 
diff --git a/include/unit_control_event.h b/include/unit_control_event.h
new file mode 100644 (file)
index 0000000..40d40e9
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * This file is a part of epc.
+ *
+ * Copyright © 2019 Samsung Electronics
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNIT_CONTROL_EVENT_H
+#define UNIT_CONTROL_EVENT_H
+
+#include <time.h>
+#include "event.h"
+
+#define UC_EVENT_ID "unit_control"
+#define UC_TIME "et"
+#define UC_UNIT "unit"
+#define UC_METHOD "method"
+
+struct unit_control_event {
+       struct epc_event event;
+       char *method;
+       char *unit;
+       time_t event_time;
+};
+
+struct unit_control_event_data {
+       char *method;
+       char *unit;
+       time_t event_time;
+};
+
+#define to_unit_control_event(EVENT)                                           \
+       container_of(EVENT, struct unit_control_event, event)
+
+
+#endif /* UNIT_CONTROL_EVENT_H */
diff --git a/modules.conf.d/unit_control_eh.conf.d/unit_control_eh.conf b/modules.conf.d/unit_control_eh.conf.d/unit_control_eh.conf
new file mode 100644 (file)
index 0000000..4673f8e
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "whitelist":[ "activationd-acceptance-test-s-p.service", "activationd-acceptance-test-s-n.service" ]
+}
diff --git a/org.tizen.Activationd.conf b/org.tizen.Activationd.conf
new file mode 100644 (file)
index 0000000..c77b123
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+       <policy user="root">
+               <allow own="org.tizen.Activationd"/>
+               <allow send_destination="org.tizen.Activationd"/>
+       </policy>
+       <policy context="default">
+               <deny send_destination="org.tizen.Activationd"/>
+       </policy>
+</busconfig>
index 98b382bc13b895f77af2854e2aa9a73e66c473a2..c07e58873c4ec093438f4c84b61c1bf1b280a17b 100644 (file)
@@ -9,6 +9,7 @@ Group:      System/Monitoring
 Requires:   event-processing-core
 Requires:   event-processing-vconf
 Requires:   event-processing-dbus
+Requires:   event-processing-unit-control
 Requires:   event-processing-activation-dm
 BuildRequires: pkgconfig(vconf)
 
@@ -49,6 +50,13 @@ This package provides a listener for vconf events
 Summary:    dbus listener module for epc
 Group:      System/Monitoring
 
+%package -n event-processing-unit-control
+Summary:    unit control dbus api module for epc
+Group:      System/Monitoring
+
+%description -n event-processing-unit-control
+This package provides dbus api for unit control
+
 %description -n event-processing-dbus
 This package provides a listener for dbus events
 
@@ -124,6 +132,9 @@ mkdir -p %{buildroot}/%{enabled_moduledir}
 %install_module vconf_listener vconf
 %install_module dbus_signal_event dbus
 %install_module dbus_listener dbus
+%install_module unit_control_api unit-control
+%install_module unit_control_event unit-control
+%install_module unit_control_dm unit-control
 %install_module activation_dm activation-dm
 
 %files
@@ -143,6 +154,11 @@ mkdir -p %{buildroot}/%{enabled_moduledir}
 %files -n event-processing-dbus -f dbus-files
 %{_prefix}/lib/actd/modules.conf.d/dbus_listener.conf.d/50-default.conf
 
+%files -n event-processing-unit-control -f unit-control-files
+%license COPYING
+%{_sysconfdir}/dbus-1/system.d/org.tizen.Activationd.conf
+%{_prefix}/lib/actd/modules.conf.d/unit_control_eh.conf.d/unit_control_eh.conf
+
 %files -n activationd-test-services
 %license COPYING
 %manifest %{name}.manifest
diff --git a/src/decision_makers/unit_control_dm.c b/src/decision_makers/unit_control_dm.c
new file mode 100644 (file)
index 0000000..8db047e
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * This file is part of unit_controld.
+ *
+ * Copyright © 2019 Samsung Electronics
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <malloc.h>
+#include <errno.h>
+
+#include "decision_made_event.h"
+#include "action.h"
+#include "event_processor.h"
+#include "log.h"
+#include "service.h"
+#include "common.h"
+#include "database.h"
+#include "json-config.h"
+
+#include "unit_control_event.h"
+
+#define MODULE_NAME "unit_control_decision_maker"
+
+struct unit_control_decision_maker {
+       struct epc_event_handler eh;
+       struct list_head whitelist;
+};
+
+struct whitelist_entry {
+       char *unit;
+       struct list_head node;
+};
+
+static int unit_control_event_match(struct epc_event_handler *handler,
+               struct epc_event *ev)
+{
+       if (strcmp(ev->type->name, UC_EVENT_ID) == 0)
+                       return 1;
+       return 0;
+}
+
+static int unit_control_execute(const char *unit, const char *command, struct epc_event *ev)
+{
+       struct dm_event_data ev_data = {
+               .reason = ev,
+               .who_made = MODULE_NAME,
+       };
+       struct epc_event *new_ev;
+       int ret;
+
+       assert(unit);
+       assert(command);
+       assert(ev);
+
+       ev_data.action = EPC_ACTION_UNIT_START_ID;
+       ret = epc_object_new(&ev_data.action_data);
+       if (ret < 0) {
+               log_error("Could not create data object for action");
+               goto out;
+       }
+
+       ret = epc_fill_for_unit_start(ev_data.action_data, unit);
+       if (ret) {
+               log_error("Unable to create event data");
+               goto out;
+       }
+
+       if (!strcmp(command, "start"))
+               epc_fill_for_unit_action(ev_data.action_data, "StartUnit");
+       else if (!strcmp(command, "stop"))
+               epc_fill_for_unit_action(ev_data.action_data, "StopUnit");
+       else if (!strcmp(command, "restart"))
+               epc_fill_for_unit_action(ev_data.action_data, "RestartUnit");
+       else {
+               ret = -EINVAL;
+               log_error("Could not dispatch action: %s", command);
+               goto out;
+       }
+
+       ret = epc_event_create(DECISION_MADE_EVENT_ID, &ev_data, &new_ev);
+       if (ret) {
+               log_error("Unable to create event");
+               goto out;
+       }
+
+       ret = event_processor_report_event(new_ev);
+       epc_event_unref(new_ev);
+       if (ret) {
+               log_error("Unable to report event");
+               goto out;
+       }
+
+out:
+       epc_object_unref(ev_data.action_data);
+       return ret;
+}
+
+static int match_unit(const char *unit_name, const char *unit_whitelist_rule)
+{
+       if (!strcmp(unit_name, unit_whitelist_rule))
+               return 1;
+       return 0;
+}
+
+static int unit_control_make_decision(struct epc_event_handler *handler)
+{
+       struct epc_event *event = pop_epc_event(&handler->event_queue);
+       struct unit_control_decision_maker *dm = container_of(handler,
+                       struct unit_control_decision_maker, eh);
+       struct unit_control_event *ev = to_unit_control_event(event);
+       int ret;
+       struct whitelist_entry *w;
+
+       list_for_each_entry(w, &dm->whitelist, node) {
+               if (!match_unit(ev->unit, w->unit))
+                       continue;
+
+               ret = unit_control_execute(ev->unit, ev->method, event);
+               if (ret < 0)
+                       break;
+       }
+
+       epc_object_unref(event);
+       return 0;
+}
+
+static void cleanup_whitelist_entry(struct whitelist_entry *w)
+{
+       free(w->unit);
+       free(w);
+}
+
+static void cleanup_whitelist(struct unit_control_decision_maker *dm)
+{
+       struct whitelist_entry *w, *next;
+
+       list_for_each_entry_safe(w, next, &dm->whitelist, node) {
+               list_del_init(&w->node);
+               cleanup_whitelist_entry(w);
+       }
+}
+
+static int add_whitelist_entry(struct unit_control_decision_maker *dm, const char *unit)
+{
+       struct whitelist_entry *w;
+
+       w = calloc(1, sizeof(*w));
+       if (!w) {
+               log_error("Could not allocate whitelist entry object");
+               return -ENOMEM;
+       }
+
+       log_debug("Adding whitelist etry for unit %s", unit);
+
+       w->unit = strdup(unit);
+       list_add_tail(&w->node, &dm->whitelist);
+       return 0;
+}
+
+static int unit_control_init(struct epc_event_handler *handler, struct epc_config *config)
+{
+       struct unit_control_decision_maker *dm = container_of(handler,
+                       struct unit_control_decision_maker, eh);
+       int len;
+       int i;
+       int ret;
+       json_object *array, *obj;
+
+       INIT_LIST_HEAD(&dm->whitelist);
+
+       if (config == NULL)
+               return 0;
+
+       if (!json_object_object_get_ex(config->root, "whitelist", &array)) {
+               log_error("There is no 'whitelist' array");
+               return -EINVAL;
+       }
+
+       if (!json_object_is_type(array, json_type_array)) {
+               log_error("Config value is not an array");
+               return -EINVAL;
+       }
+
+       len = json_object_array_length(array);
+       for (i = 0; i < len; ++i) {
+               obj = json_object_array_get_idx(array, i);
+               ret = add_whitelist_entry(dm, json_object_get_string(obj));
+               if (ret < 0) {
+                       log_error("Could not add whitelist entry");
+                       goto cleanup;
+               }
+       }
+
+       return 0;
+
+cleanup:
+       cleanup_whitelist(dm);
+       return ret;
+};
+
+static void unit_control_cleanup(struct epc_event_handler *handler)
+{
+       struct unit_control_decision_maker *dm = container_of(handler,
+                       struct unit_control_decision_maker, eh);
+
+       cleanup_whitelist(dm);
+}
+
+static struct unit_control_decision_maker unit_control_dm = {
+       .eh = {
+               .name = MODULE_NAME,
+               .init = unit_control_init,
+               .cleanup = unit_control_cleanup,
+               .event_match = unit_control_event_match,
+               .handle_event = unit_control_make_decision,
+               .node = LIST_HEAD_INIT(unit_control_dm.eh.node),
+       },
+};
+
+EPC_EVENT_HANDLER_REGISTER(unit_control_dm.eh, unit_control_eh,
+               EPC_MODULE_TYPE_DECISION_MAKER)
diff --git a/src/event_types/unit_control_event.c b/src/event_types/unit_control_event.c
new file mode 100644 (file)
index 0000000..5da0da7
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * This file is part of epc.
+ *
+ * Copyright © 2019 Samsung Electronics
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <malloc.h>
+
+#include "unit_control_event.h"
+
+static int allocate_uc_event(struct epc_event_type *type,
+                                                        void *data, struct epc_event **ev)
+{
+       struct unit_control_event *uc_ev;
+       struct unit_control_event_data *uc_ev_data = data;
+       int ret;
+
+       uc_ev = calloc(1, sizeof(*uc_ev));
+       if (!uc_ev)
+               return -ENOMEM;
+
+       ret = epc_event_init_internal(type, &uc_ev->event);
+       if (ret)
+               goto cleanup;
+
+       uc_ev->event_time = uc_ev_data->event_time;
+       uc_ev->method = uc_ev_data->method;
+       uc_ev->unit = uc_ev_data->unit;
+
+       *ev = &uc_ev->event;
+       return 0;
+cleanup:
+       free(uc_ev);
+
+       return ret;
+}
+
+static int deserialize_uc_event(struct epc_event_type *type,
+                                                               struct epc_object *data, struct epc_event **ev)
+{
+       int ret = -EINVAL;
+       struct unit_control_event_data uc_ev_data;
+       struct epc_object *obj;
+       memset(&uc_ev_data, 0, sizeof(uc_ev_data));
+
+       list_for_each_entry(obj, &data->val.children, node) {
+               switch (obj->type) {
+               case TYPE_TIME_T:
+                       if (!strcmp(UC_TIME, obj->key))
+                               uc_ev_data.event_time = obj->val.time;
+                       break;
+               case TYPE_STRING:
+                       if (!strcmp(UC_METHOD, obj->key)) {
+                               uc_ev_data.method = obj->val.s;
+                       } else if (!strcmp(UC_UNIT, obj->key)) {
+                               uc_ev_data.unit = obj->val.s;
+                       }
+                       break;
+               }
+       }
+
+       ret = allocate_uc_event(type, &uc_ev_data, ev);
+       if (ret < 0)
+               goto finish;
+
+       ret = epc_event_deserialize_internal(data, type, *ev);
+       if (ret < 0) {
+               struct unit_control_event *uc_ev =
+                       to_unit_control_event(*ev);
+               free(uc_ev);
+               goto finish;
+       }
+       ret = 0;
+finish:
+       return ret;
+}
+
+static void uc_event_release(struct epc_event *ev)
+{
+       struct unit_control_event *uc_ev =
+               to_unit_control_event(ev);
+
+       if (uc_ev->method)
+               free(uc_ev->method);
+       if (uc_ev->unit)
+               free(uc_ev->unit);
+
+       epc_event_cleanup_internal(&uc_ev->event);
+       free(uc_ev);
+}
+
+static void uc_event_serialize(struct epc_event *ev, struct epc_object *out)
+{
+       struct unit_control_event *uc_ev =
+               to_unit_control_event(ev);
+       epc_event_serialize_internal(ev, out);
+       epc_object_append_string(out, UC_METHOD, uc_ev->method);
+       epc_object_append_time_t(out, UC_TIME, uc_ev->event_time);
+}
+
+static struct epc_event_type unit_control_event_type = {
+       .name = UC_EVENT_ID,
+       .default_ops = {
+               .release = uc_event_release,
+               .serialize = uc_event_serialize,
+       },
+       .allocate_event = allocate_uc_event,
+       .deserialize_event = deserialize_uc_event,
+       .node = LIST_HEAD_INIT(unit_control_event_type.node),
+};
+
+EPC_EVENT_TYPE_REGISTER(unit_control_event_type, unit_control_et)
diff --git a/src/listeners/unit_control_api.c b/src/listeners/unit_control_api.c
new file mode 100644 (file)
index 0000000..860d88e
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * This file is part of epc.
+ *
+ * Copyright © 2019 Samsung Electronics
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "log.h"
+#include "module.h"
+#include "common.h"
+#include "event_processor.h"
+#include "systemd_dbus.h"
+#include "unit_control_event.h"
+
+#define ACTD_DBUS_API_INTERFACE_NAME "org.tizen.Activationd"
+#define ACTD_DBUS_API_SERVICE_NAME "org.tizen.Activationd"
+#define ACTD_DBUS_API_OBJECT_PATH "/org/tizen/activationd"
+
+static int method_generic_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error, const char *method)
+{
+       _cleanup_(sd_bus_error_free) sd_bus_error error_buf = SD_BUS_ERROR_NULL;
+       int r;
+       const char *s;
+       struct unit_control_event_data uc_ev_data = {};
+       struct epc_event *ev;
+       struct timespec ts;
+
+       if (!ret_error)
+               ret_error = &error_buf;
+
+       if (strcmp(method, "start") &&
+                       strcmp(method, "stop") &&
+                       strcmp(method, "restart")) {
+               r = -EINVAL;
+               goto fail;
+       }
+
+       /* Read the parameters */
+       r = sd_bus_message_read(m, "s", &s);
+       if (r < 0) {
+               fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
+               goto fail;
+       }
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+               log_error_errno(errno, "Unable to get timestamp : %m");
+               r = -errno;
+               goto fail;
+       }
+
+       uc_ev_data.event_time = ts.tv_sec;
+
+       uc_ev_data.unit = strdup(s);
+       if (uc_ev_data.unit == NULL) {
+               r = -ENOMEM;
+               goto fail;
+       }
+
+       uc_ev_data.method = strdup(method);
+       if (uc_ev_data.method == NULL) {
+               r = -ENOMEM;
+               goto fail;
+       }
+
+       r = epc_event_create(UC_EVENT_ID, &uc_ev_data, &ev);
+       if (r) {
+               log_error_errno(r, "Unable to allocate an event: %m.");
+               r = -errno;
+               goto fail;
+       }
+
+       r = event_processor_report_event(ev);
+       epc_event_unref(ev);
+       if (r) {
+               log_error_errno(r, "Unable to report event: %m");
+               r = -EINVAL;
+               goto fail;
+       }
+
+       return sd_bus_reply_method_return(m, "s", "ok");
+fail:
+       free(uc_ev_data.method);
+       free(uc_ev_data.unit);
+       return sd_bus_error_set_errno(ret_error, -r);
+}
+
+static int method_start(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
+{
+       return method_generic_callback(m, userdata, ret_error, "start");
+}
+
+static int method_stop(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
+{
+       return method_generic_callback(m, userdata, ret_error, "stop");
+}
+
+static int method_restart(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
+{
+       return method_generic_callback(m, userdata, ret_error, "restart");
+}
+
+static const sd_bus_vtable unit_control_api_vtable[] = {
+        SD_BUS_VTABLE_START(0),
+        SD_BUS_METHOD("Start", "s", "s", method_start, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("Stop", "s", "s", method_stop, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("Restart", "s", "s", method_restart, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_VTABLE_END
+};
+
+
+struct unit_control_api {
+       struct epc_module module;
+       sd_bus *bus;
+};
+
+#define to_unit_control_api(MOD)                                               \
+       container_of(MOD, struct unit_control_api, module)
+
+static void cleanup_listener(struct unit_control_api *l)
+{
+       if (l->bus)
+               sd_bus_release_name(l->bus, ACTD_DBUS_API_INTERFACE_NAME);
+}
+
+static int unit_control_api_init(struct epc_module *module,
+                                                                struct epc_config *config,
+                                                                sd_event* loop)
+{
+       struct unit_control_api *listener = to_unit_control_api(module);
+       int ret = 0;
+       sd_bus_slot *slot = NULL;
+
+       listener->bus = NULL;
+
+       ret = epc_acquire_systemd_bus(&listener->bus);
+       if (ret < 0) {
+               log_error_errno(ret, "Failed to acquire the default system bus connection: %m");
+               return -EINVAL;
+       }
+
+       ret = sd_bus_add_object_vtable(listener->bus,
+                       &slot,
+                       ACTD_DBUS_API_OBJECT_PATH,
+                       ACTD_DBUS_API_INTERFACE_NAME,
+                       unit_control_api_vtable,
+                       listener);
+
+       if (ret < 0) {
+               fprintf(stderr, "Failed to register api calls: %s\n", strerror(-ret));
+               goto cleanup;
+       }
+
+       /* Take a well-known service name so that clients can find us */
+       ret = sd_bus_request_name(listener->bus, ACTD_DBUS_API_SERVICE_NAME, 0);
+       if (ret < 0) {
+               fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-ret));
+               goto cleanup;
+       }
+       return 0;
+
+cleanup:
+       cleanup_listener(listener);
+       return ret;
+}
+
+static void unit_control_api_cleanup(struct epc_module *module)
+{
+       struct unit_control_api *listener = to_unit_control_api(module);
+       cleanup_listener(listener);
+}
+
+struct unit_control_api unit_control_api = {
+       .module = {
+               .name = "unit_control_api",
+               .type = EPC_MODULE_TYPE_LISTENER,
+
+               .init = unit_control_api_init,
+               .cleanup = unit_control_api_cleanup,
+               .node = LIST_HEAD_INIT(unit_control_api.module.node),
+       },
+};
+
+EPC_MODULE_REGISTER(&unit_control_api.module)