packages feed

eventlog-socket-0.1.2.0: include/eventlog_socket.h

/// @file eventlog_socket.h
#ifndef EVENGLOG_SOCKET_H
#define EVENGLOG_SOCKET_H

/// @mainpage
/// This is the documentation for the @c eventlog-socket C API. For most uses,
/// the Haskell API is sufficient. However, if you want to start
/// @c eventlog-socket from a C main function, you need the C API.
///
/// If you instrument your application with @c eventlog-socket using the
/// Haskell API, then the RTS is started with the default file writer, which
/// means that the first few events are written to a file instead of the
/// eventlog socket. To avoid this, you can instrument your application using
/// the C API. See `eventlog_socket_wrap_hs_main`.
///
/// If you want to register custom commands via the C API, see
/// `eventlog_socket_control_register_namespace` and
/// `eventlog_socket_control_register_command`.
///
/// The main entry point for the C API is eventlog_socket.h. This file is
/// installed as part of the @c eventlog-socket package and should be available
/// from the C bits of your Haskell package.

#include <Rts.h>
#include <rts/EventLogWriter.h>

#include <stdbool.h>
#include <stdint.h>

/******************************************************************************
 * Error Types
 ******************************************************************************/

/// @brief The status codes for `EventlogSocketStatus`.
///
/// @since 0.1.2.0
typedef enum EventlogSocketStatusCode {
  /// @brief The operation completed successfully.
  EVENTLOG_SOCKET_OK = 0,
  /// @brief The GHC RTS does not support the eventlog.
  EVENTLOG_SOCKET_ERR_RTS_NOSUPPORT,
  /// @brief The GHC RTS failed to start the eventlog writer.
  EVENTLOG_SOCKET_ERR_RTS_FAIL,
  /// @brief Configuration via the environment could not find an address.
  EVENTLOG_SOCKET_ERR_ENV_NOADDR,
  /// @brief Configuration via the environment found a Unix domain socket path
  /// that is too long.
  EVENTLOG_SOCKET_ERR_ENV_TOOLONG,
  /// @brief Configuration via the environment found a TCP/IP port number but no
  /// host name.
  EVENTLOG_SOCKET_ERR_ENV_NOHOST,
  /// @brief Configuration via the environment found a TCP/IP host name but no
  /// port number.
  EVENTLOG_SOCKET_ERR_ENV_NOPORT,
  /// @brief The binary was compiled without support for control commands.
  EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT,
  /// @brief The namespace or command ID is already registered.
  EVENTLOG_SOCKET_ERR_CTL_EXISTS,
  /// @brief An error occurred in `getaddrinfo`; the @c error_code member is set
  /// to indicate the error.
  EVENTLOG_SOCKET_ERR_GAI,
  /// @brief A system error occurred; the @c error_code member is set to
  /// indicate the error.
  EVENTLOG_SOCKET_ERR_SYS,
} EventlogSocketStatusCode;

/// @brief The return status for the functions in the @c eventlog-socket C API.
///
/// @since 0.1.2.0
typedef struct EventlogSocketStatus {
  /// @brief The eventlog socket error code.
  const EventlogSocketStatusCode ess_status_code;
  /// @brief An error code returned by `getaddrinfo` or the system call, if any.
  const int ess_error_code;
} EventlogSocketStatus;

/// @brief Return a string that describes the status.
///
/// @return Upon successful completion, a buffer containing a string that
/// describes the @p status is returned. This buffer is allocated with @c malloc
/// and must be freed with @c free.
///
/// @return On error, @c NULL is returned and @c errno is set to indicate the
/// error.
///
/// @since 0.1.2.0
char *eventlog_socket_strerror(EventlogSocketStatus status);

/******************************************************************************
 * Eventlog Writer
 ******************************************************************************/

/// @brief The address family of the eventlog socket.
///
/// Used as the tag for the tagged union `EventlogSocketAddr`.
///
/// @since 0.1.2.0
typedef enum EventlogSocketTag {
  /// @brief A Unix domain socket address.
  EVENTLOG_SOCKET_UNIX,
  /// @brief A TCP/IP address.
  EVENTLOG_SOCKET_INET,
} EventlogSocketTag;

