Add dbus signal listener 96/201896/25
authorMaciej Slodczyk <m.slodczyk2@partner.samsung.com>
Wed, 20 Mar 2019 15:01:11 +0000 (16:01 +0100)
committerMaciej Slodczyk <m.slodczyk2@partner.samsung.com>
Thu, 4 Apr 2019 13:13:44 +0000 (15:13 +0200)
Change-Id: I72e9c1b2c022bce0969c1f37b9c993ed4232755b
Signed-off-by: Maciej Slodczyk <m.slodczyk2@partner.samsung.com>
Makefile.am
include/dbus_signal_event.h [new file with mode: 0644]
modules.conf.d/dbus_listener.conf.d/50-default.conf [new file with mode: 0644]
packaging/activationd.spec
src/event_types/dbus_signal_event.c [new file with mode: 0644]
src/listeners/dbus.c [new file with mode: 0644]

index 0b2fac0a25e9f373c7e0b8748a83b3546188504b..340c8fc16a2998387a0536eb323d4487e6db363a 100644 (file)
@@ -126,12 +126,15 @@ modules_LTLIBRARIES = \
                  service_restart_action.la \
                  unit_start_action.la \
                  vconf_key_changed_event.la \
+                 dbus_signal_event.la \
                  activation_dm.la \
-                 vconf_listener.la
+                 vconf_listener.la \
+                 dbus_listener.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
 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
@@ -140,6 +143,9 @@ 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
 vconf_listener_configdir = $(modulesconfigdir)/vconf_listener.conf.d
+dbus_listener_la_SOURCES = src/listeners/dbus.c
+dbus_listener_config_DATA = modules.conf.d/dbus_listener.conf.d/50-default.conf
+dbus_listener_configdir = $(modulesconfigdir)/dbus_listener.conf.d
 
 epcd_LDADD = $(LIBSYSTEMD_LIBS) $(GLIB_LIBS) $(JSON_C_LIBS)
 
@@ -215,3 +221,4 @@ modulesconfigdir = $(configdir)/modules.conf.d
 
 install-data-local:
        $(MKDIR_P) $(DESTDIR)${vconf_listener_configdir}
+       $(MKDIR_P) $(DESTDIR)${dbus_listener_configdir}
diff --git a/include/dbus_signal_event.h b/include/dbus_signal_event.h
new file mode 100644 (file)
index 0000000..db9a072
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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 _EPC_DBUS_SIGNAL_EVENT_H
+#define _EPC_DBUS_SIGNAL_EVENT_H
+
+#include <time.h>
+#include "event.h"
+
+#define DBUS_SIGNAL_EVENT_ID "dbus_signal"
+#define DS_EV_TIME "et"
+#define DS_EV_ID "event_id"
+#define DS_EV_SENDER "sender"
+#define DS_EV_INTERFACE "interface"
+#define DS_EV_MEMBER "member"
+#define DS_EV_PATH "path"
+#define DS_EV_PATH_NAMESPACE "path_namespace"
+#define DS_EV_DESTINATION "destination"
+#define DS_EV_PARAMS "params"
+
+struct dbus_signal_event {
+       struct epc_event event;
+       time_t event_time;
+       char *event_id;
+       char *sender;
+       char *interface;
+       char *member;
+       char *path;
+       char *path_namespace;
+       char *destination;
+       struct epc_object *params;
+};
+
+struct dbus_signal_event_data {
+       time_t event_time;
+       char *event_id;
+       char *sender;
+       char *interface;
+       char *member;
+       char *path;
+       char *path_namespace;
+       char *destination;
+       struct epc_object *params;
+};
+
+#define to_dbus_signal_event(EVENT)                                            \
+       container_of(EVENT, struct dbus_signal_event, event)
+
+#endif /* _EPC_DBUS_SIGNAL_EVENT_H */
diff --git a/modules.conf.d/dbus_listener.conf.d/50-default.conf b/modules.conf.d/dbus_listener.conf.d/50-default.conf
new file mode 100644 (file)
index 0000000..16ceefc
--- /dev/null
@@ -0,0 +1,5 @@
+{
+       "signals":[
+               {"id":"example_signal", "interface":"org.freedesktop.ExampleInterface", "member":"ExampleSignal", "path_namespace":"/org/freedesktop"}
+       ]
+}
index 780a5ebdf71e179a0f47b849cf4c0bc48967dc64..ea30d0e442f71215138e0b4117ca8702466842ad 100644 (file)
@@ -8,6 +8,7 @@ Summary:    Event-based activation daemon
 Group:      System/Monitoring
 Requires:   event-processing-core
 Requires:   event-processing-vconf
