packages feed

eventlog-socket-0.1.3.0: cbits/eventlog_socket/hooks.c

#include <string.h>

#include "./error.h"
#include "./hooks.h"
#include "eventlog_socket.h"

/******************************************************************************
 * Hook Handler Registry
 ******************************************************************************/

/// @brief An entry in the hook registry.
typedef struct EventlogSocketHookHandlerEntry EventlogSocketHookHandlerEntry;

/// @brief An entry in the handler registry for one particular hook.
struct EventlogSocketHookHandlerEntry {
  /// @brief The user-provided hook handler.
  EventlogSocketHookHandler *const hook_handler;
  /// @brief The user-provided data for the hook handler.
  const void *hook_data;
  /// @brief The pointer to the next entry in the hook registry.
  EventlogSocketHookHandlerEntry *next;
};

/// @brief The registry for all hooks.
typedef struct {
  EventlogSocketHookHandlerEntry *on_connect_handlers;
  EventlogSocketHookHandlerEntry *on_disconnect_handlers;
} EventlogSocketHookHandlerRegistry;

/// @brief The global hook handler registry.
static EventlogSocketHookHandlerRegistry g_hook_handler_registry = {
    .on_connect_handlers = NULL,
    .on_disconnect_handlers = NULL,
};

/// @brief The mutex that guards the global hook handler registry.
static pthread_mutex_t g_hook_handler_registry_mutex =
    PTHREAD_MUTEX_INITIALIZER;

/* HIDDEN - see documentation in hooks.h */
HIDDEN EventlogSocketStatus es_hooks_register_hook(
    const EventlogSocketHook hook,
    EventlogSocketHookHandler *const hook_handler, const void *hook_data) {
  // Validate the arguments.
  if (hook_handler == NULL) {
    errno = EINVAL;
    return STATUS_FROM_ERRNO();
  }

  // Acquire the `g_hook_handler_registry_mutex`.
  RETURN_ON_ERROR(
      STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_hook_handler_registry_mutex)));

  // Get a pointer to the *first* entry for the given hook.
  EventlogSocketHookHandlerEntry **entry = NULL;
  switch (hook) {
  case EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING:
    entry = &g_hook_handler_registry.on_connect_handlers;
    break;
  case EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING:
    entry = &g_hook_handler_registry.on_disconnect_handlers;
    break;
  default:
    errno = EINVAL;
    return STATUS_FROM_ERRNO();
  }
  if (entry == NULL) {
    errno = EINVAL;
    return STATUS_FROM_ERRNO();
  }

  // Get a pointer to the *last* entry for the given hook.
  assert(entry != NULL);
  while (*entry != NULL) {
    // TODO: Check if the hook is already registered?
    entry = &(*entry)->next;
    assert(entry != NULL);
  }

  // Allocate memory and write the new hook handler and data.
  assert(*entry == NULL);
  *entry = malloc(sizeof(EventlogSocketHookHandlerEntry));
  if (*entry == NULL) {
    RETURN_ON_ERROR_CLEANUP(
        STATUS_FROM_ERRNO(), // `malloc` sets errno.
        pthread_mutex_unlock(&g_hook_handler_registry_mutex));
  }
  assert(*entry != NULL);
  const EventlogSocketHookHandlerEntry hook_handler_entry =
      (EventlogSocketHookHandlerEntry){
          .hook_handler = hook_handler,
          .hook_data = hook_data,
          .next = NULL,
      };
  memcpy(*entry, &hook_handler_entry, sizeof(EventlogSocketHookHandlerEntry));

  // Release the `g_hook_handler_registry_mutex`.
  RETURN_ON_ERROR(STATUS_FROM_PTHREAD(
      pthread_mutex_unlock(&g_hook_handler_registry_mutex)));

  // Return OK.
  return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);
}

/* HIDDEN - see documentation in hooks.h */
HIDDEN EventlogSocketStatus
es_hooks_handle_hook(const EventlogSocketHook hook) {

  // Acquire the `g_hook_handler_registry_mutex`.
  RETURN_ON_ERROR(
      STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_hook_handler_registry_mutex)));

  // Get a pointer to the *first* entry for the given hook.
  EventlogSocketHookHandlerEntry **entry = NULL;
  switch (hook) {
  case EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING:
    entry = &g_hook_handler_registry.on_connect_handlers;
    break;
  case EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING:
    entry = &g_hook_handler_registry.on_disconnect_handlers;
    break;
  default:
    errno = EINVAL;
    return STATUS_FROM_ERRNO();
  }
  if (entry == NULL) {
    errno = EINVAL;
    return STATUS_FROM_ERRNO();
  }

  // Loop through and call each hook handler.
  assert(entry != NULL);
  while (*entry != NULL) {
    EventlogSocketHookHandler *const hook_handler = (*entry)->hook_handler;
    if (hook_handler != NULL) {
      const void *hook_data = (*entry)->hook_data;
      hook_handler(hook_data);
    }
    entry = &(*entry)->next;
    assert(entry != NULL);
  }

  // Release the `g_hook_handler_registry_mutex`.
  RETURN_ON_ERROR(STATUS_FROM_PTHREAD(
      pthread_mutex_unlock(&g_hook_handler_registry_mutex)));

  // Return OK.
  return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);
}