commit 342dfbeb8275f5ea6ed52dd3f30126614ec1d037 Author: Ingo Franzki Date: Mon Feb 15 14:33:07 2021 +0100 Event support: pkcsslotd changes Signed-off-by: Ingo Franzki 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 #include #include +#include +#include #include #include @@ -19,32 +23,1225 @@ #include #include #include +#include + +#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 +#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