Add notify API

This change adds a notification API to libx52dcomm. It also pulls in the
logic to join multiple arguments into a single buffer for later use.
update-lkm
nirenjan 2022-01-23 02:13:28 -08:00
parent 88159d4fc5
commit 619d516ccc
15 changed files with 719 additions and 196 deletions

View File

@ -12,11 +12,14 @@ x52d_SOURCES = \
daemon/x52d_config_dump.c \
daemon/x52d_config.c \
daemon/x52d_device.c \
daemon/x52d_client.c \
daemon/x52d_clock.c \
daemon/x52d_mouse.c \
daemon/x52d_notify.c \
daemon/x52d_led.c \
daemon/x52d_command.c \
daemon/x52d_comm_internal.c
daemon/x52d_comm_internal.c \
daemon/x52d_comm_client.c
x52d_CFLAGS = \
-I $(top_srcdir) \
@ -83,6 +86,7 @@ EXTRA_DIST += \
daemon/daemon.dox \
daemon/protocol.dox \
daemon/x52d.service.in \
daemon/x52d_client.h \
daemon/x52d_clock.h \
daemon/x52d_config.def \
daemon/x52d_config.h \
@ -90,6 +94,7 @@ EXTRA_DIST += \
daemon/x52d_device.h \
daemon/x52d_io.h \
daemon/x52d_mouse.h \
daemon/x52d_notify.h \
daemon/x52d_command.h \
daemon/x52dcomm.h \
daemon/x52dcomm-internal.h \

View File

@ -46,6 +46,7 @@ string "quit", or terminates input by using Ctrl+D.
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "x52d_const.h"
#include "x52dcomm.h"
@ -65,19 +66,13 @@ static int send_command(int sock_fd, int argc, char **argv)
int rc;
char buffer[1024];
int buflen;
int i;
memset(buffer, 0, sizeof(buffer));
buflen = 0;
for (i = 0; i < argc; i++) {
int arglen = strlen(argv[i]) + 1;
if ((size_t)(buflen + arglen) >= sizeof(buffer)) {
buflen = x52d_format_command(argc, (const char **)argv, buffer, sizeof(buffer));
if (buflen < 0) {
if (errno == E2BIG) {
fprintf(stderr, _("Argument length too long\n"));
return -1;
}
memcpy(&buffer[buflen], argv[i], arglen);
buflen += arglen;
return -1;
}
rc = x52d_send_command(sock_fd, buffer, buflen, sizeof(buffer));

View File

@ -0,0 +1,146 @@
/*
* Saitek X52 Pro MFD & LED driver - Client handling
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "pinelog.h"
#include "x52d_client.h"
#include "x52dcomm-internal.h"
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS])
{
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
client_fd[i] = INVALID_CLIENT;
}
}
bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd)
{
int fd;
int i;
fd = accept(sock_fd, NULL, NULL);
if (fd < 0) {
PINELOG_ERROR(_("Error accepting client connection on socket fd %d: %s"),
sock_fd, strerror(errno));
return false;
}
if (x52d_set_socket_nonblocking(fd) < 0) {
PINELOG_ERROR(_("Error marking client fd %d as nonblocking: %s"),
fd, strerror(errno));
goto error;
}
for (i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] == INVALID_CLIENT) {
PINELOG_TRACE("Accepted client %d on socket %d, slot %d", fd, sock_fd, i);
client_fd[i] = fd;
return true;
}
}
/*
* At this point, we've looped through the entirity of client_fd, but
* have not registered an empty slot. We need to close the socket and
* tell the caller that we haven't been able to register the client.
*/
PINELOG_TRACE("Maximum connections reached, closing socket %d", fd);
error:
close(fd);
return false;
}
bool x52d_client_deregister(int client_fd[X52D_MAX_CLIENTS], int fd)
{
bool deregistered = false;
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] == fd) {
client_fd[i] = INVALID_CLIENT;
deregistered = true;
close(fd);
PINELOG_TRACE("Disconnected client %d from socket", fd);
break;
}
}
return deregistered;
}
bool x52d_client_error(int client_fd[X52D_MAX_CLIENTS], int fd)
{
int error;
socklen_t errlen = sizeof(error);
getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
PINELOG_ERROR(_("Error when polling socket: FD %d, error %d, len %lu"),
fd, error, (unsigned long int)errlen);
return x52d_client_deregister(client_fd, fd);
}
int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN], int listen_fd)
{
int pfd_count;
int rc;
memset(pfd, 0, sizeof(*pfd) * MAX_CONN);
pfd_count = 1;
pfd[0].fd = listen_fd;
pfd[0].events = POLLIN | POLLERR;
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] != INVALID_CLIENT) {
pfd[pfd_count].fd = client_fd[i];
pfd[pfd_count].events = POLLIN | POLLERR | POLLHUP;
pfd_count++;
}
}
PINELOG_TRACE("Polling %d file descriptors", pfd_count);
retry_poll:
rc = poll(pfd, pfd_count, -1);
if (rc < 0) {
if (errno == EINTR) {
goto retry_poll;
}
PINELOG_ERROR(_("Error %d when polling %d descriptors: %s"),
errno, pfd_count, strerror(errno));
} else if (rc == 0) {
PINELOG_INFO(_("Timed out when polling"));
}
return rc;
}
void x52d_client_handle(int client_fd[X52D_MAX_CLIENTS], struct pollfd *pfd, int listen_fd, x52d_poll_handler handler)
{
for (int i = 0; i < MAX_CONN; i++) {
if (pfd[i].revents & POLLHUP) {
/* Remote hungup */
x52d_client_deregister(client_fd, pfd[i].fd);
} else if (pfd[i].revents & POLLERR) {
/* Error reading from the socket */
x52d_client_error(client_fd, pfd[i].fd);
} else if (pfd[i].revents & POLLIN) {
if (pfd[i].fd == listen_fd) {
x52d_client_register(client_fd, listen_fd);
} else {
if (handler != NULL) {
handler(pfd[i].fd);
}
}
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Saitek X52 Pro MFD & LED driver - Client handling
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_CLIENT_H
#define X52D_CLIENT_H
#include <stdbool.h>
#include <poll.h>
#include "x52d_const.h"
#define MAX_CONN (X52D_MAX_CLIENTS + 1)
#define INVALID_CLIENT -1
typedef void (*x52d_poll_handler)(int);
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS]);
bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd);
bool x52d_client_deregister(int client_fd[X52D_MAX_CLIENTS], int fd);
bool x52d_client_error(int client_fd[X52D_MAX_CLIENTS], int fd);
int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN], int listen_fd);
void x52d_client_handle(int client_fd[X52D_MAX_CLIENTS], struct pollfd *pfd, int listen_fd, x52d_poll_handler handler);
#endif //!defined X52D_CLIENT_H