/// @brief The address for a Unix domain socket.
///
/// @since 0.1.2.0
typedef struct EventlogSocketUnixAddr {
  /// The path to the Unix domain socket.
  char *esa_unix_path;
} EventlogSocketUnixAddr;

/// @brief The address for a TCP/IPv4 socket.
///
/// @since 0.1.2.0
typedef struct EventlogSocketInetAddr {
  /// The host name.
  char *esa_inet_host;
  /// The port number.
  char *esa_inet_port;
} EventlogSocketInetAddr;

/// @brief The options for an eventlog socket.
///
/// @since 0.1.2.0
typedef struct EventlogSocketOpts {
  /// @brief Whether or not to wait for a client to connect.
  bool eso_wait;
  /// @brief The size of the socket send buffer.
  ///
  /// See the documentation for `SO_SNDBUF` in `socket.h`.
  int eso_sndbuf;
  /// @brief The number of seconds to linger.
  int eso_linger;
} EventlogSocketOpts;

/// @brief The address for an eventlog socket.
///
/// @since 0.1.2.0
typedef struct EventlogSocketAddr {
  /// @brief The address family.
  ///
  /// @invariant If `esa_tag` is `EVENTLOG_SOCKET_UNIX`, then `esa_unix_addr` is
  /// set.
  /// @invariant If `esa_tag` is `EVENTLOG_SOCKET_INET`, then `esa_inet_addr` is
  /// set.
  EventlogSocketTag esa_tag;
  union {
    /// @brief The address for a Unix domain socket.
    EventlogSocketUnixAddr esa_unix_addr;
    /// @brief The address for a TCP/IPv4 socket.
    EventlogSocketInetAddr esa_inet_addr;
  };
} EventlogSocketAddr;

/// @brief Free any memory allocated by the members of the `EventlogSocketAddr`
/// value.
///
/// @since 0.1.2.0
void eventlog_socket_addr_free(EventlogSocketAddr *eventlog_socket);

/// @brief Initialise the `EventlogSocketOpts` object with the default options.
///
/// @since 0.1.2.0
void eventlog_socket_opts_init(EventlogSocketOpts *eventlog_socket_opts);

/// @brief Free any memory allocated by the members of the `EventlogSocketOpts`
/// value.
///
/// @since 0.1.2.0
void eventlog_socket_opts_free(EventlogSocketOpts *opts);

/// @brief
/// The name of the environment variable used by `eventlog_socket_from_env`
/// to determine the path to the Unix domain socket.
///
/// @since 0.1.2.0
#define EVENTLOG_SOCKET_ENV_UNIX_PATH "GHC_EVENTLOG_UNIX_PATH"

/// @brief
/// The name of the environment variable used by `eventlog_socket_from_env`
/// to determine the host name for a TCP/IPv4 socket.
///
/// @since 0.1.2.0
#define EVENTLOG_SOCKET_ENV_INET_HOST "GHC_EVENTLOG_INET_HOST"

/// @brief
/// The name of the environment variable used by `eventlog_socket_from_env`
/// to determine the port number for a TCP/IPv4 socket.
///
/// @since 0.1.2.0
#define EVENTLOG_SOCKET_ENV_INET_PORT "GHC_EVENTLOG_INET_PORT"

/// @brief
/// The name of the environment variable used by `eventlog_socket_from_env`
/// to determine whether or not to wait.
///
/// @since 0.1.2.0
#define EVENTLOG_SOCKET_ENV_WAIT "GHC_EVENTLOG_WAIT"