+Requires:   event-processing-dbus
 Requires:   event-processing-activation-dm
 BuildRequires: pkgconfig(vconf)
 
@@ -44,6 +45,13 @@ Requires:   event-processing-core
 %description -n event-processing-vconf
 This package provides a listener for vconf events
 
+%package -n event-processing-dbus
+Summary:    dbus listener module for epc
+Group:      System/Monitoring
+
+%description -n event-processing-dbus
+This package provides a listener for dbus events
+
 %package -n event-processing-activation-dm
 Summary:    Activation decision maker
 Group:      System/Monitoring
@@ -115,6 +123,8 @@ mkdir -p %{buildroot}/%{_libdir}/epc/service.conf.d
 %install_module unit_start_action extra
 %install_module vconf_key_changed_event vconf
 %install_module vconf_listener vconf
+%install_module dbus_signal_event dbus
+%install_module dbus_listener dbus
 %install_module activation_dm activation-dm
 
 %files
@@ -131,6 +141,9 @@ mkdir -p %{buildroot}/%{_libdir}/epc/service.conf.d
 %files -n event-processing-vconf -f vconf-files
 %{_prefix}/lib/epc/modules.conf.d/vconf_listener.conf.d/50-default.conf
 
+%files -n event-processing-dbus -f dbus-files
+%{_prefix}/lib/epc/modules.conf.d/dbus_listener.conf.d/50-default.conf
+
 %files -n activationd-test-services
 %license COPYING
 %manifest %{name}.manifest