View File

@ -18,19 +18,11 @@
#include "x52dcomm.h"
#include "x52dcomm-internal.h"
int x52d_dial_command(const char *sock_path)
static int _setup_socket(struct sockaddr_un *remote, int len)
{
int sock;
int len;
struct sockaddr_un remote;
int saved_errno;
len = x52d_setup_command_sock(sock_path, &remote);
if (len < 0) {
/* Error when setting up sockaddr */
return -1;
}
/* Create a socket */
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
@ -39,7 +31,7 @@ int x52d_dial_command(const char *sock_path)
}
/* Connect to the socket */
if (connect(sock, (struct sockaddr *)&remote, (socklen_t)len) == -1) {
if (connect(sock, (struct sockaddr *)remote, (socklen_t)len) == -1) {
/* Failure connecting to the socket. Cleanup */
saved_errno = errno;
/* close may modify errno, so we save it prior to the call */
@ -51,6 +43,60 @@ int x52d_dial_command(const char *sock_path)
return sock;
}
int x52d_dial_command(const char *sock_path)
{
int len;
struct sockaddr_un remote;
len = x52d_setup_command_sock(sock_path, &remote);
if (len < 0) {
/* Error when setting up sockaddr */
return -1;
}
return _setup_socket(&remote, len);
}
int x52d_dial_notify(const char *sock_path)
{
int len;
struct sockaddr_un remote;
len = x52d_setup_notify_sock(sock_path, &remote);
if (len < 0) {
/* Error when setting up sockaddr */
return -1;
}
return _setup_socket(&remote, len);
}
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen)
{
int msglen;
int i;
if (argc == 0 || argv == NULL || buffer == NULL || buflen < X52D_BUFSZ) {
errno = EINVAL;
return -1;
}
memset(buffer, 0, buflen);
msglen = 0;
for (i = 0; i < argc; i++) {
int arglen = strlen(argv[i]) + 1;
if ((size_t)(msglen + arglen) >= buflen) {
errno = E2BIG;
return -1;
}
memcpy(&buffer[msglen], argv[i], arglen);
msglen += arglen;
}
return msglen;
}
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout)
{
int rc;
@ -94,3 +140,38 @@ int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout)
return rc;
}
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback)
{
int rc;
char buffer[X52D_BUFSZ];
int argc;
char *argv[X52D_BUFSZ];
if (callback == NULL) {
errno = EINVAL;
return -1;
}
/* Wait till we get a response */
for (;;) {
rc = recv(sock_fd, buffer, sizeof(buffer), 0);
if (rc < 0) {
// Error
if (errno == EINTR) {
// System call interrupted due to signal. Try again
continue;
} else {
// Failed. Return early
return -1;
}
}
break;
}
/* Split into individual arguments */
x52d_split_args(&argc, argv, buffer, rc);
return callback(argc, argv);
}

