diff --git a/.gitignore b/.gitignore index ccbf738..16f9b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ libx52test* libx52util/util_char_map.c udev/*.rules x52d* -!daemon/x52d_*.* +!daemon/x52d*.* test-* libx52-*.tar.gz diff --git a/Doxyfile.in b/Doxyfile.in index 92eac6d..0847d5a 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -861,6 +861,7 @@ FILE_PATTERNS = \ libx52io.h \ libx52util.h \ x52_cli.c \ + x52dcomm.h \ *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 5e6780e..9492e39 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -46,6 +46,20 @@ x52d_LDFLAGS += @EVDEV_LIBS@ x52d_LDADD += libx52io.la endif +lib_LTLIBRARIES += libx52dcomm.la + +# Client library to communicate with X52 daemon +libx52dcomm_la_SOURCES = \ + daemon/x52d_comm_client.c +libx52dcomm_la_CFLAGS = \ + -I $(top_srcdir) \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -DLOCALEDIR=\"$(localedir)\" \ + -DLOGDIR=\"$(localstatedir)/log\" \ + -DRUNDIR=\"$(localstatedir)/run\" \ + $(WARN_CFLAGS) +libx52dcomm_la_LDFLAGS = $(WARN_LDFLAGS) + x52dconfdir = @sysconfdir@/x52d x52dconf_DATA = daemon/x52d.conf @@ -63,6 +77,7 @@ EXTRA_DIST += \ daemon/x52d_device.h \ daemon/x52d_io.h \ daemon/x52d_mouse.h \ + daemon/x52dcomm.h \ daemon/x52d.conf if HAVE_SYSTEMD diff --git a/daemon/x52d_comm_client.c b/daemon/x52d_comm_client.c new file mode 100644 index 0000000..2dd6d32 --- /dev/null +++ b/daemon/x52d_comm_client.c @@ -0,0 +1,108 @@ +/* + * Saitek X52 Pro MFD & LED driver - Client communication library + * + * Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "x52dcomm.h" +#include "x52d_const.h" + +int x52d_dial_command(const char *sock_path) +{ + int sock; + socklen_t len; + struct sockaddr_un remote; + int saved_errno; + + if (sock_path == NULL) { + sock_path = X52D_SOCK_COMMAND; + } + + len = strlen(sock_path); + if (len >= sizeof(remote.sun_path)) { + /* Socket path will not fit inside sun_path */ + errno = E2BIG; + return -1; + } + + /* Create a socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + /* Failure creating the socket, abort early */ + return -1; + } + + /* Setup the sockaddr structure */ + memset(&remote, 0, sizeof(remote)); + remote.sun_family = AF_UNIX; + /* We've already verified that sock_path will fit, so we don't need strncpy */ + strcpy(remote.sun_path, sock_path); + len += sizeof(remote.sun_family); + + /* Connect to the socket */ + if (connect(sock, (struct sockaddr *)&remote, len) == -1) { + /* Failure connecting to the socket. Cleanup */ + saved_errno = errno; + /* close may modify errno, so we save it prior to the call */ + close(sock); + sock = -1; + errno = saved_errno; + } + + return sock; +} + +int x52d_send_command(int sock_fd, char *buffer, size_t buflen) +{ + int rc; + + for (;;) { + /* + * Unix sockets should have sufficient capacity to send the full + * datagram in a single message. Assume that is the case. + */ + rc = send(sock_fd, buffer, buflen, 0); + if (rc < 0) { + // Error + if (errno == EINTR) { + // System call interrupted due to signal. Try again + continue; + } else { + // Failed. Return early + return -1; + } + } + + break; + } + + /* Wait till we get a response */ + for (;;) { + rc = recv(sock_fd, buffer, buflen, 0); + if (rc < 0) { + // Error + if (errno == EINTR) { + // System call interrupted due to signal. Try again + continue; + } else { + // Failed. Return early + return -1; + } + } + + break; + } + + return rc; +} diff --git a/daemon/x52d_const.h b/daemon/x52d_const.h index 16c3384..9f2d48c 100644 --- a/daemon/x52d_const.h +++ b/daemon/x52d_const.h @@ -17,6 +17,8 @@ #define X52D_PID_FILE RUNDIR "/" X52D_APP_NAME ".pid" +#define X52D_SOCK_COMMAND RUNDIR "/" X52D_APP_NAME ".cmd" + #include "gettext.h" #define N_(x) gettext_noop(x) #define _(x) gettext(x) diff --git a/daemon/x52dcomm.h b/daemon/x52dcomm.h new file mode 100644 index 0000000..bf40ce4 --- /dev/null +++ b/daemon/x52dcomm.h @@ -0,0 +1,90 @@ +/* + * Saitek X52 Pro MFD & LED driver + * + * Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +/** + * @file x52dcomm.h + * @brief Functions, structures and enumerations for the Saitek X52 MFD & LED + * daemon communication library. + * + * This file contains the type, enum and function prototypes for the Saitek X52 + * daemon communication library. These functions allow a client application to + * communicate with a running X52 daemon, execute commands and retrieve data. + * + * @author Nirenjan Krishnan (nirenjan@nirenjan.org) + */ +#ifndef X52DCOMM_H +#define X52DCOMM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup x52dcomm Daemon communication + * + * These functions are used to communicate with the X52 daemon. + * + * @{ + */ + +/** + * @brief Open a connection to the daemon. + * + * This method opens a socket connection to the daemon command socket. This + * socket allows the client to issue commands and retrieve data. The \p sock_path + * parameter may be NULL, in which case, it will use the default socket path. + * + * The client will need to use the returned descriptor to communicate with the + * daemon using \ref x52d_send_command. Once finished, the client may use the + * \c close(2) method to close the file descriptor. + * + * @param[in] sock_path Path to the daemon command socket. + * + * @returns Non-negative socket file descriptor on success. + * @returns -1 on failure, and set \c errno accordingly. + * + * @exception E2BIG returned if the passed socket path is too big + */ +int x52d_dial_command(const char *sock_path); + +/** + * @brief Send a command to the daemon and retrieve the response. + * + * The client sends the command and parameters as a single NULL terminated + * string, and retrieves the response in the same manner. Depending on the + * result, the return status is either a positive integer or -1, and \c errno + * is set accordingly. + * + * \p buffer should contain sufficient space to accomodate the returned + * response string. + * + * This is a blocking function and will not return until either a response is + * received from the server, or an exception condition occurs. + * + * @param[in] sock_fd Socket descriptor returned from + * \ref x52d_dial_command + * + * @param[inout] buffer Pointer to the string containing the command and + * parameters. This is also used to save the returned + * response. + * + * @param[in] buflen Length of the buffer to hold the returned response. + * + * @returns number of bytes returned from the server + * @returns -1 on an error condition, and \c errno is set accordingly. + */ +int x52d_send_command(int sock_fd, char *buffer, size_t buflen); + +/** @} */ +#ifdef __cplusplus +} +#endif +#endif // !defined X52DCOMM_H +