diff --git a/src/event_types/dbus_signal_event.c b/src/event_types/dbus_signal_event.c
new file mode 100644 (file)
index 0000000..8e003b1
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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 "dbus_signal_event.h"
+
+static int allocate_dbus_signal_event(struct epc_event_type *type,
+                                                        void *data, struct epc_event **ev)
+{
+       struct dbus_signal_event *ds_ev;
+       struct dbus_signal_event_data *ds_ev_data = data;
+       int ret;
+
+       ds_ev = calloc(1, sizeof(*ds_ev));
+       if (!ds_ev)
+               return -ENOMEM;
+
+       ret = epc_event_init_internal(type, &ds_ev->event);
+       if (ret)
+               goto cleanup;
+
+       ds_ev->event_time = ds_ev_data->event_time;
+       ds_ev->event_id = ds_ev_data->event_id;
+       ds_ev->sender = ds_ev_data->sender;
+       ds_ev->interface = ds_ev_data->interface;
+       ds_ev->member = ds_ev_data->member;
+       ds_ev->path = ds_ev_data->path;
+       ds_ev->path_namespace = ds_ev_data->path_namespace;
+       ds_ev->destination = ds_ev_data->destination;
+       ds_ev->params = ds_ev_data->params;
+
+       *ev = &ds_ev->event;
+       return 0;
+cleanup:
+       free(ds_ev);
+
+       return ret;
+}
+
+static int deserialize_dbus_signal_event(struct epc_event_type *type,
+                                                               struct epc_object *data, struct epc_event **ev)
+{
+       int ret = -EINVAL;
+       struct dbus_signal_event_data ds_ev_data;
+       struct epc_object *obj;
+       memset(&ds_ev_data, 0, sizeof(ds_ev_data));
+
+       list_for_each_entry(obj, &data->val.children, node) {
+               switch (obj->type) {
+               case TYPE_TIME_T:
+                       if (!strcmp(DS_EV_TIME, obj->key))
+                               ds_ev_data.event_time = obj->val.time;
+                       break;
+               case TYPE_STRING:
+                       if (!strcmp(DS_EV_ID, obj->key))
+                               ds_ev_data.event_id = strdup(obj->val.s);
+                       else if (!strcmp(DS_EV_SENDER, obj->key))
+                               ds_ev_data.sender = strdup(obj->val.s);
+                       else if (!strcmp(DS_EV_INTERFACE, obj->key))
+                               ds_ev_data.interface = strdup(obj->val.s);
+                       else if (!strcmp(DS_EV_MEMBER, obj->key))
+                               ds_ev_data.member = strdup(obj->val.s);
+                       else if (!strcmp(DS_EV_PATH, obj->key))
+                               ds_ev_data.path = strdup(obj->val.s);
+                       else if (!strcmp(DS_EV_PATH_NAMESPACE, obj->key))
+                               ds_ev_data.path_namespace = strdup(obj->val.s);
+                       else if (!strcmp(DS_EV_DESTINATION, obj->key))
+                               ds_ev_data.destination = strdup(obj->val.s);
+                       break;
+               case TYPE_OBJECT:
+                       if (!strcmp(DS_EV_PARAMS, obj->key)) {
+                               epc_object_ref(obj);
+                               ds_ev_data.params = obj;
+                       }
+                       break;
+               }
+       }
+
+       ret = allocate_dbus_signal_event(type, &ds_ev_data, ev);
+       if (ret < 0)
+               goto finish;
+
+       ret = epc_event_deserialize_internal(data, type, *ev);
+       if (ret < 0) {
+               struct dbus_signal_event *ds_ev =
+                       to_dbus_signal_event(*ev);
+               free(ds_ev);
+               goto finish;
+       }
+       ret = 0;
+finish:
+       return ret;
+}
+
+static void dbus_signal_event_release(struct epc_event *ev)
+{
+       struct dbus_signal_event *ds_ev =
+               to_dbus_signal_event(ev);
+
+       free(ds_ev->event_id);
+       free(ds_ev->sender);
+       free(ds_ev->interface);
+       free(ds_ev->member);
+       free(ds_ev->path);
+       free(ds_ev->path_namespace);
+       free(ds_ev->destination);
+       if (ds_ev->params)
+               epc_object_destroy(ds_ev->params);
+       epc_event_cleanup_internal(&ds_ev->event);
+       free(ds_ev);
+}
+
+static void dbus_signal_event_serialize(struct epc_event *ev, struct epc_object *out)
+{
+       struct dbus_signal_event *ds_ev =
+               to_dbus_signal_event(ev);
+       epc_event_serialize_internal(ev, out);
+       epc_object_append_time_t(out, DS_EV_TIME, ds_ev->event_time);
+       epc_object_append_string(out, DS_EV_ID, ds_ev->event_id);
+       if (ds_ev->sender)
+               epc_object_append_string(out, DS_EV_SENDER, ds_ev->sender);
+       if (ds_ev->interface)
+               epc_object_append_string(out, DS_EV_INTERFACE, ds_ev->interface);
+       if (ds_ev->member)
+               epc_object_append_string(out, DS_EV_MEMBER, ds_ev->member);
+       if (ds_ev->path)
+               epc_object_append_string(out, DS_EV_PATH, ds_ev->path);
+       if (ds_ev->path_namespace)
+               epc_object_append_string(out, DS_EV_PATH_NAMESPACE, ds_ev->path_namespace);
+       if (ds_ev->destination)
+               epc_object_append_string(out, DS_EV_DESTINATION, ds_ev->destination);
+       if (ds_ev->params)
+               epc_object_append_object(out, DS_EV_PARAMS, ds_ev->params);
+}
+
+static struct epc_event_type dbus_signal_event_type = {
+       .name = DBUS_SIGNAL_EVENT_ID,
+       .default_ops = {
+               .release = dbus_signal_event_release,
+               .serialize = dbus_signal_event_serialize,
+       },
+       .allocate_event = allocate_dbus_signal_event,
+       .deserialize_event = deserialize_dbus_signal_event,
+       .node = LIST_HEAD_INIT(dbus_signal_event_type.node),
+};
+
+EPC_EVENT_TYPE_REGISTER(dbus_signal_event_type, dbus_signal_et)
diff --git a/src/listeners/dbus.c b/src/listeners/dbus.c
new file mode 100644 (file)
index 0000000..159b8f1
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * 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 "list.h"
+#include "json-config.h"
+#include "event_processor.h"
+#include "systemd_dbus.h"
+#include "dbus_signal_event.h"
+
+struct dbus_signal {
+       char *event_id;
+       char *sender;
+       char *interface;
+       char *member;
+       char *path;
+       char *path_namespace;
+       char *destination;
+       struct list_head node;
+};
+
+struct dbus_listener {
+       struct epc_module module;
+       struct list_head signals;
+};
+
+#define to_dbus_listener(MOD)                                          \
+       container_of(MOD, struct dbus_listener, module)
+
+static int append_param(struct epc_object *params, char type, int pos, const void *val)
+{
+       int r;
+       char key[64];
+
+       memset (key, 0, sizeof key);
+
+       r = snprintf(key, sizeof key, "arg%d", pos);
+       if (r < 0)
+               return r;
+
+       switch (type) {
+       case SD_BUS_TYPE_STRING:
+               r = epc_object_append_string(params, key, (char *)val);
+               break;
+       case SD_BUS_TYPE_BOOLEAN:
+               r = epc_object_append_bool(params, key, *(bool *)val);
+               break;
+       case SD_BUS_TYPE_DOUBLE:
+               r = epc_object_append_double(params, key, *(double *)val);
+               break;
+       case SD_BUS_TYPE_UINT64:
+       case SD_BUS_TYPE_INT64:
+       case SD_BUS_TYPE_UINT32:
+       case SD_BUS_TYPE_INT32:
+               r = epc_object_append_int(params, key, *(int *)val);
+               break;
+       default:
+               r = 0;
+               break;
+       }
+       if (r < 0)
+               return r;
+       return 0;
+}
+
+static int parse_message_args(sd_bus_message *m, struct epc_object *params)
+{
+       int r;
+       char type;
+       const char *contents;
+       int pos = 0;
+
+       while (1) {
+               r = sd_bus_message_peek_type(m, &type, &contents);
+               if (r == 0) {
+                       break;
+               } else if (r < 0) {
+                       log_error("sd_bus_message_peek_type: %s\n", strerror(-r));
+                       return r;
+               }
+               pos++;
+
+               switch (type) {
+               case SD_BUS_TYPE_STRING: {
+                       const char *v;
+
+                       r = sd_bus_message_read_basic(m, type, &v);
+                       if (r < 0)
+                               return r;
+
+                       r = append_param(params, type, pos, v);
+                       if (r < 0)
+                               return r;
+                       break;
+               }
+               case SD_BUS_TYPE_BOOLEAN:
+               case SD_BUS_TYPE_UINT64:
+               case SD_BUS_TYPE_INT64:
+               case SD_BUS_TYPE_UINT32:
+               case SD_BUS_TYPE_INT32:
+               case SD_BUS_TYPE_DOUBLE: {
+                       union {
+                               int b;
+                               uint64_t u64;
+                               int64_t i64;
+                               uint32_t u32;
+                               int32_t i32;
+                               double d;
+                       } v;
+
+                       r = sd_bus_message_read_basic(m, type, &v);
+                       if (r < 0)
+                               return r;
+
+                       r = append_param(params, type, pos, &v);
+                       if (r < 0)
+                               return r;
+                       break;
+               }
+               default: {
+                       r = sd_bus_message_read_basic(m, type, NULL);
+                       if (r < 0)
+                               return r;
+                       break;
+               }
+               }
+
+       }
+       return 0;
+}
+
+
+static int on_dbus_signal_match(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+       struct dbus_signal *s = (struct dbus_signal *)userdata;
+
+       struct dbus_signal_event_data ds_ev_data = {};
+       struct epc_event *ev;
+       struct timespec ts;
+       int ret = 0;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+               log_error_errno(errno, "Unable to get timestamp : %m");
+               return -1;
+       }
+
+       ds_ev_data.event_time = ts.tv_sec;
+
+#define COPY_COND(x) do { \
+               if (s->x) { \
+                       ds_ev_data.x = strdup(s->x); \
+                       if (ds_ev_data.x == NULL) \
+                               goto finish; \
+               } \
+       } while (0)
+
+       COPY_COND(event_id);
+       COPY_COND(sender);
+       COPY_COND(interface);
+       COPY_COND(member);
+       COPY_COND(path);
+       COPY_COND(path_namespace);
+       COPY_COND(destination);
+
+#undef COPY_COND
+
+       if (epc_object_new(&ds_ev_data.params) < 0)
+               goto finish;
+
+       ret = parse_message_args(m, ds_ev_data.params);
+       if (ret) {
+               log_error_errno(ret, "Unable to parse signal parameters: %m.");
+               goto finish;
+       }
+
+       ret = epc_event_create(DBUS_SIGNAL_EVENT_ID, &ds_ev_data, &ev);
+       if (ret) {
+               log_error_errno(ret, "Unable to allocate an event: %m.");
+               goto finish;
+       }
+
+       ret = event_processor_report_event(ev);
+       epc_event_unref(ev);
+       if (ret)
+               log_error_errno(ret, "Unable to report event: %m");
+       return 0;
+finish:
+       free(ds_ev_data.event_id);
+       free(ds_ev_data.sender);
+       free(ds_ev_data.interface);
+       free(ds_ev_data.member);
+       free(ds_ev_data.path);
+       free(ds_ev_data.path_namespace);
+       free(ds_ev_data.destination);
+       if (ds_ev_data.params)
+               epc_object_destroy(ds_ev_data.params);
+
+       return -1;
+}
+
+static void signal_cleanup(struct dbus_signal *s)
+{
+       free(s->event_id);
+       free(s->sender);
+       free(s->interface);
+       free(s->member);
+       free(s->path);
+       free(s->path_namespace);
+       free(s->destination);
+       free(s);
+}
+
+
+static void cleanup(struct dbus_listener *l)
+{
+       struct dbus_signal *s, *next;
+
+       list_for_each_entry_safe(s, next, &l->signals, node) {
+               list_del(&s->node);
+               signal_cleanup(s);
+       }
+}
+
+static int add_signal(struct dbus_listener *l, json_object *root, sd_bus *bus)
+{
+       int ret;
+       struct dbus_signal *s;
+       char match_buf[1024];
+       int size;
+       char err[512];
+
+       s = calloc(1, sizeof(*s));
+       if (!s) {
+               log_error("Could not allocate dbus_signal object");
+               return -ENOMEM;
+       }
+
+       ret = get_config_field(root, "id", &s->event_id, json_type_string);
+       if (ret < 0)
+               goto out;
+
+       log_debug("Adding signal: %s", s->event_id);
+
+       (void) get_config_field(root, "sender", &s->sender, json_type_string);
+
+       ret = get_config_field(root, "interface", &s->interface, json_type_string);
+       if (ret < 0)
+               goto out;
+
+       ret = get_config_field(root, "member", &s->member, json_type_string);
+       if (ret < 0)
+               goto out;
+
+       (void) get_config_field(root, "path", &s->path, json_type_string);
+       (void) get_config_field(root, "path_namespace", &s->path_namespace, json_type_string);
+
+       if (!s->path && !s->path_namespace)
+               goto out;
+
+       (void) get_config_field(root, "destination", &s->destination, json_type_string);
+
+       memset(match_buf, 0, sizeof match_buf);
+       size = 0;
+
+       ret = snprintf(match_buf+size, sizeof match_buf-size, "type='signal',");
+       if (ret < 0)
+               goto out;
+       size += ret;
+
+#define APPEND_MATCH(x) do { \
+               ret = snprintf(match_buf+size, sizeof match_buf-size, #x "='%s',", s->x); \
+               if (ret < 0) \
+                       goto out; \
+               size += ret; \
+       } while (0)
+
+       if (s->sender)
+               APPEND_MATCH(sender);
+       APPEND_MATCH(interface);
+       APPEND_MATCH(member);
+
+       if (s->path)
+               APPEND_MATCH(path);
+       else if (s->path_namespace)
+               APPEND_MATCH(path_namespace);
+
+       if (s->destination)
+               APPEND_MATCH(destination);
+
+#undef APPEND_MATCH
+
+       match_buf[size-1] = '\0';
+
+       ret = sd_bus_add_match(bus, NULL, match_buf, on_dbus_signal_match, s);
+       if (ret < 0) {
+               if (strerror_r(-ret, err, sizeof err))
+                       log_error("Failed to add signal match: %s\n", err);
+               goto out;
+       }
+
+       list_add_tail(&s->node, &l->signals);
+       return 0;
+
+out:
+       signal_cleanup(s);
+       return ret;
+}
+
+
+static int dbus_listener_init(struct epc_module *module,
+                                                                struct epc_config *config,
+                                                                sd_event* loop)
+{
+       struct dbus_listener *listener = to_dbus_listener(module);
+       int ret = 0, len, i;
+       json_object *arr, *obj;
+       sd_bus *bus = NULL;
+       char err[512];
+
+       INIT_LIST_HEAD(&listener->signals);
+
+       if (config == NULL)
+               return 0;
+
+       ret = epc_acquire_systemd_bus(&bus);
+       if (ret < 0) {
+               log_error_errno(ret, "Failed to acquire the default system bus connection: %m");
+               return -EINVAL;
+       }
+
+       if (!json_object_object_get_ex(config->root, "signals", &arr)) {
+               log_error("There is no 'signals' array");
+               return -EINVAL;
+       }
+
+       if (!json_object_is_type(arr, json_type_array)) {
+               log_error("Config value is not an array");
+               return -EINVAL;
+       }
+
+       len = json_object_array_length(arr);
+       for (i = 0; i < len; ++i) {
+               obj = json_object_array_get_idx(arr, i);
+               ret = add_signal(listener, obj, bus);
+               if (ret < 0) {
+                       log_error("Could not add signal");
+                       goto cleanup;
+               }
+       }
+
+       ret = sd_bus_attach_event(bus, loop, 0);
+       if (ret < 0) {
+               if (strerror_r(-ret, err, sizeof err))
+                       log_error("Failed to attach bus to event loop: %s\n", err);
+               goto cleanup;
+       }
+
+       return 0;
+
+cleanup:
+       cleanup(listener);
+       return ret;
+}
+
+static void dbus_listener_cleanup(struct epc_module *module)
+{
+       struct dbus_listener *listener = to_dbus_listener(module);
+       cleanup(listener);
+}
+
+struct dbus_listener dbus_listener = {
+       .module = {
+               .name = "dbus_listener",
+               .type = EPC_MODULE_TYPE_LISTENER,
+
+               .init = dbus_listener_init,
+               .cleanup = dbus_listener_cleanup,
+               .node = LIST_HEAD_INIT(dbus_listener.module.node),
+       },
+};
+
+EPC_MODULE_REGISTER(&dbus_listener.module)