View File

@ -10,6 +10,8 @@
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "x52dcomm-internal.h"
#include "x52d_const.h"
@ -23,7 +25,16 @@ const char * x52d_command_sock_path(const char *sock_path)
return sock_path;
}
int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote)
const char * x52d_notify_sock_path(const char *sock_path)
{
if (sock_path == NULL) {
sock_path = X52D_SOCK_NOTIFY;
}
return sock_path;
}
static int _setup_sockaddr(struct sockaddr_un *remote, const char *sock_path)
{
int len;
if (remote == NULL) {
@ -31,8 +42,6 @@ int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote)
return -1;
}
sock_path = x52d_command_sock_path(sock_path);
len = strlen(sock_path);
if ((size_t)len >= sizeof(remote->sun_path)) {
/* Socket path will not fit inside sun_path */
@ -49,3 +58,54 @@ int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote)
return len;
}
int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote)
{
return _setup_sockaddr(remote, x52d_command_sock_path(sock_path));
}
int x52d_setup_notify_sock(const char *sock_path, struct sockaddr_un *remote)
{
return _setup_sockaddr(remote, x52d_notify_sock_path(sock_path));
}
int x52d_set_socket_nonblocking(int sock_fd)
{
int flags;
/* Mark the socket as non-blocking */
flags = fcntl(sock_fd, F_GETFL);
if (flags < 0) {
goto sock_failure;
}
if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
goto sock_failure;
}
return 0;
sock_failure:
close(sock_fd);
return -1;
}
void x52d_split_args(int *argc, char **argv, char *buffer, int buflen)
{
int i = 0;
while (i < buflen) {
if (buffer[i]) {
argv[*argc] = buffer + i;
(*argc)++;
for (; i < buflen && buffer[i]; i++);
// At this point, buffer[i] = '\0'
// Skip to the next character.
i++;
} else {
// We should never reach here, unless we have two NULs in a row
argv[*argc] = buffer + i;
(*argc)++;
i++;
}
}
}

View File

