Blob Blame History Raw
commit 342dfbeb8275f5ea6ed52dd3f30126614ec1d037
Author: Ingo Franzki <ifranzki@linux.ibm.com>
Date:   Mon Feb 15 14:33:07 2021 +0100

    Event support: pkcsslotd changes
    
    Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>

diff --git a/configure.ac b/configure.ac
index e0ae4a82..a0b098e1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -234,6 +234,12 @@ AC_ARG_WITH([systemd],
 	AS_HELP_STRING([--with-systemd@<:@=DIR@:>@],[systemd system unit files location]),
 	[],
 	[with_systemd=no])
+	
+dnl --- libudev development files
+AC_ARG_WITH([libudev],
+	AS_HELP_STRING([--with-libudev@<:@=DIR@:>@],[libudev development files location]),
+	[],
+	[with_libudev=check])
 
 dnl ---
 dnl ---
@@ -438,6 +444,46 @@ fi
 AC_SUBST([XCRYPTOLINZ_CFLAGS])
 AC_SUBST([XCRYPTOLINZ_LIBS])
 
+dnl --- with_libudev
+LIBUDEV_CFLAGS=
+LIBUDEV_LIBS=
+if test "x$with_libudev" != "xno"; then
+	if test "x$with_libudev" != "xyes" -a "x$with_libudev" != "xcheck"; then
+		LIBUDEV_CFLAGS="-I$with_libudev"
+		LIBUDEV_LIBS="-L$with_libudev"
+	fi
+	old_cflags="$CFLAGS"
+	old_libs="$LIBS"
+	CFLAGS="$CFLAGS $LIBUDEV_CFLAGS"
+	LIBS="$LIBS $LIBUDEV_LIBS"
+	# Use libudev only on s390 platforms, only s390 emits AP related uevents
+	case $target in
+	     *s390x* | *s390*)
+		CFLAGS="$CFLAGS -DWITH_LIBUDEV"
+		;;
+	     *)
+		if test "x$with_libudev" != "xyes"; then
+			with_libudev=no
+			echo "Default to 'with_libudev=no' on non-s390 platforms"
+		fi
+		;;
+	esac
+	if test "x$with_libudev" != "xno"; then
+		AC_CHECK_HEADER([libudev.h], [with_libudev=yes], [
+			AC_MSG_ERROR([Build with libudev requested but libudev headers couldn't be found])
+		])
+		AC_CHECK_LIB([udev], [udev_monitor_new_from_netlink], [with_libudev=yes], [
+			AC_MSG_ERROR([Build with libudev requested but libudev libraries couldn't be found])
+		])
+	fi
+	if test "x$with_libudev" = "xno"; then
+		CFLAGS="$old_cflags"
+		LIBS="$old_libs"
+	fi
+fi
+AC_SUBST([LIBUDEV_CFLAGS])
+AC_SUBST([LIBUDEV_LIBS])
+AM_CONDITIONAL([HAVE_LIBUDEV], [test "x$with_libudev" = "xyes"])
 
 dnl ---
 dnl --- Now check enabled features, while making sure every required
@@ -649,6 +695,7 @@ echo "	Daemon build:		$enable_daemon"
 echo "	Library build:		$enable_library"
 echo "	Systemd service:	$enable_systemd"
 echo "	Build with locks:	$enable_locks"
+echo "	Build with libudev:	$with_libudev"
 echo "	Build p11sak tool:	$enable_p11sak"
 echo "	token migrate tool:	$enable_pkcstok_migrate"
 echo
diff --git a/usr/include/slotmgr.h b/usr/include/slotmgr.h
index 4d038435..e37368a5 100644
--- a/usr/include/slotmgr.h
+++ b/usr/include/slotmgr.h
@@ -31,6 +31,7 @@
 #define OCK_API_LOCK_FILE LOCKDIR_PATH "/LCK..APIlock"
 
 #define PROC_SOCKET_FILE_PATH "/var/run/pkcsslotd.socket"
+#define ADMIN_SOCKET_FILE_PATH "/var/run/pkcsslotd.admin.socket"
 
 #define PID_FILE_PATH "/var/run/pkcsslotd.pid"
 #define OCK_CONFIG OCK_CONFDIR "/opencryptoki.conf"
@@ -45,6 +46,7 @@
 
 #define NUMBER_SLOTS_MANAGED 1024
 #define NUMBER_PROCESSES_ALLOWED  1000
+#define NUMBER_ADMINS_ALLOWED     1000
 
 //
 // Per Process Data structure
diff --git a/usr/sbin/pkcsslotd/pkcsslotd.h b/usr/sbin/pkcsslotd/pkcsslotd.h
index 69eb59f3..d7edcb3c 100644
--- a/usr/sbin/pkcsslotd/pkcsslotd.h
+++ b/usr/sbin/pkcsslotd/pkcsslotd.h
@@ -92,5 +92,8 @@ int init_socket_server();
 int term_socket_server();
 int init_socket_data(Slot_Mgr_Socket_t *sp);
 int socket_connection_handler(int timeout_secs);
+#ifdef DEV
+void dump_socket_handler();
+#endif
 
 #endif                          /* _SLOTMGR_H */
diff --git a/usr/sbin/pkcsslotd/pkcsslotd.mk b/usr/sbin/pkcsslotd/pkcsslotd.mk
index 2d36b4a9..c574edf8 100644
--- a/usr/sbin/pkcsslotd/pkcsslotd.mk
+++ b/usr/sbin/pkcsslotd/pkcsslotd.mk
@@ -8,6 +8,9 @@ CLEANFILES += usr/lib/common/parser.c usr/lib/common/parser.h	\
 	usr/lib/common/parser.output usr/lib/common/lexer.c
 
 usr_sbin_pkcsslotd_pkcsslotd_LDFLAGS = -lpthread -lcrypto
+if HAVE_LIBUDEV
+usr_sbin_pkcsslotd_pkcsslotd_LDFLAGS += -ludev
+endif
 
 usr_sbin_pkcsslotd_pkcsslotd_CFLAGS = -DPROGRAM_NAME=\"$(@)\"	\
 	-I${srcdir}/usr/include -I${srcdir}/usr/lib/common	\
diff --git a/usr/sbin/pkcsslotd/signal.c b/usr/sbin/pkcsslotd/signal.c
index 49482a2f..17167632 100644
--- a/usr/sbin/pkcsslotd/signal.c
+++ b/usr/sbin/pkcsslotd/signal.c
@@ -21,7 +21,7 @@
 extern BOOL IsValidProcessEntry(pid_t_64 pid, time_t_64 RegTime);
 
 static int SigsToIntercept[] = {
-    SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM,
+    SIGHUP, SIGINT, SIGQUIT, SIGALRM,
     SIGTERM, SIGTSTP, SIGTTIN,
     SIGTTOU, SIGUSR1, SIGUSR2, SIGPROF
 };
@@ -32,8 +32,11 @@ static int SigsToIntercept[] = {
 /* SIGCHLD - Don't want to exit.  Should never receive, but we do, apparently
  * when something tries to cancel the GC Thread */
 
+/* SIGPIPE - Don't want to exit.  May happen when a connection to an admin
+ * event sender or a process is closed before all events are delivered. */
+
 static int SigsToIgnore[] = {
-    SIGCHLD,
+    SIGCHLD, SIGPIPE,
 };
 
 
@@ -71,6 +74,10 @@ void slotdGenericSignalHandler(int Signal)
     CheckForGarbage(shmp);
 #endif
 
+#ifdef DEV
+    dump_socket_handler();
+#endif
+
     for (procindex = 0; (procindex < NUMBER_PROCESSES_ALLOWED); procindex++) {
 
         Slot_Mgr_Proc_t_64 *pProc = &(shmp->proc_table[procindex]);
diff --git a/usr/sbin/pkcsslotd/socket_server.c b/usr/sbin/pkcsslotd/socket_server.c
index 1fae0b95..41408670 100644
--- a/usr/sbin/pkcsslotd/socket_server.c
+++ b/usr/sbin/pkcsslotd/socket_server.c
@@ -1,4 +1,6 @@
 /*
+ * COPYRIGHT (c) International Business Machines Corp. 2013, 2021
+ *
  * This program is provided under the terms of the Common Public License,
  * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
  * software constitutes recipient's acceptance of CPL-1.0 terms which can be
@@ -12,6 +14,8 @@
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
 
 #include <sys/time.h>
 #include <sys/socket.h>
@@ -19,32 +23,1225 @@
 #include <sys/select.h>
 #include <sys/stat.h>
 #include <grp.h>
+#include <sys/epoll.h>
+
+#if defined(__GNUC__) && __GNUC__ >= 7 || defined(__clang__) && __clang_major__ >= 12
+    #define FALL_THROUGH __attribute__ ((fallthrough))
+#else
+    #define FALL_THROUGH ((void)0)
+#endif
+
+#ifdef WITH_LIBUDEV
+#include <libudev.h>
+#endif
 
 #include "log.h"
 #include "slotmgr.h"
 #include "pkcsslotd.h"
 #include "apictl.h"
+#include "dlist.h"
+#include "events.h"
+
+#define MAX_EPOLL_EVENTS            128
+
+#ifdef WITH_LIBUDEV
+#define UDEV_RECV_BUFFFER_SIZE      512 * 1024
+#define UDEV_SUBSYSTEM_AP           "ap"
+#define UDEV_ACTION_BIND            "bind"
+#define UDEV_ACTION_UNBIND          "unbind"
+#define UDEV_ACTION_DEVTYPE_APQN    "ap_queue"
+#define UDEV_PROERTY_DEVTYPE        "DEV_TYPE"
+#endif
+
+struct epoll_info {
+    int (* notify)(int events, void *private);
+    void (* free)(void *private);
+    void *private;
+    unsigned long ref_count;
+};
+
+struct listener_info {
+    int socket;
+    const char *file_path;
+    int (* new_conn)(int socket, struct listener_info *listener);
+    struct epoll_info ep_info;
+    unsigned long num_clients;
+    unsigned long max_num_clients;
+};
+
+enum xfer_state {
+    XFER_IDLE = 0,
+    XFER_RECEIVE = 1,
+    XFER_SEND = 2,
+};
+
+struct client_info {
+    int socket;
+    int (* xfer_complete)(void *client);
+    void (* hangup)(void *client);
+    void (* free)(void *client);
+    void *client;
+    struct epoll_info ep_info;
+    enum xfer_state xfer_state;
+    char *xfer_buffer;
+    size_t xfer_size;
+    size_t xfer_offset;
+};
+
+enum proc_state {
+    PROC_INITIAL_SEND = 0,
+    PROC_WAIT_FOR_EVENT = 1,
+    PROC_SEND_EVENT = 2,
+    PROC_SEND_PAYLOAD = 3,
+    PROC_RECEIVE_REPLY = 4,
+    PROC_HANGUP = 5,
+};
+
+struct proc_conn_info {
+    struct client_info client_info;
+    enum proc_state state;
+    DL_NODE *events;
+    struct event_info *event;
+    event_reply_t reply;
+};
+
+enum admin_state {
+    ADMIN_RECEIVE_EVENT = 0,
+    ADMIN_RECEIVE_PAYLOAD = 1,
+    ADMIN_EVENT_DELIVERED = 2,
+    ADMIN_SEND_REPLY = 3,
+    ADMIN_WAIT_FOR_EVENT_LIMIT = 4,
+    ADMIN_HANGUP = 5,
+};
+
+struct admin_conn_info {
+    struct client_info client_info;
+    enum admin_state state;
+    struct event_info *event;
+};
+
+#ifdef WITH_LIBUDEV
+struct udev_mon {
+    struct udev *udev;
+    struct udev_monitor *mon;
+    int socket;
+    struct epoll_info ep_info;
+    struct event_info *delayed_event;
+};
+#endif
+
+struct event_info {
+    event_msg_t event;
+    char *payload;
+    event_reply_t reply;
+    unsigned long proc_ref_count;      /* # of processes using this event */
+    struct admin_conn_info *admin_ref; /* Admin connection to send reply back */
+};
+
+static int epoll_fd = -1;
+static struct listener_info proc_listener;
+static DL_NODE *proc_connections = NULL;
+static struct listener_info admin_listener;
+static DL_NODE *admin_connections = NULL;
+#ifdef WITH_LIBUDEV
+static struct udev_mon udev_mon;
+#endif
+static DL_NODE *pending_events = NULL;
+static unsigned long pending_events_count = 0;
+
+#define MAX_PENDING_EVENTS      1024
+
+/*
+ * Iterate over all connections in a safe way. Before actually iterating,
+ * increment the ref count of ALL connections, because any processing may
+ * cause any of the connections to be hang-up, and thus freed and removed
+ * from the list. We need to make sure that while we are iterating over the
+ * connections, none of them gets removed from the list.
+ */
+#define FOR_EACH_CONN_SAFE_BEGIN(list, conn) {                              \
+        DL_NODE *_node, *_next;                                             \
+        _node = dlist_get_first(list);                                      \
+        while (_node != NULL) {                                             \
+            conn = _node->data;                                             \
+            _next = dlist_next(_node);                                      \
+            client_socket_get(&(conn)->client_info);                        \
+            _node = _next;                                                  \
+        }                                                                   \
+        _node = dlist_get_first(list);                                      \
+        while (_node != NULL) {                                             \
+            conn = _node->data;                                             \
+            _next = dlist_next(_node);
+
+#define FOR_EACH_CONN_SAFE_END(list, conn)                                  \
+            _node = _next;                                                  \
+        }                                                                   \
+        _node = dlist_get_first(list);                                      \
+        while (_node != NULL) {                                             \
+            conn = _node->data;                                             \
+            _next = dlist_next(_node);                                      \
+            client_socket_put(&(conn)->client_info);                        \
+            _node = _next;                                                  \
+        }                                                                   \
+    }
+
+
+
+static void listener_socket_close(int socketfd, const char *file_path);
+static int listener_client_hangup(struct listener_info *listener);
+static void event_delivered(struct event_info *event);
+static int client_socket_notify(int events, void *private);
+static void client_socket_free(void *private);
+static int proc_xfer_complete(void *client);
+static int proc_start_deliver_event(struct proc_conn_info *conn);
+static int proc_deliver_event(struct proc_conn_info *conn,
+                              struct event_info *event);
+static int proc_event_delivered(struct proc_conn_info *conn,
+                                struct event_info *event);
+static inline void proc_get(struct proc_conn_info *conn);
+static inline void proc_put(struct proc_conn_info *conn);
+static void proc_hangup(void *client);
+static void proc_free(void *client);
+static int admin_xfer_complete(void *client);
+static void admin_event_limit_underrun(struct admin_conn_info *conn);
+static int admin_event_delivered(struct admin_conn_info *conn,
+                                 struct event_info *event);
+static inline void admin_get(struct admin_conn_info *conn);
+static inline void admin_put(struct admin_conn_info *conn);
+static void admin_hangup(void *client);
+static void admin_free(void *client);
+#ifdef WITH_LIBUDEV
+static void udev_mon_term(struct udev_mon *udev_mon);
+static int udev_mon_notify(int events, void *private);
+#endif
+
+static void epoll_info_init(struct epoll_info *epoll_info,
+                    int (* notify)(int events, void *private),
+                    void (* free_cb)(void *private),
+                    void *private)
+{
+    epoll_info->ref_count = 1;
+    epoll_info->notify = notify;
+    epoll_info->free = free_cb;
+    epoll_info->private = private;
+}
+
+static void epoll_info_get(struct epoll_info *epoll_info)
+{
+    epoll_info->ref_count++;
+
+    DbgLog(DL3, "%s: private: %p, ref_count: %lu", __func__,
+           epoll_info->private, epoll_info->ref_count);
+}
+
+static void epoll_info_put(struct epoll_info *epoll_info)
+{
+    if (epoll_info->ref_count > 0)
+        epoll_info->ref_count--;
+
+    DbgLog(DL3, "%s: private: %p, ref_count: %lu", __func__,
+           epoll_info->private, epoll_info->ref_count);
+
+    if (epoll_info->ref_count == 0 && epoll_info->free != NULL)
+        epoll_info->free(epoll_info->private);
+}
+
+static int client_socket_init(int socket, int (* xfer_complete)(void *client),
+                              void (* hangup)(void *client),
+                              void (* free_cb)(void *client), void *client,
+                              struct client_info *client_info)
+{
+    struct epoll_event evt;
+    int rc, err;
+
+    if (xfer_complete == NULL || hangup == NULL)
+        return -EINVAL;
+
+    epoll_info_init(&client_info->ep_info, client_socket_notify,
+                    client_socket_free, client_info);
+    client_info->socket = socket;
+    client_info->xfer_complete = xfer_complete;
+    client_info->hangup = hangup;
+    client_info->free = free_cb;
+    client_info->client = client;
+    client_info->xfer_state = XFER_IDLE;
+
+    rc = fcntl(socket, F_SETFL, O_NONBLOCK);
+    if (rc < 0) {
+        err = errno;
+        InfoLog("%s: Failed to set client socket %d to non-blocking, errno "
+                "%d (%s).", __func__, socket, err, strerror(err));
+        return -err;
+    }
+
+    evt.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLERR | EPOLLET;
+    evt.data.ptr = &client_info->ep_info;
+    rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket, &evt);
+    if (rc != 0) {
+        err = errno;
+        InfoLog("%s: Failed to add client socket %d to epoll, errno %d (%s).",
+                 __func__, socket, err, strerror(err));
+        close(socket);
+        return -err;
+    }
+
+    return 0;
+}
+
+static inline void client_socket_get(struct client_info *client_info)
+{
+    epoll_info_get(&client_info->ep_info);
+}
+
+static inline void client_socket_put(struct client_info *client_info)
+{
+    epoll_info_put(&client_info->ep_info);
+}
+
+static void client_socket_term(struct client_info *client_info)
+{
+    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_info->socket, NULL);
+    close(client_info->socket);
+    client_info->socket = -1;
+}
+
+static int client_socket_notify(int events, void *private)
+{
+    struct client_info *client_info = private;
+    ssize_t num;
+    int rc, err, socket = client_info->socket;
+
+    DbgLog(DL3, "%s: Epoll event on client %p socket %d: events: 0x%x xfer: %d",
+           __func__, client_info, socket, events, client_info->xfer_state);
+
+    if (socket < 0)
+        return -ENOTCONN;
+
+    if (events & (EPOLLHUP | EPOLLERR)) {
+        DbgLog(DL3, "EPOLLHUP | EPOLLERR");
+
+        client_info->hangup(client_info->client);
+        client_info = NULL; /* client_info may have been freed by now */
+        return 0;
+    }
+
+    if (client_info->xfer_state == XFER_RECEIVE && (events & EPOLLIN)) {
+        DbgLog(DL3, "%s: EPOLLIN: buffer: %p size: %lu ofs: %lu", __func__,
+               client_info->xfer_buffer, client_info->xfer_size,
+               client_info->xfer_offset);
+
+        num = read(client_info->socket,
+                   client_info->xfer_buffer + client_info->xfer_offset,
+                   client_info->xfer_size - client_info->xfer_offset);
+        if (num <= 0) {
+            err = errno;
+
+            DbgLog(DL3, "%s: read failed with: num: %d errno: %d (%s)",
+                   __func__, num, num < 0 ? err : 0,
+                   num < 0 ? strerror(err) : "none");
+
+            if (num < 0 && err == EWOULDBLOCK)
+                return 0; /* Will be continued when socket becomes readable */
+
+            /* assume connection closed by peer */
+            client_info->hangup(client_info->client);
+            client_info = NULL; /* client_info may have been freed by now */
+            return 0;
+        } else {
+            DbgLog(DL3, "%s: %lu bytes received", __func__, num);
+
+            client_info->xfer_offset += num;
+
+            DbgLog(DL3, "%s: %lu bytes left", __func__,
+                   client_info->xfer_size - client_info->xfer_offset);
+
+            if (client_info->xfer_offset >= client_info->xfer_size) {
+                client_info->xfer_state = XFER_IDLE;
+                client_info->xfer_buffer = NULL;
+                client_info->xfer_size = 0;
+                client_info->xfer_offset = 0;
+
+                client_socket_get(client_info);
+                rc = client_info->xfer_complete(client_info->client);
+                if (rc != 0) {
+                    InfoLog("%s: xfer_complete callback failed for client "
+                            "socket %d, rc: %d", __func__, socket,
+                            rc);
+                    client_info->hangup(client_info->client);
+                }
+                client_socket_put(client_info);
+                client_info = NULL; /* client_info may have been freed by now */
+                return rc;
+            }
+            return 0;
+        }
+    }
+
+    if (client_info->xfer_state == XFER_SEND && (events & EPOLLOUT)) {
+        DbgLog(DL3, "%s: EPOLLOUT: buffer: %p size: %lu ofs: %lu", __func__,
+               client_info->xfer_buffer, client_info->xfer_size,
+               client_info->xfer_offset);
+
+        num = write(client_info->socket,
+                    client_info->xfer_buffer + client_info->xfer_offset,
+                    client_info->xfer_size - client_info->xfer_offset);
+        if (num < 0) {
+            err = errno;
+
+            DbgLog(DL3, "%s: write failed with: errno: %d (%s)", __func__, err,
+                   strerror(err));
+
+            if (err == EWOULDBLOCK)
+                return 0; /* Will be continued when socket becomes writable */
+
+            /* assume connection closed by peer */
+            client_info->hangup(client_info->client);
+            client_info = NULL; /* client_info may have been freed by now */
+            return 0;
+        } else {
+            DbgLog(DL3, "%s: %lu bytes sent", __func__, num);
+
+            client_info->xfer_offset += num;
+
+            DbgLog(DL3, "%s: %lu bytes left", __func__,
+                   client_info->xfer_size - client_info->xfer_offset);
+
+            if (client_info->xfer_offset >= client_info->xfer_size) {
+                client_info->xfer_state = XFER_IDLE;
+                client_info->xfer_buffer = NULL;
+                client_info->xfer_size = 0;
+                client_info->xfer_offset = 0;
+
+                client_socket_get(client_info);
+                rc = client_info->xfer_complete(client_info->client);
+                if (rc != 0) {
+                    InfoLog("%s: xfer_complete callback failed for client "
+                            "socket %d, rc: %d", __func__, socket,
+                            rc);
+                    client_info->hangup(client_info->client);
+                }
+                client_socket_put(client_info);
+                client_info = NULL; /* client_info may have been freed by now */
+                return rc;
+            }
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+static void client_socket_free(void *private)
+{
+    struct client_info *client_info = private;
+
+    DbgLog(DL3, "%s: %p", __func__, client_info);
+
+    if (client_info->free != NULL)
+        client_info->free(client_info->client);
+}
+
+static int client_socket_receive(struct client_info *client_info,
+                                 void *buffer, size_t size)
+{
+    if (client_info->socket < 0)
+        return -ENOTCONN;
+
+    client_info->xfer_state = XFER_RECEIVE;
+    client_info->xfer_buffer = (char *)buffer;
+    client_info->xfer_size = size;
+    client_info->xfer_offset = 0;
+
+    DbgLog(DL3, "%s: Start receive on client socket %d: buffer: %p size: %lu",
+            __func__, client_info->socket, buffer, size);
+
+    return client_socket_notify(EPOLLIN, client_info);
+}
+
+
+static int client_socket_send(struct client_info *client_info,
+                              void *buffer, size_t size)
+{
+    if (client_info->socket < 0)
+        return -ENOTCONN;
+
+    client_info->xfer_state = XFER_SEND;
+    client_info->xfer_buffer = (char *)buffer;
+    client_info->xfer_size = size;
+    client_info->xfer_offset = 0;
+
+    DbgLog(DL3, "%s: Start send on client socket %d: buffer: %p size: %lu",
+            __func__, client_info->socket, buffer, size);
+
+    return client_socket_notify(EPOLLOUT, client_info);
+}
+
+static struct event_info *event_new(unsigned int payload_len,
+                                    struct admin_conn_info *admin_conn)
+{
+    struct event_info *event;
+
+    event = calloc(1, sizeof(struct event_info));
+    if (event == NULL) {
+        ErrLog("%s: Failed to allocate the event", __func__);
+        return NULL;
+    }
+
+    event->event.version = EVENT_VERSION_1;
+    event->event.payload_len = payload_len;
+    if (payload_len > 0) {
+        event->payload = malloc(payload_len);
+        if (event->payload == NULL) {
+            ErrLog("%s: Failed to allocate the event payload", __func__);
+            free(event);
+            return NULL;
+        }
+    }
+
+    event->reply.version = EVENT_VERSION_1;
+
+    if (admin_conn != NULL)
+        admin_get(admin_conn);
+    event->admin_ref = admin_conn;
+
+    DbgLog(DL3, "%s: allocated event: %p", __func__, event);
+    return event;
+}
+
+static void event_limit_underrun()
+{
+    struct admin_conn_info *conn;
+
+    DbgLog(DL3, "%s: pending_events_count: %lu", __func__, pending_events_count);
+
+#ifdef WITH_LIBUDEV
+    /* Notify the udev monitor */
+    udev_mon_notify(EPOLLIN, &udev_mon);
+#endif
+
+    /* Notify all admin connections */
+    FOR_EACH_CONN_SAFE_BEGIN(admin_connections, conn) {
+        admin_event_limit_underrun(conn);
+    }
+    FOR_EACH_CONN_SAFE_END(admin_connections, conn)
+}
+
+static void event_free(struct event_info *event)
+{
+    DbgLog(DL3, "%s: free event: %p", __func__, event);
+
+    if (event->payload != NULL)
+        free(event->payload);
+    free(event);
+}
+
+static int event_add_to_pending_list(struct event_info *event)
+{
+    DL_NODE *list;
+
+    list = dlist_add_as_last(pending_events, event);
+    if (list == NULL) {
+        ErrLog("%s: failed add event to list of pending events", __func__);
+        return -ENOMEM;
+    }
+    pending_events = list;
+
+    pending_events_count++;
+
+    return 0;
+}
+
+static void event_remove_from_pending_list(struct event_info *event)
+{
+    DL_NODE *node;
+    int trigger = 0;
+
+    node = dlist_find(pending_events, event);
+    if (node != NULL) {
+        pending_events = dlist_remove_node(pending_events, node);
+
+        if (pending_events_count >= MAX_PENDING_EVENTS)
+            trigger = 1;
+
+        if (pending_events_count > 0)
+            pending_events_count--;
+
+        if (trigger)
+            event_limit_underrun();
+    }
+}
+
+static int event_start_deliver(struct event_info *event)
+{
+    struct proc_conn_info *conn;
+    int rc;
+
+    DbgLog(DL3, "%s: event: %p", __func__, event);
+
+    if (pending_events_count >= MAX_PENDING_EVENTS) {
+        InfoLog("%s: Max pending events reached", __func__);
+        return -ENOSPC;
+    }
+
+    /* Add event of the list of pending events */
+    rc = event_add_to_pending_list(event);
+    if (rc != 0)
+        return rc;
+
+    /*
+     * Need to increment the event's ref count here, proc_deliver_event() may
+     * already complete the event delivery for one process, which then would
+     * free the event but it needs to be passed to other processes here, too.
+     */
+    event->proc_ref_count++;
+    FOR_EACH_CONN_SAFE_BEGIN(proc_connections, conn) {
+        rc = proc_deliver_event(conn, event);
+        if (rc != 0)
+            proc_hangup(conn);
+    }
+    FOR_EACH_CONN_SAFE_END(proc_connections, conn)
+    event->proc_ref_count--;
+
+    DbgLog(DL3, "%s: proc_ref_count: %u", __func__, event->proc_ref_count);
+
+    if (event->proc_ref_count == 0)
+        event_delivered(event);
+
+    return 0;
+}
+
+static void event_delivered(struct event_info *event)
+{
+    struct admin_conn_info *conn;
+    int rc;
+
+    DbgLog(DL3, "%s: event: %p", __func__, event);
+
+    event_remove_from_pending_list(event);
+
+    /* Notify owning admin connection (if available), free otherwise */
+    if (event->admin_ref != NULL) {
+        conn = event->admin_ref;
+        admin_get(conn);
+        rc = admin_event_delivered(conn, event);
+        if (rc != 0) {
+            admin_hangup(conn);
+            event_free(event);
+        }
+        admin_put(conn);
+    } else {
+        event_free(event);
+    }
+}
+
+static int proc_new_conn(int socket, struct listener_info *listener)
+{
+    struct proc_conn_info *conn;
+    struct event_info *event;
+    DL_NODE *list, *node;
+    int rc = 0;
+
+    UNUSED(listener);
+
+    DbgLog(DL0, "%s: Accepted connection from process: socket: %d", __func__,
+           socket);
+
+    conn = calloc(1, sizeof(struct proc_conn_info));
+    if (conn == NULL) {
+        ErrLog("%s: Failed to to allocate memory for the process connection",
+               __func__);
+        return -ENOMEM;
+        /* Caller will close socket */
+    }
+
+    DbgLog(DL3, "%s: process conn: %p", __func__, conn);
+
+    /* Add currently pending events to this connection */
+    node = dlist_get_first(pending_events);
+    while (node != NULL) {
+        event = (struct event_info *)node->data;
+        DbgLog(DL3, "%s: event: %p", __func__, event);
+
+        list = dlist_add_as_last(conn->events, event);
+        if (list == NULL) {
+            ErrLog("%s: failed add event to list of process's pending events",
+                   __func__);
+            rc = -ENOMEM;
+            goto out;
+        }
+        conn->events = list;
+
+        event->proc_ref_count++;
+
+        node = dlist_next(node);
+    }
+
+    conn->state = PROC_INITIAL_SEND;
+
+    rc = client_socket_init(socket, proc_xfer_complete, proc_hangup, proc_free,
+                            conn, &conn->client_info);
+    if (rc != 0)
+        goto out;
+
+    /* Add it to the process connections list */
+    list = dlist_add_as_first(proc_connections, conn);
+    if (list == NULL) {
+        rc = -ENOMEM;
+        goto out;
+    }
+    proc_connections = list;
+
+    proc_get(conn);
+    rc = client_socket_send(&conn->client_info, &socketData,
+                            sizeof(socketData));
+    proc_put(conn);
+    conn = NULL; /* conn may have been freed by now */
+
+out:
+    if (rc != 0 && conn != NULL) {
+        proc_hangup(conn);
+        rc = 0; /* Don't return an error, we have already handled it */
+    }
+
+    return rc;
+}
+
+static int proc_xfer_complete(void *client)
+{
+    struct proc_conn_info *conn = client;
+    int rc;
+
+    DbgLog(DL0, "%s: Xfer completed: process: %p socket: %d state: %d",
+           __func__, conn, conn->client_info.socket, conn->state);
+
+    /*
+     * A non-zero return code returned by this function causes the caller to
+     * call proc_hangup(). Thus, no need to call proc_hangup() ourselves.
+     */
+
+    switch (conn->state) {
+    case PROC_INITIAL_SEND:
+        conn->state = PROC_WAIT_FOR_EVENT;
+        rc = proc_start_deliver_event(conn);
+        conn = NULL; /* conn may have been freed by now */
+        return rc;
+
+    case PROC_WAIT_FOR_EVENT:
+        /* handled in proc_start_deliver_event */
+        break;
+
+    case PROC_SEND_EVENT:
+        if (conn->event == NULL) {
+            TraceLog("%s: No current event to handle", __func__);
+            return -EINVAL;
+        }
+
+        if (conn->event->event.payload_len > 0) {
+            conn->state = PROC_SEND_PAYLOAD;
+            rc = client_socket_send(&conn->client_info, conn->event->payload,
+                                    conn->event->event.payload_len);
+            conn = NULL; /* conn may have been freed by now */
+            return rc;
+        }
+        FALL_THROUGH;
+        /* fall through */
+
+    case PROC_SEND_PAYLOAD:
+        if (conn->event == NULL) {
+            TraceLog("%s: No current event to handle", __func__);
+            return -EINVAL;
+        }
+
+        if (conn->event->event.flags & EVENT_FLAGS_REPLY_REQ) {
+            conn->state = PROC_RECEIVE_REPLY;
+            rc = client_socket_receive(&conn->client_info, &conn->reply,
+                                       sizeof(conn->reply));
+            conn = NULL; /* conn may have been freed by now */
+            return rc;
+        }
+        FALL_THROUGH;
+        /* fall through */
+
+    case PROC_RECEIVE_REPLY:
+        if (conn->event == NULL) {
+            TraceLog("%s: No current event to handle", __func__);
+            return -EINVAL;
+        }
+
+        if (conn->event->event.flags & EVENT_FLAGS_REPLY_REQ) {
+            if (conn->reply.version != EVENT_VERSION_1) {
+                InfoLog("%s: Reply has a wrong version: %u", __func__,
+                        conn->reply.version);
+                return -EINVAL;
+            }
+
+            /* Update reply counters in event */
+            conn->event->reply.positive_replies += conn->reply.positive_replies;
+            conn->event->reply.negative_replies += conn->reply.negative_replies;
+            conn->event->reply.nothandled_replies +=
+                                                conn->reply.nothandled_replies;
+        }
+
+        conn->state = PROC_WAIT_FOR_EVENT;
+
+        rc = proc_event_delivered(conn, conn->event);
+        conn = NULL; /* conn may have been freed by now */
+        return rc;
+
+    case PROC_HANGUP:
+        break;
+    }
+
+    return 0;
+}
+
+static int proc_start_deliver_event(struct proc_conn_info *conn)
+{
+    DL_NODE *node;
+    int rc;
+
+    if (conn->state != PROC_WAIT_FOR_EVENT)
+        return 0;
+
+    node = dlist_get_first(conn->events);
+    if (node == NULL)
+        return 0;
+
+    conn->event = node->data;
+    memset(&conn->reply, 0, sizeof(conn->reply));
+
+    DbgLog(DL3, "%s: process: %p event: %p", __func__, conn, conn->event);
+
+    conn->state = PROC_SEND_EVENT;
+    rc = client_socket_send(&conn->client_info, &conn->event->event,
+                            sizeof(conn->event->event));
+    conn = NULL; /* conn may have been freed by now */
+    return rc;
+}
+
+static int proc_deliver_event(struct proc_conn_info *conn,
+                              struct event_info *event)
+{
+    DL_NODE *list;
+    int rc;
+
+    DbgLog(DL3, "%s: process: %p event: %p", __func__, conn, event);
+
+    if (conn->state == PROC_HANGUP)
+        return 0;
+
+    /* Add to process's event list and incr. reference count */
+    list = dlist_add_as_last(conn->events, event);
+    if (list == NULL) {
+        ErrLog("%s: failed add event to list of process's pending events",
+               __func__);
+        return -ENOMEM;
+    }
+    conn->events = list;
+
+    event->proc_ref_count++;
+
+    rc = proc_start_deliver_event(conn);
+    return rc;
+}
+
+static int proc_event_delivered(struct proc_conn_info *conn,
+                                struct event_info *event)
+{
+    DL_NODE *node;
+    int rc;
+
+    DbgLog(DL3, "%s: process: %p event: %p", __func__, conn, event);
+
+    conn->event = NULL;
+
+    /* Remove from process's event list and decr. reference count */
+    node = dlist_find(conn->events, event);
+    if (node != NULL) {
+        conn->events = dlist_remove_node(conn->events, node);
+        event->proc_ref_count--;
+    }
+
+    DbgLog(DL3, "%s: proc_ref_count: %u", __func__, event->proc_ref_count);
+
+    if (event->proc_ref_count == 0)
+        event_delivered(event);
+
+    /* Deliver further pending events, if any */
+    rc = proc_start_deliver_event(conn);
+    conn = NULL; /* conn may have been freed by now */
+    return rc;
+}
+
+static inline void proc_get(struct proc_conn_info *conn)
+{
+    client_socket_get(&conn->client_info);
+}
+
+static inline void proc_put(struct proc_conn_info *conn)
+{
+    client_socket_put(&conn->client_info);
+}
+
+static void proc_hangup(void *client)
+{
+    struct proc_conn_info *conn = client;
+    struct event_info *event;
+    DL_NODE *node;
+
+    DbgLog(DL0, "%s: process: %p socket: %d state: %d", __func__, conn,
+           conn->client_info.socket, conn->state);
+
+    if (conn->state == PROC_HANGUP)
+        return;
+    conn->state = PROC_HANGUP;
+
+    /* Unlink all pending events */
+    while ((node = dlist_get_first(conn->events)) != NULL) {
+        event = node->data;
+        /* We did not handle this event */
+        event->reply.nothandled_replies++;
+        proc_event_delivered(conn, event);
+    }
+
+    client_socket_term(&conn->client_info);
+    proc_put(conn);
+}
+
+static void proc_free(void *client)
+{
+    struct proc_conn_info *conn = client;
+    DL_NODE *node;
+
+    /* Remove it from the process connections list */
+    node = dlist_find(proc_connections, conn);
+    if (node != NULL) {
+        proc_connections = dlist_remove_node(proc_connections, node);
+        listener_client_hangup(&proc_listener);
+    }
+
+    DbgLog(DL0, "%s: process: %p", __func__, conn);
+    free(conn);
+}
+
+static int admin_new_conn(int socket, struct listener_info *listener)
+{
+    struct admin_conn_info *conn;
+    DL_NODE *list;
+    int rc = 0;
+
+    UNUSED(listener);
+
+    DbgLog(DL0, "%s: Accepted connection from admin: socket: %d", __func__,
+           socket);
+
+    conn = calloc(1, sizeof(struct admin_conn_info));
+    if (conn == NULL) {
+        ErrLog("%s: Failed to to allocate memory for the admin connection",
+               __func__);
+        return -ENOMEM;
+        /* Caller will close socket */
+    }
+
+    DbgLog(DL3, "%s: admin conn: %p", __func__, conn);
+
+    conn->state = ADMIN_RECEIVE_EVENT;
+
+    rc = client_socket_init(socket, admin_xfer_complete, admin_hangup,
+                            admin_free, conn, &conn->client_info);
+    if (rc != 0)
+        goto out;
+
+    conn->event = event_new(0, conn);
+    if (conn->event == NULL) {
+        ErrLog("%s: Failed to allocate a new event", __func__);
+        rc = -ENOMEM;
+        goto out;
+    }
+
+    /* Add it to the admin connections list */
+    list = dlist_add_as_first(admin_connections, conn);
+    if (list == NULL) {
+        ErrLog("%s: Failed to add connection to list of admin connections",
+               __func__);
+        rc = -ENOMEM;
+        goto out;
+    }
+    admin_connections = list;
+
+    admin_get(conn);
+    rc = client_socket_receive(&conn->client_info, &conn->event->event,
+                               sizeof(conn->event->event));
+    admin_put(conn);
+    conn = NULL; /* conn may have been freed by now */
+
+out:
+    if (rc != 0 && conn != NULL) {
+        admin_hangup(conn);
+        rc = 0; /* Don't return an error, we have already handled it */
+    }
+
+    return rc;
+}
+
+static int admin_xfer_complete(void *client)
+{
+    struct admin_conn_info *conn = client;
+    int rc;
+
+    DbgLog(DL0, "%s: Xfer completed: admin: %p socket: %d state: %d",
+           __func__, conn, conn->client_info.socket, conn->state);
+
+    /*
+     * A non-zero return code returned by this function causes the caller to
+     * call admin_hangup(). Thus, no need to call admin_hangup() ourselves.
+     */
+
+    if (conn->event == NULL) {
+        TraceLog("%s: No current event", __func__);
+        return -EINVAL;
+    }
+
+    switch (conn->state) {
+    case ADMIN_RECEIVE_EVENT:
+        /* We have received the event from the admin */
+        DbgLog(DL3, "%s: Event version:      %u", __func__,
+               conn->event->event.version);
+        DbgLog(DL3, "%s: Event type:         0x%08x", __func__,
+               conn->event->event.type);
+        DbgLog(DL3, "%s: Event flags:        0x%08x", __func__,
+               conn->event->event.flags);
+        DbgLog(DL3, "%s: Event token_type:   0x%08x", __func__,
+               conn->event->event.token_type);
+        DbgLog(DL3, "%s: Event token_name:   '%.32s'", __func__,
+               conn->event->event.token_label);
+        DbgLog(DL3, "%s: Event process_id:   %u", __func__,
+               conn->event->event.process_id);
+        DbgLog(DL3, "%s: Event payload_len:  %u", __func__,
+               conn->event->event.payload_len);
+
+        if (conn->event->event.version != EVENT_VERSION_1) {
+            InfoLog("%s: Admin event has invalid version: %d", __func__,
+                    conn->event->event.version);
+            return -EINVAL;
+        }
+        if (conn->event->event.payload_len > EVENT_MAX_PAYLOAD_LENGTH) {
+            InfoLog("%s: Admin event payload is too large: %u", __func__,
+                    conn->event->event.payload_len);
+            return -EMSGSIZE;
+        }
+
+        if (conn->event->event.payload_len > 0) {
+            conn->event->payload = malloc(conn->event->event.payload_len);
+            if (conn->event->payload == NULL) {
+                ErrLog("%s: Failed to allocate the payload buffer", __func__);
+                return -ENOMEM;
+            }
+
+            conn->state = ADMIN_RECEIVE_PAYLOAD;
+            rc = client_socket_receive(&conn->client_info, conn->event->payload,
+                                       conn->event->event.payload_len);
+            conn = NULL; /* conn may have been freed by now */
+            return rc;
+        }
+        FALL_THROUGH;
+        /* fall through */
+
+    case ADMIN_RECEIVE_PAYLOAD:
+        /* We have received the payload (if any) from the admin */
+        conn->state = ADMIN_EVENT_DELIVERED;
+        rc = event_start_deliver(conn->event);
+        if (rc != 0) {
+            if (rc == -ENOSPC) {
+                /* Event limit reached, delay */
+                conn->state = ADMIN_WAIT_FOR_EVENT_LIMIT;
+                return 0;
+            }
+            return rc;
+        }
+        break;
+
+    case ADMIN_WAIT_FOR_EVENT_LIMIT:
+        /* This state is handled in admin_event_limit_underrun() */
+        break;
+
+    case ADMIN_EVENT_DELIVERED:
+        /* This state is handled in admin_event_delivered() */
+        break;
+
+    case ADMIN_SEND_REPLY:
+        /* The reply has been sent to the admin */
+        if (conn->event->admin_ref != NULL)
+            admin_put(conn->event->admin_ref);
+        conn->event->admin_ref = NULL;
+        event_free(conn->event);
+
+        conn->event = event_new(0, conn);
+        if (conn->event == NULL) {
+            ErrLog("%s: Failed to allocate a new event", __func__);
+            return -ENOMEM;
+        }
+
+        conn->state = ADMIN_RECEIVE_EVENT;
+        rc = client_socket_receive(&conn->client_info, &conn->event->event,
+                                   sizeof(conn->event->event));
+        conn = NULL; /* conn may have been freed by now */
+        return rc;
+
+    case ADMIN_HANGUP:
+        break;
+    }
+
+    return 0;
+}
+
+static void admin_event_limit_underrun(struct admin_conn_info *conn)
+{
+    int rc;
+
+    DbgLog(DL3, "%s: admin: %p state: %d", __func__, conn, conn->state);
+
+    if (conn->state != ADMIN_WAIT_FOR_EVENT_LIMIT)
+        return;
+
+    conn->state = ADMIN_EVENT_DELIVERED;
+
+    rc = event_start_deliver(conn->event);
+    if (rc != 0) {
+        if (rc == -ENOSPC) {
+            /* Event limit reached, delay */
+            conn->state = ADMIN_WAIT_FOR_EVENT_LIMIT;
+            return;
+        }
+        admin_hangup(conn);
+    }
+}
+
+static int admin_event_delivered(struct admin_conn_info *conn,
+                                 struct event_info *event)
+{
+    int rc;
+
+    DbgLog(DL3, "%s: admin: %p event: %p", __func__, conn, event);
+
+    /*
+     * A non-zero return code returned by this function causes the caller to
+     * call admin_hangup(). Thus, no need to call admin_hangup() ourselves.
+     */
+
+    if (conn->state != ADMIN_EVENT_DELIVERED) {
+        TraceLog("%s: wrong state: %d", __func__, conn->state);
+        return -EINVAL;
+    }
+
+    if (event->event.flags & EVENT_FLAGS_REPLY_REQ) {
+        if (conn->event != event) {
+            TraceLog("%s: event not the current event", __func__);
+            return -EINVAL;
+        }
+
+        DbgLog(DL3, "%s: Reply version:      %u", __func__,
+               event->reply.version);
+        DbgLog(DL3, "%s: Reply positive:     %lu", __func__,
+               event->reply.positive_replies);
+        DbgLog(DL3, "%s: Reply negative:     %lu", __func__,
+               event->reply.negative_replies);
+        DbgLog(DL3, "%s: Reply not-handled:  %lu", __func__,
+               event->reply.nothandled_replies);
+
+        conn->state = ADMIN_SEND_REPLY;
+        rc = client_socket_send(&conn->client_info, &event->reply,
+                                sizeof(event->reply));
+        return rc;
+    }
+
+    /* No reply required, free the event, and receive the next one */
+    if (event->admin_ref != NULL)
+        admin_put(event->admin_ref);
+    event->admin_ref = NULL;
+    event_free(event);
+
+    conn->event = event_new(0, conn);
+    if (conn->event == NULL) {
+        ErrLog("%s: Failed to allocate a new event", __func__);
+        return -ENOMEM;
+    }
+
+    conn->state = ADMIN_RECEIVE_EVENT;
+    rc = client_socket_receive(&conn->client_info, &conn->event->event,
+                               sizeof(conn->event->event));
+    return rc;
+}
+
+static inline void admin_get(struct admin_conn_info *conn)
+{
+    client_socket_get(&conn->client_info);
+}
+
+static inline void admin_put(struct admin_conn_info *conn)
+{
+    client_socket_put(&conn->client_info);
+}
+
+static void admin_hangup(void *client)
+{
+    struct admin_conn_info *conn = client;
+
+    DbgLog(DL0, "%s: admin: %p socket: %d state: %d", __func__, conn,
+           conn->client_info.socket, conn->state);
+
+    if (conn->state == ADMIN_HANGUP)
+        return;
+    conn->state = ADMIN_HANGUP;
+
+    /* Unlink pending event (if any) */
+    if (conn->event != NULL) {
+        if (conn->event->admin_ref != NULL)
+            admin_put(conn->event->admin_ref);
+        conn->event->admin_ref = NULL;
+        if (conn->event->proc_ref_count == 0) {
+            event_remove_from_pending_list(conn->event);
+            event_free(conn->event);
+        }
+        conn->event = NULL;
+    }
+
+    client_socket_term(&conn->client_info);
+    admin_put(conn);
+}
+
+static void admin_free(void *client)
+{
+    struct admin_conn_info *conn = client;
+    DL_NODE *node;
 
-int proc_listener_socket = -1;
+    /* Remove it from the admin connections list */
+    node = dlist_find(admin_connections, conn);
+    if (node != NULL) {
+        admin_connections = dlist_remove_node(admin_connections, node);
+        listener_client_hangup(&admin_listener);
+    }
 
-static void close_listener_socket(int socketfd, const char *file_path);
+    DbgLog(DL0, "%s: admin: %p", __func__, conn);
+    free(conn);
+}
 
-// Creates the daemon's listener socket, to which clients will connect and
-// retrieve slot information through.  Returns the file descriptor of the
-// created socket.
-static int create_listener_socket(const char *file_path)
+static int listener_socket_create(const char *file_path)
 {
     struct sockaddr_un address;
     struct group *grp;
-    int socketfd;
+    int listener_socket, err;
 
-    socketfd = socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
-    if (socketfd < 0) {
-        ErrLog("Failed to create listener socket, errno 0x%X.", errno);
+    listener_socket = socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
+    if (listener_socket < 0) {
+        err = errno;
+        ErrLog("%s: Failed to create listener socket, errno %d (%s).",
+               __func__, err, strerror(err));
         return -1;
     }
     if (unlink(file_path) && errno != ENOENT) {
-        ErrLog("Failed to unlink socket file, errno 0x%X.", errno);
+        err = errno;
+        ErrLog("%s: Failed to unlink socket file, errno %d (%s).", __func__,
+               err, strerror(err));
         goto error;
     }
 
@@ -52,50 +1249,389 @@ static int create_listener_socket(const char *file_path)
     address.sun_family = AF_UNIX;
     strcpy(address.sun_path, file_path);
 
-    if (bind(socketfd,
+    if (bind(listener_socket,
              (struct sockaddr *) &address, sizeof(struct sockaddr_un)) != 0) {
-        ErrLog("Failed to bind to socket, errno 0x%X.", errno);
+        err = errno;
+        ErrLog("%s: Failed to bind to socket, errno %d (%s).", __func__, err,
+               strerror(err));
         goto error;
     }
     // make socket file part of the pkcs11 group, and write accessable
     // for that group
     grp = getgrnam("pkcs11");
     if (!grp) {
-        ErrLog("Group PKCS#11 does not exist");
+        ErrLog("%s: Group PKCS#11 does not exist", __func__);
         goto error;
     }
     if (chown(file_path, 0, grp->gr_gid)) {
-        ErrLog("Could not change file group on socket, errno 0x%X.", errno);
+        err = errno;
+        ErrLog("%s: Could not change file group on socket, errno %d (%s).",
+               __func__, err, strerror(err));
         goto error;
     }
     if (chmod(file_path,
-              S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP | S_IXUSR | S_IXGRP)) {
-        ErrLog("Could not change file permissions on socket, errno 0x%X.",
-               errno);
+              S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP)) {
+        err = errno;
+        ErrLog("%s: Could not change file permissions on socket, errno %d (%s).",
+                __func__, err, strerror(err));
         goto error;
     }
 
-    if (listen(socketfd, 20) != 0) {
-        ErrLog("Failed to listen to socket, errno 0x%X.", errno);
+    if (listen(listener_socket, 20) != 0) {
+        err = errno;
+        ErrLog("%s: Failed to listen to socket, errno %d (%s).", __func__, err,
+               strerror(err));
         goto error;
     }
 
-    return socketfd;
+    return listener_socket;
 
 error:
-    if (socketfd >= 0)
-        close_listener_socket(socketfd, file_path);
+    if (listener_socket >= 0)
+        listener_socket_close(listener_socket, file_path);
 
     return -1;
 }
 
 
-static void close_listener_socket(int socketfd, const char *file_path)
+static void listener_socket_close(int listener_socket, const char *file_path)
 {
-    close(socketfd);
+    close(listener_socket);
     unlink(file_path);
 }
 
+
+
+static int listener_notify(int events, void *private)
+{
+    struct listener_info *listener = private;
+    struct sockaddr_un address;
+    socklen_t address_length = sizeof(address);
+    int client_socket, rc, err;
+
+    if ((events & EPOLLIN) == 0)
+        return 0;
+
+    /* epoll is edge triggered. We must call accept until we get EWOULDBLOCK */
+    while (listener->num_clients < listener->max_num_clients) {
+        client_socket = accept(listener->socket, (struct sockaddr *) &address,
+                               &address_length);
+        if (client_socket < 0) {
+            err = errno;
+            if (err == EWOULDBLOCK)
+                break;
+            InfoLog("%s: Failed to accept connection on socket %d, errno %d (%s).",
+                    __func__, listener->socket, err, strerror(err));
+            return -err;
+        }
+
+        rc = listener->new_conn(client_socket, listener);
+        if (rc != 0) {
+            TraceLog("%s: new_conn callback failed for client socket %d, rc: %d",
+                      __func__, client_socket, rc);
+            close(client_socket);
+            continue;
+        }
+
+        listener->num_clients++;
+    }
+
+    return 0;
+}
+
+static int listener_client_hangup(struct listener_info *listener)
+{
+    int rc, trigger = 0;
+
+    if (listener->num_clients >= listener->max_num_clients)
+        trigger = 1; /* We were at max clients, trigger accept now */
+
+    if (listener->num_clients > 0)
+        listener->num_clients--;
+
+    if (trigger && listener->num_clients < listener->max_num_clients) {
+        rc = listener_notify(EPOLLIN, listener);
+        if (rc != 0)
+            return rc;
+    }
+
+    return 0;
+}
+
+static int listener_create(const char *file_path,
+                           struct listener_info *listener,
+                           int (* new_conn)(int socket,
+                                            struct listener_info *listener),
+                           unsigned long max_num_clients)
+{
+    struct epoll_event evt;
+    int rc, err;
+
+    if (listener == NULL || new_conn == NULL)
+        return FALSE;
+
+    memset(listener, 0, sizeof(*listener));
+    epoll_info_init(&listener->ep_info, listener_notify, NULL, listener);
+    listener->file_path = file_path;
+    listener->new_conn = new_conn;
+    listener->max_num_clients = max_num_clients;
+
+    listener->socket = listener_socket_create(file_path);
+    if (listener->socket < 0)
+        return FALSE;
+
+    evt.events = EPOLLIN | EPOLLET;
+    evt.data.ptr = &listener->ep_info;
+    rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listener->socket, &evt);
+    if (rc != 0) {
+        err = errno;
+        TraceLog("%s: Failed add listener socket %d to epoll, errno %d (%s).",
+                  __func__, listener->socket, err, strerror(err));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void listener_term(struct listener_info *listener)
+{
+    if (listener == NULL || listener->socket < 0)
+        return;
+
+    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, listener->socket, NULL);
+    listener_socket_close(listener->socket, listener->file_path);
+}
+
+#ifdef WITH_LIBUDEV
+
+static int udev_mon_init(const char *subsystem, struct udev_mon *udev_mon)
+{
+    struct epoll_event evt;
+    int rc, err;
+
+    if (subsystem == NULL || udev_mon == NULL)
+        return FALSE;
+
+    udev_mon->delayed_event = 0;
+
+    udev_mon->udev = udev_new();
+    if (udev_mon->udev == NULL) {
+        ErrLog("%s: udev_new failed", __func__);
+        goto error;
+    }
+
+    udev_mon->mon = udev_monitor_new_from_netlink(udev_mon->udev, "udev");
+    if (udev_mon->mon == NULL) {
+        ErrLog("%s: udev_monitor_new_from_netlink failed", __func__);
+        goto error;
+    }
+
+    /*
+     * Try to increase the receive buffer size. This may fail if the required
+     * privileges are not given. Ignore if it fails.
+     */
+    udev_monitor_set_receive_buffer_size(udev_mon->mon, UDEV_RECV_BUFFFER_SIZE);
+
+    rc = udev_monitor_filter_add_match_subsystem_devtype(udev_mon->mon,
+                                                         subsystem, NULL);
+    if (rc != 0) {
+        ErrLog("%s: udev_monitor_filter_add_match_subsystem_devtype failed: "
+               "rc=%d", __func__, rc);
+        goto error;
+    }
+
+    rc = udev_monitor_enable_receiving(udev_mon->mon);
+    if (rc != 0) {
+        ErrLog("%s: udev_monitor_enable_receiving failed: rc=%d", __func__, rc);
+        goto error;
+    }
+
+    udev_mon->socket = udev_monitor_get_fd(udev_mon->mon);
+    if (udev_mon->socket < 0) {
+        ErrLog("%s: udev_monitor_get_fd failed", __func__);
+        goto error;
+    }
+
+    epoll_info_init(&udev_mon->ep_info, udev_mon_notify, NULL, udev_mon);
+
+    evt.events = EPOLLIN | EPOLLET;
+    evt.data.ptr = &udev_mon->ep_info;
+    rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, udev_mon->socket, &evt);
+    if (rc != 0) {
+        err = errno;
+        ErrLog("%s: Failed add udev_mon socket %d to epoll, errno %d (%s).",
+                __func__, udev_mon->socket, err, strerror(err));
+        goto error;
+    }
+
+    /* Epoll is edge triggered, thus try to receive once */
+    rc = udev_mon_notify(EPOLLIN, udev_mon);
+    if (rc != 0)
+        goto error;
+
+    return TRUE;
+
+error:
+    udev_mon_term(udev_mon);
+    return FALSE;
+}
+
+
+static int udev_mon_handle_device(struct udev_mon *udev_mon,
+                                  struct udev_device *dev)
+{
+    const char *action, *devname, *devpath, *devtype, *dev_type_prop;
+    unsigned int card, domain, dev_type;
+    struct event_info *event;
+    event_udev_apqn_data_t *apqn_data;
+    int rc;
+
+    UNUSED(udev_mon);
+
+    action = udev_device_get_action(dev);
+    devname = udev_device_get_sysname(dev);
+    devpath = udev_device_get_devpath(dev);
+    devtype = udev_device_get_devtype(dev);
+    dev_type_prop = udev_device_get_property_value(dev, UDEV_PROERTY_DEVTYPE);
+
+    if (action == NULL || devname == NULL || devpath == NULL ||
+        devtype == NULL || dev_type_prop == NULL)
+        return 0;
+
+    DbgLog(DL3, "%s: Uevent: ACTION=%s DEVNAME=%s DEVPATH=%s DEVTYPE=%s "
+           "DEV_TYPE=%s", __func__, action, devname, devpath, devtype,
+           dev_type_prop);
+
+    /* We are only interested in bind and unbind events ... */
+    if (strcmp(action, UDEV_ACTION_BIND) != 0 &&
+        strcmp(action, UDEV_ACTION_UNBIND) != 0)
+        return 0;
+
+    /* ... for an APQN device */
+    if (strcmp(devtype, UDEV_ACTION_DEVTYPE_APQN) != 0)
+        return 0;
+
+    if (sscanf(devname, "%x.%x", &card, &domain) != 2) {
+        TraceLog("%s: failed to parse APQN from DEVNAME: %s", __func__, devname);
+        return -EIO;
+    }
+    if (sscanf(dev_type_prop, "%x", &dev_type) != 1) {
+        TraceLog("%s: failed to parse DEV_TYPE: %s", __func__, dev_type_prop);
+        return -EIO;
+    }
+
+    event = event_new(sizeof(event_udev_apqn_data_t), NULL);
+    if (event == NULL) {
+        ErrLog("%s: failed to allocate an event", __func__);
+        return -ENOMEM;
+    }
+
+    if (strcmp(udev_device_get_action(dev), UDEV_ACTION_BIND) == 0)
+        event->event.type = EVENT_TYPE_APQN_ADD;
+    else
+        event->event.type = EVENT_TYPE_APQN_REMOVE;
+    event->event.flags = EVENT_FLAGS_NONE;
+    event->event.token_type = EVENT_TOK_TYPE_ALL;
+    memset(event->event.token_label, ' ',
+           sizeof(event->event.token_label));
+
+    apqn_data = (event_udev_apqn_data_t *)event->payload;
+    apqn_data->card = card;
+    apqn_data->domain = domain;
+    apqn_data->device_type = dev_type;
+
+    DbgLog(DL3, "%s: Event version:      %u", __func__, event->event.version);
+    DbgLog(DL3, "%s: Event type:         0x%08x", __func__, event->event.type);
+    DbgLog(DL3, "%s: Event flags:        0x%08x", __func__, event->event.flags);
+    DbgLog(DL3, "%s: Event token_type:   0x%08x", __func__,
+           event->event.token_type);
+    DbgLog(DL3, "%s: Event token_name:   '%.32s'", __func__,
+           event->event.token_label);
+    DbgLog(DL3, "%s: Event process_id:   %u", __func__, event->event.process_id);
+    DbgLog(DL3, "%s: Event payload_len:  %u", __func__,
+           event->event.payload_len);
+
+    DbgLog(DL3, "%s: Payload: card:      %u", __func__, apqn_data->card);
+    DbgLog(DL3, "%s: Payload: domain:    %u", __func__, apqn_data->domain);
+    DbgLog(DL3, "%s: Payload: dev.type:  %u", __func__, apqn_data->device_type);
+
+    rc = event_start_deliver(event);
+    if (rc != 0) {
+        if (rc == -ENOSPC) {
+            /* Event limit reached, delay event delivery */
+            udev_mon->delayed_event = event;
+            return -ENOSPC;
+        }
+        event_free(event);
+        return rc;
+    }
+
+    return 0;
+}
+
+static int udev_mon_notify(int events, void *private)
+{
+    struct udev_mon *udev_mon = private;
+    struct udev_device *dev;
+    struct event_info *event;
+    int rc;
+
+    DbgLog(DL3, "%s: Epoll event on udev_mon socket %d: events: 0x%x",
+           __func__, udev_mon->socket, events);
+
+    if (udev_mon->delayed_event != NULL) {
+        /* Deliver delayed event first */
+        event = udev_mon->delayed_event;
+        udev_mon->delayed_event = NULL;
+
+        rc = event_start_deliver(event);
+        if (rc != 0) {
+            if (rc == -ENOSPC) {
+                /* Event limit reached, delay event delivery */
+                udev_mon->delayed_event = event;
+                return 0;
+            }
+            event_free(event);
+            return rc;
+        }
+    }
+
+    while (1) {
+        dev = udev_monitor_receive_device(udev_mon->mon);
+        if (dev == NULL)
+            break; /* this is just like EWOULDBLOCK */
+
+        rc = udev_mon_handle_device(udev_mon, dev);
+        if (rc != 0)
+            TraceLog("%s: udev_mon_handle_device failed, rc: %d", __func__, rc);
+
+        udev_device_unref(dev);
+
+        /* If event limit reached, stop receiving more events */
+        if (rc == -ENOSPC)
+            break;
+    };
+
+    return 0;
+}
+
+static void udev_mon_term(struct udev_mon *udev_mon)
+{
+    if (udev_mon == NULL)
+        return;
+
+    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, udev_mon->socket, NULL);
+    if (udev_mon->udev != NULL)
+        udev_unref(udev_mon->udev);
+    if (udev_mon->mon != NULL)
+        udev_monitor_unref(udev_mon->mon);
+
+    if (udev_mon->delayed_event != NULL)
+        event_free(udev_mon->delayed_event);
+}
+
+#endif
+
 int init_socket_data(Slot_Mgr_Socket_t *socketData)
 {
     unsigned int processed = 0;
@@ -106,7 +1642,7 @@ int init_socket_data(Slot_Mgr_Socket_t *socketData)
 
     /* check that we read in correct amount of slots */
     if (processed != NumberSlotsInDB) {
-        ErrLog("Failed to populate slot info.\n");
+        ErrLog("%s: Failed to populate slot info.", __func__);
         return FALSE;
     }
 
@@ -115,72 +1651,277 @@ int init_socket_data(Slot_Mgr_Socket_t *socketData)
 
 int socket_connection_handler(int timeout_secs)
 {
-    int returnVal;
-    fd_set set;
-    struct timeval timeout;
-
-    FD_ZERO(&set);
-    FD_SET(proc_listener_socket, &set);
+    struct epoll_event events[MAX_EPOLL_EVENTS];
+    int num_events, i, rc, err;
+    struct epoll_info *info;
 
-    timeout.tv_sec = timeout_secs;
-    timeout.tv_usec = 0;
-
-    returnVal = select(proc_listener_socket + 1, &set, NULL, NULL, &timeout);
-    if (returnVal == -1) {
-        ErrLog("select failed on socket connection, errno 0x%X.", errno);
-        return FALSE;
-    } else if (returnVal == 0) {
-        // select call timed out, return
-        return FALSE;
-    } else {
-        struct sockaddr_un address;
-        socklen_t address_length = sizeof(address);
-
-        int connectionfd = accept(proc_listener_socket,
-                                  (struct sockaddr *) &address,
-                                  &address_length);
-        if (connectionfd < 0) {
-            if (errno != EAGAIN && errno != EWOULDBLOCK) {
-                /* These errors are allowed since
-                 * socket is non-blocking
-                 */
-                ErrLog("Failed to accept socket connection, errno 0x%X.",
-                       errno);
-            }
+    do {
+        num_events = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS,
+                                timeout_secs * 1000);
+        if (num_events < 0) {
+            err = errno;
+            if (err == EINTR)
+                continue;
+            ErrLog("%s: epoll_wait failed, errno %d (%s).", __func__, err,
+                   strerror(err));
             return FALSE;
         }
 
-        DbgLog(DL0, "Accepted connection from process: socket: %d", 
-               connectionfd);
+        /*
+         * Inc ref count of all epoll_infos returned by epoll before handling
+         * any of them via notify. The notify callback may hangup any of
+         * the connections associated with the returned epoll_infos, and we
+         * need to avoid them getting freed before we all handled them.
+         */
+        for (i = 0; i < num_events; i++)
+            epoll_info_get(events[i].data.ptr);
 
-        if (write(connectionfd, &socketData, sizeof(socketData)) !=
-            sizeof(socketData)) {
-            ErrLog("Failed to write socket data, errno 0x%X.", errno);
-            close(connectionfd);
-            return FALSE;
+        for (i = 0; i < num_events; i++) {
+            info = events[i].data.ptr;
+            if (info == NULL || info->notify == NULL)
+                continue;
+
+            rc = info->notify(events[i].events, info->private);
+            if (rc != 0)
+                TraceLog("%s: notify callback failed, rc: %d", __func__, rc);
+
+            epoll_info_put(info);
         }
-        close(connectionfd);
-        return TRUE;
-    }
+    } while (num_events > 0 && rc == 0); /* num_events = 0: timeout */
+
+    return TRUE;
 }
 
 int init_socket_server()
 {
-    proc_listener_socket = create_listener_socket(PROC_SOCKET_FILE_PATH);
-    if (proc_listener_socket < 0)
+    int err;
+
+    epoll_fd = epoll_create1(0);
+    if (epoll_fd < 0) {
+        err = errno;
+        ErrLog("%s: Failed to open epoll socket, errno %d (%s).", __func__, err,
+               strerror(err));
+        return FALSE;
+    }
+
+    if (!listener_create(PROC_SOCKET_FILE_PATH, &proc_listener,
+                         proc_new_conn, NUMBER_PROCESSES_ALLOWED)) {
+        term_socket_server();
+        return FALSE;
+    }
+
+    if (!listener_create(ADMIN_SOCKET_FILE_PATH, &admin_listener,
+                         admin_new_conn, NUMBER_ADMINS_ALLOWED)) {
+        term_socket_server();
         return FALSE;
+    }
+
+#ifdef WITH_LIBUDEV
+    if (!udev_mon_init(UDEV_SUBSYSTEM_AP, &udev_mon)) {
+        term_socket_server();
+        return FALSE;
+    }
+#endif
 
-    DbgLog(DL0, "Socket server started");
+    DbgLog(DL0, "%s: Socket server started", __func__);
 
     return TRUE;
 }
 
 int term_socket_server()
 {
-    if (proc_listener_socket >= 0)
-        close_listener_socket(proc_listener_socket, PROC_SOCKET_FILE_PATH);
+    DL_NODE *node, *next;
+
+#ifdef WITH_LIBUDEV
+    udev_mon_term(&udev_mon);
+#endif
+
+    listener_term(&proc_listener);
+    listener_term(&admin_listener);
+
+    node = dlist_get_first(proc_connections);
+    while (node != NULL) {
+        next = dlist_next(node);
+        proc_hangup(node->data);
+        node = next;
+    }
+    dlist_purge(proc_connections);
 
-    DbgLog(DL0, "Socket server stopped");
+    node = dlist_get_first(admin_connections);
+    while (node != NULL) {
+        next = dlist_next(node);
+        admin_hangup(node->data);
+        node = next;
+    }
+    dlist_purge(admin_connections);
+
+    node = dlist_get_first(pending_events);
+    while (node != NULL) {
+        next = dlist_next(node);
+        event_free((struct event_info *)node->data);
+        node = next;
+    }
+    dlist_purge(pending_events);
+
+    if (epoll_fd >= 0)
+        close(epoll_fd);
+    epoll_fd = -1;
+
+    DbgLog(DL0, "%s: Socket server stopped", __func__);
 
     return TRUE;
 }
+
+#ifdef DEV
+
+static void dump_listener(struct listener_info *listener)
+{
+    DbgLog(DL0, "    socket: %d", listener->socket);
+    DbgLog(DL0, "    file_path: %s", listener->file_path);
+    DbgLog(DL0, "    ep_info.ref_count: %lu", listener->ep_info.ref_count);
+    DbgLog(DL0, "    num_clients: %lu", listener->num_clients);
+    DbgLog(DL0, "    max_num_clients: %lu", listener->max_num_clients);
+}
+
+static void dump_event_msg(event_msg_t *event, int indent)
+{
+    DbgLog(DL0, "%*sevent version: %u", indent, "", event->version);
+    DbgLog(DL0, "%*sevent type: %08x", indent, "", event->type);
+    DbgLog(DL0, "%*sevent flags: %08x", indent, "", event->flags);
+    DbgLog(DL0, "%*sevent token_type: %08x", indent, "", event->token_type);
+    DbgLog(DL0, "%*sevent token_label: '%.32s'", indent, "", event->token_label);
+    DbgLog(DL0, "%*sevent process_id: %lu", indent, "", event->process_id);
+    DbgLog(DL0, "%*sevent payload_len: %u", indent, "", event->payload_len);
+}
+
+static void dump_event_reply(event_reply_t *reply, int indent)
+{
+    DbgLog(DL0, "%*sreply version: %u", indent, "", reply->version);
+    DbgLog(DL0, "%*sreply positive_replies: %u", indent, "", reply->positive_replies);
+    DbgLog(DL0, "%*sreply negative_replies: %u", indent, "", reply->negative_replies);
+    DbgLog(DL0, "%*sreply nothandled_replies: %u", indent, "", reply->nothandled_replies);
+}
+
+static void dump_event_info(struct event_info *event, int indent)
+{
+    dump_event_msg(&event->event, indent);
+    dump_event_reply(&event->reply, indent);
+    DbgLog(DL0, "%*sproc_ref_count: %lu", indent, "", event->proc_ref_count);
+    if (event->admin_ref != NULL)
+        DbgLog(DL0, "%*sadmin_ref: %p", indent, "", event->admin_ref);
+    else
+        DbgLog(DL0, "%*sadmin_ref: None", indent, "");
+}
+
+static void dump_proc_conn(struct proc_conn_info *proc_conn)
+{
+    DL_NODE *node;
+    unsigned long i;
+
+    DbgLog(DL0, "      socket: %d", proc_conn->client_info.socket);
+    DbgLog(DL0, "      state: %d", proc_conn->state);
+    DbgLog(DL0, "      ref-count: %lu", proc_conn->client_info.ep_info.ref_count);
+    DbgLog(DL0, "      xfer state: %d", proc_conn->client_info.xfer_state);
+    DbgLog(DL0, "      xfer size: %d", proc_conn->client_info.xfer_size);
+    DbgLog(DL0, "      xfer offset: %d", proc_conn->client_info.xfer_offset);
+    DbgLog(DL0, "      pending events:");
+    node = dlist_get_first(proc_conn->events);
+    i = 1;
+    while (node != NULL) {
+        DbgLog(DL0, "        event %lu (%p):", i, node->data);
+        dump_event_info(node->data, 10);
+        node = dlist_next(node);
+        i++;
+    }
+    if (proc_conn->event != NULL) {
+        DbgLog(DL0, "      current event:");
+        dump_event_info(proc_conn->event, 8);
+        DbgLog(DL0, "      current reply:");
+        dump_event_reply(&proc_conn->reply, 8);
+    } else {
+        DbgLog(DL0, "      current event: none");
+    }
+}
+
+static void dump_admin_conn(struct admin_conn_info *admin_conn)
+{
+    DbgLog(DL0, "      socket: %d", admin_conn->client_info.socket);
+    DbgLog(DL0, "      state: %d", admin_conn->state);
+    DbgLog(DL0, "      ref-count: %lu", admin_conn->client_info.ep_info.ref_count);
+    DbgLog(DL0, "      xfer state: %d", admin_conn->client_info.xfer_state);
+    DbgLog(DL0, "      xfer size: %d", admin_conn->client_info.xfer_size);
+    DbgLog(DL0, "      xfer offset: %d", admin_conn->client_info.xfer_offset);
+    if (admin_conn->event != NULL) {
+        DbgLog(DL0, "      current event (%p):", admin_conn->event);
+        dump_event_info(admin_conn->event, 8);
+    } else {
+        DbgLog(DL0, "      current event: none");
+    }
+}
+
+#ifdef WITH_LIBUDEV
+void dump_udev_mon(struct udev_mon *udev_mon)
+{
+    DbgLog(DL0, "    socket: %d", udev_mon->socket);
+    DbgLog(DL0, "    udev: %p", udev_mon->udev);
+    DbgLog(DL0, "    mon: %p", udev_mon->mon);
+    DbgLog(DL0, "    ep_info.ref_count: %lu", udev_mon->ep_info.ref_count);
+    if (udev_mon->delayed_event != NULL) {
+        DbgLog(DL0, "    delayed_event (%p):", udev_mon->delayed_event);
+        dump_event_info(udev_mon->delayed_event, 6);
+    } else {
+        DbgLog(DL0, "    delayed_event: node");
+    }
+}
+#endif
+
+void dump_socket_handler()
+{
+    DL_NODE *node;
+    unsigned long i;
+
+    DbgLog(DL0, "%s: Dump of socket handler data:", __func__);
+    DbgLog(DL0, "  epoll_fd: %d", epoll_fd);
+
+    DbgLog(DL0, "  proc_listener (%p): ", &proc_listener);
+    dump_listener(&proc_listener);
+
+    DbgLog(DL0, "  proc_connections: ");
+    node = dlist_get_first(proc_connections);
+    i = 1;
+    while (node != NULL) {
+        DbgLog(DL0, "    proc_connection %lu (%p): ", i, node->data);
+        dump_proc_conn(node->data);
+        i++;
+        node = dlist_next(node);
+    }
+
+    DbgLog(DL0, "  admin_listener (%p): ", &admin_listener);
+    dump_listener(&admin_listener);
+
+    DbgLog(DL0, "  admin_connections: ");
+    node = dlist_get_first(admin_connections);
+    i = 1;
+    while (node != NULL) {
+        DbgLog(DL0, "    admin_connection %lu (%p): ", i, node->data);
+        dump_admin_conn(node->data);
+        i++;
+        node = dlist_next(node);
+    }
+
+#ifdef WITH_LIBUDEV
+    DbgLog(DL0, "  udev_mon (%p): ", &udev_mon);
+    dump_udev_mon(&udev_mon);
+#endif
+
+    DbgLog(DL0, "  pending events (%lu): ", pending_events_count);
+    node = dlist_get_first(pending_events);
+    i = 1;
+    while (node != NULL) {
+        DbgLog(DL0, "    event %lu (%p): ", i, node->data);
+        dump_event_info(node->data, 6);
+        i++;
+        node = dlist_next(node);
+    }
+}
+#endif