/// @brief Start the eventlog socket writer.
///
/// Use this when you install the eventlog socket writer *after* the GHC RTS has
/// started. This is equivalent to `eventlog_socket_init` followed by
/// evaluating the main closure with `eventlog_socket_wrap_hs_main`.
/// This is primarily intended to be called from Haskell.
/// To install the eventlog socket writer *before* the GHC RTS has started, use
/// `eventlog_socket_init` and `eventlog_socket_wrap_hs_main`.
///
/// @pre The GHC RTS is initialized.
///
/// @pre The argument `eventlog_socket_addr` is nonnull.
///
/// @since 0.1.2.0
EventlogSocketStatus
eventlog_socket_start(const EventlogSocketAddr *eventlog_socket_addr,
                      const EventlogSocketOpts *eventlog_socket_opts);

/// @brief Read the eventlog socket address and options from the environment.
///
/// @par MT-Unsafe
///
/// @return Upon successful completion, the status code `EVENTLOG_SOCKET_OK` is
/// returned  A valid object is written to @p eventlog_socket_addr_out, which
/// must be freed using `eventlog_socket_addr_free`. If @p
/// eventlog_socket_opts_out was nonnull, then a valid object is written to @p
/// eventlog_socket_opts_out, which must be freed using
/// `eventlog_socket_opts_free`.
///
/// @return If no socket address is found, the status code
/// `EVENTLOG_SOCKET_ERR_ENV_NOADDR` is returned and the content of
/// @p eventlog_socket_addr_out and @p eventlog_socket_opts_out are unchanged.
///
/// @return If an invalid address is found, one of
/// `EVENTLOG_SOCKET_ERR_ENV_TOOLONG`, `EVENTLOG_SOCKET_ERR_ENV_NOHOST`, or
/// `EVENTLOG_SOCKET_ERR_ENV_NOPORT` is returned. A valid object is written to
/// @p eventlog_socket_addr_out, which must be freed using
/// `eventlog_socket_addr_free`. If @p eventlog_socket_opts_out was nonnull,
/// then a valid object is written to @p eventlog_socket_opts_out, which must be
/// freed using `eventlog_socket_opts_free`. These objects is for use in error
/// messages only, and should not be passed to `eventlog_socket_init` or
/// `eventlog_socket_start`.
///
/// @return If any other status is returned, the contents of
/// @p eventlog_socket_addr_out and @p eventlog_socket_opts_out are unchanged.
///
/// @since 0.1.2.0
///
/// @par Examples
/// @parblock
/// The following function initialises eventlog socket from a C main function.
/// @code{.c}
/// #include <stdio.h>
/// #include <stdlib.h>
/// #include <Rts.h>
/// #include <eventlog_socket.h>
///
/// // Define the eventlog Unix domain socket path.
/// #define MY_EVENTLOG_SOCKET "/tmp/my_eventlog.socket"
///
/// // Get the closure for the Haskell main.
/// extern StgClosure ZCMain_main_closure;
///
/// int main(int argc, char* argv[]) {
///
///   // Create a GHC RTS configuration object.
///   RtsConfig rts_config = {0};
///   memcpy(&rts_config, &defaultRtsConfig, sizeof(RtsConfig));
///   rts_config.rts_opts_enabled = RtsOptsAll; // Enable all RTS options.
///   rts_config.rts_opts = "-l";               // Enable binary eventlog.
///
///   // Read the socket address and options from the environment.
///   EventlogSocketAddr addr = {0};
///   EventlogSocketOpts opts = {0};
///   EventlogSocketStatus status = eventlog_socket_from_env(&addr, &opts);
///
///   // Handle the return status.
///   switch (status) {
///   case EVENTLOG_SOCKET_FROM_ENV_OK:
///     // Evaluate the wrapped Haskell main closure.
///     return eventlog_socket_wrap_hs_main(argc, argv, rts_config,
///                                         &ZCMain_main_closure, &addr, &opts);
///   case EVENTLOG_SOCKET_ERR_ENV_NOADDR:
///     // Return with an exit failure, skip free.
///     exit(EXIT_FAILURE);
///   case EVENTLOG_SOCKET_ERR_ENV_TOOLONG:
///     fprintf(stderr, "Error: value of %s (%s) is too long\n",
///             EVENTLOG_SOCKET_ENV_UNIX_PATH,
///             eventlog_socket_addr.esa_unix_addr.esa_unix_path);
///     break;
///   case EVENTLOG_SOCKET_ERR_ENV_NOHOST:
///     fprintf(stderr, "Error: no value given for %s\n",
///             EVENTLOG_SOCKET_ENV_INET_HOST);
///     break;
///   case EVENTLOG_SOCKET_ERR_ENV_NOPORT:
///     fprintf(stderr, "Error: no value given for %s\n",
///             EVENTLOG_SOCKET_ENV_INET_PORT);
///     break;
///   default:
///     char *errmsg = eventlog_socket_strerror(status);
///     if (errmsg != NULL) {
///       fprintf(stderr, "Error: %s\n", errmsg);
///       free(errmsg);
///     }
///     break;
///   }
///
///   // Free the memory held by socket address and options.
///   eventlog_socket_addr_free(&eventlog_socket_addr);
///   eventlog_socket_opts_free(&eventlog_socket_opts);
///
///   // Return with an exit failure.
///   exit(EXIT_FAILURE);
/// }
/// @endcode
/// @endparblock
EventlogSocketStatus
eventlog_socket_from_env(EventlogSocketAddr *eventlog_socket_addr_out,
                         EventlogSocketOpts *eventlog_socket_opts_out);