@ -24,104 +24,15 @@
#include "x52d_const.h"
#include "x52d_command.h"
#include "x52d_config.h"
#include "x52d_client.h"
#include "x52dcomm-internal.h"
#define MAX_CONN (X52D_MAX_CLIENTS + 1)
#define INVALID_CLIENT -1
static int client_fd[X52D_MAX_CLIENTS];
static int active_clients;
static pthread_t command_thr;
static int command_sock_fd;
static const char *command_sock;
static void register_client(int sock_fd)
{
int fd;
if (active_clients >= X52D_MAX_CLIENTS) {
/* Ignore the incoming connection */
return;
}
fd = accept(sock_fd, NULL, NULL);
if (fd < 0) {
PINELOG_ERROR(_("Error accepting client connection on command socket: %s"),
strerror(errno));
return;
}
PINELOG_TRACE("Accepted client %d on command socket", fd);
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] == INVALID_CLIENT) {
client_fd[i] = fd;
active_clients++;
break;
}
}
}
static void deregister_client(int fd)
{
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] == fd) {
client_fd[i] = INVALID_CLIENT;
active_clients--;
close(fd);
PINELOG_TRACE("Disconnected client %d from command socket", fd);
break;
}
}
}
static void client_error(int fd)
{
int error;
socklen_t errlen = sizeof(error);
getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
PINELOG_ERROR(_("Error when polling command socket: FD %d, error %d, len %lu"),
fd, error, (unsigned long int)errlen);
deregister_client(fd);
}
static int poll_clients(int sock_fd, struct pollfd *pfd)
{
int pfd_count;
int rc;
memset(pfd, 0, sizeof(*pfd) * MAX_CONN);
pfd_count = 1;
pfd[0].fd = sock_fd;
pfd[0].events = POLLIN | POLLERR;
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
if (client_fd[i] != INVALID_CLIENT) {
pfd[pfd_count].fd = client_fd[i];
pfd[pfd_count].events = POLLIN | POLLERR | POLLHUP;
pfd_count++;
}
}
PINELOG_TRACE("Polling %d file descriptors", pfd_count);
rc = poll(pfd, pfd_count, -1);
if (rc < 0) {
if (errno != EINTR) {
PINELOG_ERROR(_("Error when polling for command: %s"), strerror(errno));
return -1;
}
} else if (rc == 0) {
PINELOG_INFO(_("Timed out when polling for command"));
}
return rc;
}
#if defined __has_attribute
# if __has_attribute(format)
__attribute((format(printf, 4, 5)))
@ -131,7 +42,7 @@ static void response_formatted(char *buffer, int *buflen, const char *type,
const char *fmt, ...)
{
va_list ap;
char response[1024];
char response[X52D_BUFSZ];
int resplen;
int typelen;
@ -152,7 +63,7 @@ static void response_formatted(char *buffer, int *buflen, const char *type,
static void response_strings(char *buffer, int *buflen, const char *type, int count, ...)
{
va_list ap;
char response[1024];
char response[X52D_BUFSZ];
int resplen;
int arglen;
int i;
@ -351,6 +262,7 @@ static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
[X52D_MOD_LED] = "led",
[X52D_MOD_MOUSE] = "mouse",
[X52D_MOD_COMMAND] = "command",
[X52D_MOD_CLIENT] = "client",
};
// This corresponds to the levels in pinelog
@ -430,24 +342,9 @@ static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
static void command_parser(char *buffer, int *buflen)
{
int argc = 0;
char *argv[1024] = { 0 };
int i = 0;
char *argv[X52D_BUFSZ] = { 0 };
while (i < *buflen) {
if (buffer[i]) {
argv[argc] = buffer + i;
argc++;
for (; i < *buflen && buffer[i]; i++);
// At this point, buffer[i] = '\0'
// Skip to the next character.
i++;
} else {
// We should never reach here, unless we have two NULs in a row
argv[argc] = buffer + i;
argc++;
i++;
}
}
x52d_split_args(&argc, argv, buffer, *buflen);
MATCH(0, "config") {
cmd_config(buffer, buflen, argc, argv);
@ -458,50 +355,41 @@ static void command_parser(char *buffer, int *buflen)
}
}
static void client_handler(int fd)
{
char buffer[X52D_BUFSZ] = { 0 };
int sent;
int rc;
rc = recv(fd, buffer, sizeof(buffer), 0);
if (rc < 0) {
PINELOG_ERROR(_("Error reading from client %d: %s"),
fd, strerror(errno));
return;
}
// Parse and handle command.
command_parser(buffer, &rc);
PINELOG_TRACE("Sending %d bytes in response '%s'", rc, buffer);
sent = send(fd, buffer, rc, 0);
if (sent != rc) {
PINELOG_ERROR(_("Short write to client %d; expected %d bytes, wrote %d bytes"),
fd, rc, sent);
}
}
int x52d_command_loop(int sock_fd)
{
struct pollfd pfd[MAX_CONN];
int rc;
int i;
rc = poll_clients(sock_fd, pfd);
rc = x52d_client_poll(client_fd, pfd, sock_fd);
if (rc <= 0) {
return -1;
}
for (i = 0; i < MAX_CONN; i++) {
if (pfd[i].revents & POLLHUP) {
/* Remote hungup */
deregister_client(pfd[i].fd);
} else if (pfd[i].revents & POLLERR) {
/* Error reading from the socket */
client_error(pfd[i].fd);
} else if (pfd[i].revents & POLLIN) {
if (pfd[i].fd == sock_fd) {
register_client(sock_fd);
} else {
char buffer[1024] = { 0 };
int sent;
rc = recv(pfd[i].fd, buffer, sizeof(buffer), 0);
if (rc < 0) {
PINELOG_ERROR(_("Error reading from client %d: %s"),
pfd[i].fd, strerror(errno));
continue;
}
// Parse and handle command.
command_parser(buffer, &rc);
PINELOG_TRACE("Sending %d bytes in response '%s'", rc, buffer);
sent = send(pfd[i].fd, buffer, rc, 0);
if (sent != rc) {
PINELOG_ERROR(_("Short write to client %d; expected %d bytes, wrote %d bytes"),
pfd[i].fd, rc, sent);
}
}
}
}
x52d_client_handle(client_fd, pfd, sock_fd, client_handler);
return 0;
}
@ -525,11 +413,7 @@ int x52d_command_init(const char *sock_path)
struct sockaddr_un local;
int flags;
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
client_fd[i] = INVALID_CLIENT;
}
active_clients = 0;
x52d_client_init(client_fd);
command_sock = sock_path;
command_sock_fd = -1;

View File

@ -18,6 +18,7 @@
#define X52D_PID_FILE RUNDIR "/" X52D_APP_NAME ".pid"
#define X52D_SOCK_COMMAND RUNDIR "/" X52D_APP_NAME ".cmd"
#define X52D_SOCK_NOTIFY RUNDIR "/" X52D_APP_NAME ".notify"
#include "gettext.h"
#define N_(x) gettext_noop(x)
@ -33,6 +34,8 @@ enum {
X52D_MOD_LED,
X52D_MOD_MOUSE,
X52D_MOD_COMMAND,
X52D_MOD_CLIENT,
X52D_MOD_NOTIFY,
X52D_MOD_MAX
};

View File

@ -23,6 +23,7 @@
#include "x52d_io.h"
#include "x52d_mouse.h"
#include "x52d_command.h"
#include "x52d_notify.h"
#include "x52dcomm-internal.h"
#include "x52dcomm.h"
#include "pinelog.h"
@ -90,7 +91,8 @@ static void usage(int exit_code)
_("Usage: %s [-f] [-v] [-q]\n"
"\t[-l log-file] [-o override]\n"
"\t[-c config-file] [-p pid-file]\n"
"\t[-s command-socket-path]\n"),
"\t[-s command-socket-path]\n"
"\t[-b notify-socket-path]\n"),
X52D_APP_NAME);
exit(exit_code);
}
@ -205,6 +207,7 @@ int main(int argc, char **argv)
char *conf_file = NULL;
const char *pid_file = NULL;
const char *command_sock = NULL;
const char *notify_sock = NULL;
int opt;
int rc;
sigset_t sigblockset;
@ -231,8 +234,9 @@ int main(int argc, char **argv)
* -l path to log file
* -p path to PID file (only used if running in background)
* -s path to command socket
* -b path to notify socket
*/
while ((opt = getopt(argc, argv, "fvql:o:c:p:s:h")) != -1) {
while ((opt = getopt(argc, argv, "fvql:o:c:p:s:b:h")) != -1) {
switch (opt) {
case 'f':
foreground = true;
@ -277,6 +281,10 @@ int main(int argc, char **argv)
command_sock = optarg;
break;
case 'b':
notify_sock = optarg;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
@ -314,6 +322,7 @@ int main(int argc, char **argv)
if (x52d_command_init(command_sock) < 0) {
goto cleanup;
}
x52d_notify_init(notify_sock);
#if defined(HAVE_EVDEV)
x52d_io_init();
x52d_mouse_evdev_init();
@ -355,6 +364,7 @@ cleanup:
x52d_clock_exit();
x52d_dev_exit();
x52d_command_exit();
x52d_notify_exit();
#if defined(HAVE_EVDEV)
x52d_mouse_evdev_exit();
x52d_io_exit();

View File

@ -0,0 +1,220 @@
/*
* Saitek X52 Pro MFD & LED driver - Notification manager
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#define PINELOG_MODULE X52D_MOD_NOTIFY
#include "pinelog.h"
#include "x52d_const.h"
#include "x52d_notify.h"
#include "x52d_client.h"
#include "x52dcomm.h"
#include "x52dcomm-internal.h"
static pthread_t notify_thr;
static pthread_t notify_listen;
static pthread_mutex_t notify_mutex = PTHREAD_MUTEX_INITIALIZER;
static int notify_pipe[2];
static int notify_sock;
static int client_fd[X52D_MAX_CLIENTS];
/* Bind and listen to the notify socket */
static int listen_notify(const char *notify_sock_path)
{
int sock_fd;
int len;
struct sockaddr_un local;
len = x52d_setup_notify_sock(notify_sock_path, &local);
if (len < 0) {
return -1;
}
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd < 0) {
/* Failure creating the socket. Abort early */
PINELOG_ERROR(_("Error creating notification socket: %s"), strerror(errno));
return -1;
}
if (x52d_set_socket_nonblocking(sock_fd) < 0) {
PINELOG_ERROR(_("Error marking notification socket as nonblocking: %s"), strerror(errno));
return -1;
}
/* Cleanup any existing socket */
unlink(local.sun_path);
if (bind(sock_fd, (struct sockaddr *)&local, (socklen_t)len) < 0) {
/* Failure binding socket */
PINELOG_ERROR(_("Error binding to notification socket: %s"), strerror(errno));
goto listen_failure;
}
if (listen(sock_fd, X52D_MAX_CLIENTS) < 0) {
PINELOG_ERROR(_("Error listening on notification socket: %s"), strerror(errno));
goto listen_failure;
}
return sock_fd;
listen_failure:
unlink(local.sun_path);
close(sock_fd);
return -1;
}
static void * x52_notify_thr(void * param)
{
char buffer[X52D_BUFSZ];
uint16_t bufsiz;
int rc;
for (;;) {
read_pipe_size:
rc = read(notify_pipe[0], &bufsiz, sizeof(bufsiz));
if (rc < 0) {
if (errno == EINTR) {
goto read_pipe_size;
} else {
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
errno, strerror(errno));
}
}
if (rc < 0) {
// Error condition, try again
continue;
}
read_pipe_data:
rc = read(notify_pipe[0], buffer, bufsiz);
if (rc < 0) {
if (errno == EINTR) {
goto read_pipe_data;
} else {
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
errno, strerror(errno));
}
}
if (rc < 0) {
continue;
}
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
// Broadcast to every connected client
if (client_fd[i] != INVALID_CLIENT) {
write_client_notification:
rc = write(client_fd[i], buffer, bufsiz);
if (rc < 0 && errno == EINTR) {
goto write_client_notification;
}
}
}
}
return NULL;
}
void x52d_notify_send(int argc, const char **argv)
{
char buffer[X52D_BUFSZ + sizeof(uint16_t)];
uint16_t bufsiz;
uint16_t written;
int rc;
bufsiz = (uint16_t)x52d_format_command(argc, argv, buffer + sizeof(uint16_t), X52D_BUFSZ);
memcpy(buffer, &bufsiz, sizeof(bufsiz));
pthread_mutex_lock(&notify_mutex);
written = 0;
while (written < bufsiz) {
rc = write(notify_pipe[1], buffer + written, bufsiz - written);
if (rc < 0) {
if (errno == EINTR) {
continue;
}
PINELOG_ERROR(_("Error %d writing notification pipe: %s"),
errno, strerror(errno));
} else {
written += rc;
}
}
pthread_mutex_unlock(&notify_mutex);
}
static void client_handler(int fd)
{
char buffer[X52D_BUFSZ] = { 0 };
int rc;
rc = recv(fd, buffer, sizeof(buffer), 0);
PINELOG_TRACE("Received and discarded %d bytes from notification client %d", rc, fd);
}
static void * x52_notify_loop(void * param)
{
struct pollfd pfd[MAX_CONN];
int rc;
for (;;) {
rc = x52d_client_poll(client_fd, pfd, notify_sock);
if (rc <= 0) {
continue;
}
x52d_client_handle(client_fd, pfd, notify_sock, client_handler);
}
return NULL;
}
void x52d_notify_init(const char *notify_sock_path)
{
int rc;
PINELOG_TRACE("Initializing notification manager");
x52d_client_init(client_fd);
PINELOG_TRACE("Creating notifications pipe");
rc = pipe(notify_pipe);
if (rc != 0) {
PINELOG_FATAL(_("Error %d creating notification pipe: %s"),
errno, strerror(errno));
}
PINELOG_TRACE("Opening notification listener socket");
notify_sock = listen_notify(notify_sock_path);
rc = pthread_create(&notify_thr, NULL, x52_notify_thr, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing notify thread: %s"),
rc, strerror(rc));
}
rc = pthread_create(&notify_listen, NULL, x52_notify_loop, NULL);
if (rc != 0) {
PINELOG_FATAL(_("Error %d initializing notify listener: %s"),
rc, strerror(rc));
}
}
void x52d_notify_exit(void)
{
close(notify_pipe[0]);
close(notify_pipe[1]);
close(notify_sock);
pthread_cancel(notify_thr);
pthread_cancel(notify_listen);
}