/// @brief Initialise the eventlog socket.
///
/// Use this when you install the eventlog socket writer *before* the GHC RTS
/// has started. This is primarily intended to be called from a C main function.
/// To install the eventlog socket writer *after* the GHC RTS has started, use
/// `eventlog_socket_start`.
///
/// @note You do not need to call this function if you're starting eventlog
/// socket using the Haskell API, via `eventlog_socket_start`, or via
/// `eventlog_socket_wrap_hs_main`.
///
/// @warning `eventlog_socket_init` ignores the @c eso_wait option.
///
/// @return See `EventlogSocketStatus`.
///
/// @since 0.1.2.0
///
/// @par Errors
/// @parblock
/// This function may return the following system errors:
/// `EACCES`, `EADDRINUSE`, `EADDRNOTAVAIL`, `EAFNOSUPPORT`, `EAGAIN`,
/// `EALREADY`, `EBADF`, `EDESTADDRREQ`, `EDOM`, `EFAULT`, `EINPROGRESS`,
/// `EINVAL`, `EIO`, `EISCONN`, `EISDIR`, `ELOOP`, `EMFILE`, `ENAMETOOLONG`,
/// `ENFILE`, `ENOBUFS`, `ENOENT`, `ENOMEM`, `ENOPKG`, `ENOPROTOOPT`, `ENOTDIR`,
/// `ENOTSOCK`, `EOPNOTSUPP`, `EPERM`, `EPROTONOSUPPORT`, or `EROFS`.
///
/// This function may return the following `getaddrinfo` errors:
/// `EAI_ADDRFAMILY`, `EAI_AGAIN`, `EAI_BADFLAGS`, `EAI_FAIL`, `EAI_FAMILY`,
/// `EAI_MEMORY`, `EAI_NODATA`, `EAI_NONAME`, `EAI_SERVICE`, `EAI_SOCKTYPE`, or
/// `EAI_SYSTEM`.
/// @endparblock
EventlogSocketStatus
eventlog_socket_init(const EventlogSocketAddr *eventlog_socket_addr,
                     const EventlogSocketOpts *eventlog_socket_opts);

/// @brief Evaluate the Haskell main closure using the eventlog socket writer.
///
/// Use this when you install the eventlog socket writer *before* the GHC RTS
/// has started. This is primarily intended to be called from a C main function.
/// To install the eventlog socket writer *after* the GHC RTS has started, use
/// `eventlog_socket_start`.
///
/// This function attaches the eventlog-socket writer to the @p rts_config,
/// initializes eventlog-socket and the GHC RTS, and evaluates the Haskell
/// main closure.
///
/// If your needs are more fine-grained, you can use `eventlog_socket_init`,
/// `EventlogSocketWriter`, `eventlog_socket_signal_ghc_rts_ready`, and the
/// GHC RTS API directly. For details, see the code for this function.
///
/// @par Examples
/// @parblock
/// The following function initialises eventlog socket from a C main function.
/// @code{.c}
/// #include <Rts.h>
/// #include <eventlog_socket.h>
///
/// // Define the eventlog Unix domain socket path.
/// #define MY_EVENTLOG_SOCKET "/tmp/my_eventlog.socket"
///
/// // Get the closure for the Haskell main.
/// extern StgClosure ZCMain_main_closure;
///
/// int main(int argc, char* argv[]) {
///
///   // Create a GHC RTS configuration object.
///   RtsConfig rts_config = {0};
///   memcpy(&rts_config, &defaultRtsConfig, sizeof(RtsConfig));
///   rts_config.rts_opts_enabled = RtsOptsAll; // Enable all RTS options.
///   rts_config.rts_opts = "-l";               // Enable binary eventlog.
///
///   // Create the eventlog-socket address
///   const EventlogSocketAddr addr = {
///     .esa_tag = EVENTLOG_SOCKET_UNIX,
///     .esa_unix_addr = {
///       .esa_unix_path = MY_EVENTLOG_SOCKET,
///   }};
///
///   // Create the eventlog-socket options
///   EventlogSocketOpts opts = {0};
///   eventlog_socket_opts_init(&opts);
///   opts->eso_wait = true; // Ask eventlog-socket to wait for a connection.
///
///   // Evaluate the wrapped Haskell main closure.
///   return eventlog_socket_wrap_hs_main(argc, argv, rts_config,
///                                       &ZCMain_main_closure, &addr, &opts);
/// }
/// @endcode
/// @endparblock
void eventlog_socket_wrap_hs_main(
    int argc, char *argv[], RtsConfig rts_config, StgClosure *main_closure,
    const EventlogSocketAddr *eventlog_socket_addr,
    const EventlogSocketOpts *eventlog_socket_opts);

/// The global `EventLogWriter` object for @c eventlog-socket.
///
/// @note You do not need to use this object if you're starting eventlog
/// socket using the Haskell API, via `eventlog_socket_start`, or via
/// `eventlog_socket_wrap_hs_main`.
extern const EventLogWriter EventLogSocketWriter;

/// @brief Signal that the GHC RTS is ready.
///
/// Since control command handlers may call function from the GHC RTS API, no
/// control commands are executed until the GHC RTS is ready.
///
/// @pre The GHC RTS is initialized.
///
/// @note You do not need to call this function if you're starting eventlog
/// socket using the Haskell API, via `eventlog_socket_start`, or via
/// `eventlog_socket_wrap_hs_main`.
///
/// @return Upon successful completion, 0 is returned.
///
/// @return On error, On error, -1 is returned, errno is set to indicate the
/// error.
///
/// @par Errors
/// @parblock
/// `EAGAIN`, `EBUSY`, `EDEADLK`, `EINVAL`, `ENOTRECOVERABLE`, `EOWNERDEAD`, or
/// `EPERM`.
/// @endparblock
///
/// @since 0.1.2.0
EventlogSocketStatus eventlog_socket_signal_ghc_rts_ready(void);

/// @brief Wait for a client to connect to the eventlog socket.
///
/// @pre The eventlog socket was successfully initialised using
/// `eventlog_socket_init` or `eventlog_socket_start`.
///
/// @note If the eventlog socket is initialised with
/// `EventlogSocketOpts.eso_wait` set to `true`, then `eventlog_socket_wait` is
/// called automatically.
///
/// @return Upon successful completion, 0 is returned.
///
/// @return On error, On error, -1 is returned, errno is set to indicate the
/// error.
///
/// @par Errors
/// @parblock
/// `EAGAIN`, `EBUSY`, `EDEADLK`, `EINVAL`, `ENOTRECOVERABLE`, `EOWNERDEAD`, or
/// `EPERM`.
/// @endparblock
///
/// @since 0.1.2.0
EventlogSocketStatus eventlog_socket_wait(void);