View File

@ -0,0 +1,17 @@
/*
* Saitek X52 Pro MFD & LED driver - Notification manager
*
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#ifndef X52D_NOTIFY_H
#define X52D_NOTIFY_H
void x52d_notify_init(const char *notify_sock_path);
void x52d_notify_exit(void);
void x52d_notify_send(int argc, const char **argv);
#endif // !defined X52D_NOTIFY_H

View File

@ -13,7 +13,13 @@
#include <sys/socket.h>
#include <sys/un.h>
#define X52D_BUFSZ 1024
const char *x52d_command_sock_path(const char *sock_path);
int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote);
const char *x52d_notify_sock_path(const char *sock_path);
int x52d_setup_notify_sock(const char *sock_path, struct sockaddr_un *remote);
int x52d_set_socket_nonblocking(int sock_fd);
void x52d_split_args(int *argc, char **argv, char *buffer, int buflen);
#endif // !defined X52DCOMM_INTERNAL_H

View File

@ -35,7 +35,7 @@ extern "C" {
*/
/**
* @brief Open a connection to the daemon.
* @brief Open a connection to the daemon command socket.
*
* 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
@ -54,6 +54,46 @@ extern "C" {
*/
int x52d_dial_command(const char *sock_path);
/**
* @brief Open a connection to the daemon notify socket.
*
* This method opens a socket connection to the daemon notify socket. This
* socket allows the client to receive notifications from the daemon. Thej
* \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_recv_notification. 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_notify(const char *sock_path);
/**
* @brief Format a series of command strings into a buffer
*
* The client sends the command and parameters as a series of NUL terminated
* strings. This function concatenates the commands into a single buffer that
* can be passed to \ref x52d_send_command.
*
* \p buffer should be at least 1024 bytes long.
*
* @param[in] argc Number of arguments to fit in the buffer
* @param[in] argv Pointer to an array of arguments.
* @param[out] buffer Buffer to store the formatted command
* @param[in] buflen Length of the buffer
*
* @returns number of bytes in the formatted command
* @returns -1 on an error condition, and \c errno is set accordingly.
*/
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen);
/**
* @brief Send a command to the daemon and retrieve the response.
*
@ -83,6 +123,32 @@ int x52d_dial_command(const char *sock_path);
*/
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout);
/**
* @brief Notification callback function type
*/
typedef int (* x52d_notify_callback_fn)(int argc, char **argv);
/**
* @brief Receive a notification from the daemon
*
* This function blocks until it receives a notification from the daemon. Once
* it receives a notification successfully, it will call the callback function
* with the arguments as string pointers. It will return the return value of
* the callback function, if it was called.
*
* This is a blocking function and will not return until either a notification
* is received from the server, or an exception condition occurs.
*
* @param[in] sock_fd Socket descriptor returned from
* \ref x52d_dial_notify
*
* @param[in] callback Pointer to the callback function
*
* @returns return code of the callback function on success
* @returns -1 on an error condition, and \c errno is set accordingly.
*/
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback);
/** @} */
#ifdef __cplusplus
}

View File

@ -665,51 +665,51 @@ msgstr ""
msgid "Timed out when polling for command"
msgstr ""
#: daemon/x52d_command.c:488
#: daemon/x52d_command.c:473
#, c-format
msgid "Error reading from client %d: %s"
msgstr ""
#: daemon/x52d_command.c:499
#: daemon/x52d_command.c:484
#, c-format
msgid "Short write to client %d; expected %d bytes, wrote %d bytes"
msgstr ""
#: daemon/x52d_command.c:513
#: daemon/x52d_command.c:498
#, c-format
msgid "Error %d during command loop: %s"
msgstr ""
#: daemon/x52d_command.c:545
#: daemon/x52d_command.c:530
#, c-format
msgid "Error creating command socket: %s"
msgstr ""
#: daemon/x52d_command.c:552
#: daemon/x52d_command.c:537
#, c-format
msgid "Error getting command socket flags: %s"
msgstr ""
#: daemon/x52d_command.c:556
#: daemon/x52d_command.c:541
#, c-format
msgid "Error setting command socket flags: %s"
msgstr ""
#: daemon/x52d_command.c:564
#: daemon/x52d_command.c:549
#, c-format
msgid "Error binding to command socket: %s"
msgstr ""
#: daemon/x52d_command.c:569
#: daemon/x52d_command.c:554
#, c-format
msgid "Error listening on command socket: %s"
msgstr ""
#: daemon/x52d_command.c:579
#: daemon/x52d_command.c:564
msgid "Starting command processing thread"
msgstr ""
#: daemon/x52d_command.c:597
#: daemon/x52d_command.c:582
msgid "Shutting down command processing thread"
msgstr ""