/// @brief Read the current status of the worker thread.
///
/// @since 0.1.2.0
EventlogSocketStatus eventlog_socket_worker_status(void);

/// @brief Read the current status of the control thread.
///
/// @since 0.1.2.0
EventlogSocketStatus eventlog_socket_control_status(void);

/******************************************************************************
 * Control Commands
 ******************************************************************************/

/// @brief The version of the eventlog-socket control command protocol
/// implemented by this version of the library.
///
/// @since 0.1.2.0
#define EVENTLOG_SOCKET_CONTROL_PROTOCOL_VERSION 0

/// @brief A control command namespace.
///
/// See `eventlog_socket_control_register_namespace`.
///
/// @since 0.1.2.0
typedef struct EventlogSocketControlNamespace EventlogSocketControlNamespace;

/// @brief A control command ID.
///
/// Individual commands are identified by numbers (0-255).
///
/// @since 0.1.2.0
typedef uint8_t EventlogSocketControlCommandId;

/// @brief A control command handler.
///
/// This function is called when the corresponding control command message is
/// received on the eventlog socket. The `command_data` parameter will contain
/// the pointer provided to `eventlog_socket_control_register_command`.
///
/// @since 0.1.2.0
typedef void EventlogSocketControlCommandHandler(
    const EventlogSocketControlNamespace *const namespace,
    const EventlogSocketControlCommandId command_id, const void *command_data);

/// @brief Get the name of a registered namespace.
///
/// @return Upon successful completion, this function returns an allocated
/// string with the namespace name.
///
/// @return If @p namespace is @c NULL, this function returns @c NULL.
///
/// @return If the binary was compiled without control support, this function
/// returns @c NULL.
///
/// @since 0.1.2.0
const char *
eventlog_socket_control_strnamespace(EventlogSocketControlNamespace *namespace);

/// @brief Register a new namespace.
///
/// @return Upon successful completion, this function registers a new namespace,
/// writes a pointer to it to @p namespace_out, and returns a status with code
/// @c EVENTLOG_SOCKET_OK.
///
/// @return If the given namespace is already registered, this function returns
/// a status with code @c EVENTLOG_SOCKET_ERR_CTL_EXISTS and the object pointer
/// to by @p namespace_out is unchanged.
///
/// @return If @p namespace_out is @c NULL, this function returns a status with
/// code @c EVENTLOG_SOCKET_ERR_SYS and error code @c EINVAL.
///
/// @return If the binary was compiled without control support, this function
/// returns a status with code @c EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT.
///
/// @since 0.1.2.0
EventlogSocketStatus eventlog_socket_control_register_namespace(
    uint8_t namespace_len, const char namespace[namespace_len + 1],
    EventlogSocketControlNamespace **namespace_out);

/// @brief Register a new command.
///
/// @return Upon successful completion, this function registers the command and
/// returns a status with code @c EVENTLOG_SOCKET_OK.
///
/// @return If the given command is already registered, this function returns
/// a status with code @c EVENTLOG_SOCKET_ERR_CTL_EXISTS.
///
/// @return If @p namespace is @c NULL, this function returns a status with
/// code @c EVENTLOG_SOCKET_ERR_SYS and error code @c EINVAL.
///
/// @return If @p namespace is the builtin namespace, this function returns a
/// status with code @c EVENTLOG_SOCKET_ERR_SYS and error code @c EINVAL.
///
/// @return If a system error occurs, this function returns a status with code
/// @c EVENTLOG_SOCKET_ERR_SYS and the error code set to indicate the error.
///
/// @return If the binary was compiled without control support, this function
/// returns a status with code @c EVENTLOG_SOCKET_ERR_CTL_NOSUPPORT.
///
/// @note The @p command_handler should not use the functions from this header.
///
/// @since 0.1.2.0
EventlogSocketStatus eventlog_socket_control_register_command(
    EventlogSocketControlNamespace *namespace,
    EventlogSocketControlCommandId command_id,
    EventlogSocketControlCommandHandler *command_handler,
    const void *command_data);

#endif /* EVENGLOG_SOCKET_H */