View File

@ -716,52 +716,52 @@ msgstr "Erroray enwhay ollingpay orfay ommandcay: %s"
msgid "Timed out when polling for command"
msgstr "Imedtay outay enwhay ollingpay orfay ommandcay"
#: daemon/x52d_command.c:488
#: daemon/x52d_command.c:473
#, c-format
msgid "Error reading from client %d: %s"
msgstr "Erroray eadingray omfray ientclay %d: %s"
#: daemon/x52d_command.c:499
#: daemon/x52d_command.c:484
#, c-format
msgid "Short write to client %d; expected %d bytes, wrote %d bytes"
msgstr ""
"Ortshay itewray otay ientclay %d; expecteday %d ytesbay, otewray %d ytesbay"
#: daemon/x52d_command.c:513
#: daemon/x52d_command.c:498
#, c-format
msgid "Error %d during command loop: %s"
msgstr "Erroray %d uringday ommandcay ooplay: %s"
#: daemon/x52d_command.c:545
#: daemon/x52d_command.c:530
#, c-format
msgid "Error creating command socket: %s"
msgstr "Erroray eatingcray ommandcay ocketsay: %s"
#: daemon/x52d_command.c:552
#: daemon/x52d_command.c:537
#, c-format
msgid "Error getting command socket flags: %s"
msgstr "Erroray ettinggay ommandcay ocketsay agsflay: %s"
#: daemon/x52d_command.c:556
#: daemon/x52d_command.c:541
#, c-format
msgid "Error setting command socket flags: %s"
msgstr "Erroray ettingsay ommandcay ocketsay agsflay: %s"
#: daemon/x52d_command.c:564
#: daemon/x52d_command.c:549
#, c-format
msgid "Error binding to command socket: %s"
msgstr "Erroray indingbay otay ommandcay ocketsay: %s"
#: daemon/x52d_command.c:569
#: daemon/x52d_command.c:554
#, c-format
msgid "Error listening on command socket: %s"
msgstr "Erroray isteninglay onay ommandcay ocketsay: %s"
#: daemon/x52d_command.c:579
#: daemon/x52d_command.c:564
msgid "Starting command processing thread"
msgstr "Artingstay ommandcay ocessingpray eadthray"
#: daemon/x52d_command.c:597
#: daemon/x52d_command.c:582
msgid "Shutting down command processing thread"
msgstr "Uttingshay ownday ommandcay ocessingpray eadthray"