NEDM/ipc_server.c

407 lines
11 KiB
C

// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT
#define _DEFAULT_SOURCE
#include "ipc_server.h"
#include "message.h"
#include "parse.h"
#include "server.h"
#include "util.h"
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include <wlr/util/log.h>
static const char ipc_magic[] = {'c', 'g', '-', 'i', 'p', 'c'};
#define IPC_HEADER_SIZE sizeof(ipc_magic)
static void
handle_display_destroy(struct wl_listener *listener,
__attribute__((unused)) void *data) {
struct nedm_ipc_handle *ipc = wl_container_of(listener, ipc, display_destroy);
if(ipc->event_source != NULL) {
wl_event_source_remove(ipc->event_source);
}
close(ipc->socket);
unlink(ipc->sockaddr->sun_path);
struct nedm_ipc_client *tmp_client, *client;
wl_list_for_each_safe(client, tmp_client, &ipc->client_list, link) {
ipc_client_disconnect(client);
}
free(ipc->sockaddr);
wl_list_remove(&ipc->display_destroy.link);
}
int
ipc_init(struct nedm_server *server) {
if(server->enable_socket == false) {
return 0;
}
struct nedm_ipc_handle *ipc = &server->ipc;
ipc->socket = socket(AF_UNIX, SOCK_STREAM, 0);
if(ipc->socket == -1) {
wlr_log(WLR_ERROR, "Unable to create IPC socket");
return -1;
}
if(fcntl(ipc->socket, F_SETFD, FD_CLOEXEC) == -1) {
wlr_log(WLR_ERROR, "Unable to set CLOEXEC on IPC socket");
return -1;
}
if(fcntl(ipc->socket, F_SETFL, O_NONBLOCK) == -1) {
wlr_log(WLR_ERROR, "Unable to set NONBLOCK on IPC socket");
return -1;
}
ipc->sockaddr = malloc(sizeof(struct sockaddr_un));
if(ipc->sockaddr == NULL) {
wlr_log(WLR_ERROR, "Unable to allocate socket address");
return -1;
}
ipc->sockaddr->sun_family = AF_UNIX;
int max_path_size = sizeof(ipc->sockaddr->sun_path);
const char *sockdir = getenv("XDG_RUNTIME_DIR");
if(sockdir == NULL) {
sockdir = "/tmp";
}
if(max_path_size <= snprintf(ipc->sockaddr->sun_path, max_path_size,
"%s/cagebreak-ipc.%i.%i.sock", sockdir,
getuid(), getpid())) {
wlr_log(WLR_ERROR, "Unable to write socket path to "
"ipc->sockaddr->sun_path. Path too long");
free(ipc->sockaddr);
return -1;
}
unlink(ipc->sockaddr->sun_path);
if(bind(ipc->socket, (struct sockaddr *)ipc->sockaddr,
sizeof(*ipc->sockaddr)) == -1) {
wlr_log(WLR_ERROR, "Unable to bind IPC socket");
free(ipc->sockaddr);
return -1;
}
if(listen(ipc->socket, 3) == -1) {
wlr_log(WLR_ERROR, "Unable to listen on IPC socket");
free(ipc->sockaddr);
return -1;
}
chmod(ipc->sockaddr->sun_path, 0700);
setenv("CAGEBREAK_SOCKET", ipc->sockaddr->sun_path, 1);
wl_list_init(&ipc->client_list);
ipc->display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(server->wl_display, &ipc->display_destroy);
ipc->event_source =
wl_event_loop_add_fd(server->event_loop, ipc->socket, WL_EVENT_READABLE,
ipc_handle_connection, server);
return 0;
}
int
ipc_client_handle_writable(__attribute__((unused)) int client_fd, uint32_t mask,
void *data) {
struct nedm_ipc_client *client = data;
if(mask & WL_EVENT_ERROR) {
ipc_client_disconnect(client);
return 0;
}
if(mask & WL_EVENT_HANGUP) {
ipc_client_disconnect(client);
return 0;
}
if(fcntl(client->fd, F_GETFD) == -1) {
return 0;
}
ssize_t written =
write(client->fd, client->write_buffer, client->write_buffer_len);
if(written == -1 && errno == EAGAIN) {
return 0;
} else if(written == -1) {
wlr_log(WLR_ERROR, "Unable to send data from queue to IPC client");
ipc_client_disconnect(client);
return 0;
}
memmove(client->write_buffer, client->write_buffer + written,
client->write_buffer_len - written);
client->write_buffer_len -= written;
if(client->write_buffer_len == 0 && client->writable_event_source) {
wl_event_source_remove(client->writable_event_source);
client->writable_event_source = NULL;
}
return 0;
}
int
ipc_handle_connection(int fd, uint32_t mask, void *data) {
(void)fd;
struct nedm_server *server = data;
struct nedm_ipc_handle *ipc = &server->ipc;
if(mask != WL_EVENT_READABLE) {
wlr_log(WLR_ERROR, "Expected to receive a WL_EVENT_READABLE");
return 0;
}
int client_fd = accept(ipc->socket, NULL, NULL);
if(client_fd == -1) {
wlr_log(WLR_ERROR, "Unable to accept IPC client connection");
return 0;
}
int flags;
if((flags = fcntl(client_fd, F_GETFD)) == -1 ||
fcntl(client_fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
wlr_log(WLR_ERROR, "Unable to set CLOEXEC on IPC client socket");
close(client_fd);
return 0;
}
if((flags = fcntl(client_fd, F_GETFL)) == -1 ||
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
wlr_log(WLR_ERROR, "Unable to set NONBLOCK on IPC client socket");
close(client_fd);
return 0;
}
struct nedm_ipc_client *client = malloc(sizeof(struct nedm_ipc_client));
if(!client) {
wlr_log(WLR_ERROR, "Unable to allocate ipc client");
close(client_fd);
return 0;
}
client->read_buf_cap = 64;
client->read_buffer = calloc(client->read_buf_cap, sizeof(char));
client->read_buf_len = 0;
client->read_discard = 0;
client->server = server;
client->fd = client_fd;
client->event_source =
wl_event_loop_add_fd(server->event_loop, client_fd, WL_EVENT_READABLE,
ipc_client_handle_readable, client);
client->writable_event_source =
wl_event_loop_add_fd(server->event_loop, client_fd, WL_EVENT_WRITABLE,
ipc_client_handle_writable, client);
client->write_buffer_size = 128;
client->write_buffer_len = 0;
client->write_buffer = malloc(client->write_buffer_size);
if(!client->write_buffer) {
wlr_log(WLR_ERROR, "Unable to allocate ipc client write buffer");
close(client_fd);
return 0;
}
wl_list_insert(&ipc->client_list, &client->link);
return 0;
}
int
ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
struct nedm_ipc_client *client = data;
if(mask & WL_EVENT_ERROR) {
wlr_log(WLR_ERROR, "IPC Client socket error, removing client");
ipc_client_disconnect(client);
return 0;
}
if(mask & WL_EVENT_HANGUP) {
ipc_client_disconnect(client);
return 0;
}
int read_available;
if(ioctl(client_fd, FIONREAD, &read_available) < 0) {
wlr_log(WLR_ERROR, "Unable to read IPC socket buffer size");
ipc_client_disconnect(client);
return 0;
}
while(read_available + client->read_buf_len >
(int32_t)client->read_buf_cap - 1) {
client->read_buf_cap *= 2;
client->read_buffer = reallocarray(client->read_buffer,
client->read_buf_cap, sizeof(char));
if(client->read_buffer == NULL) {
wlr_log(WLR_ERROR, "Unable to allocate buffer large enough to hold "
"client read data");
ipc_client_disconnect(client);
return 0;
}
}
// Append to buffer
ssize_t received =
recv(client_fd, client->read_buffer + client->read_buf_len,
read_available, 0);
if(received == -1) {
wlr_log(WLR_ERROR, "Unable to receive data from IPC client");
ipc_client_disconnect(client);
return 0;
}
// Client hung up
if(!received) {
ipc_client_disconnect(client);
return 0;
}
client->read_buf_len += received;
ipc_client_handle_command(client);
return 0;
}
void
ipc_client_disconnect(struct nedm_ipc_client *client) {
if(client == NULL) {
wlr_log(WLR_ERROR,
"Client \"NULL\" was passed to ipc_client_disconnect");
return;
}
shutdown(client->fd, SHUT_RDWR);
wl_event_source_remove(client->event_source);
if(client->writable_event_source) {
wl_event_source_remove(client->writable_event_source);
}
wl_list_remove(&client->link);
if(client->write_buffer != NULL) {
free(client->write_buffer);
}
if(client->read_buffer != NULL) {
free(client->read_buffer);
}
close(client->fd);
free(client);
}
void
ipc_client_handle_command(struct nedm_ipc_client *client) {
if(client == NULL) {
wlr_log(WLR_ERROR,
"Client \"NULL\" was passed to ipc_client_handle_command");
return;
}
client->read_buffer[client->read_buf_len] = '\0';
char *nl_pos;
uint32_t offset = 0;
while((nl_pos = strchr(client->read_buffer + offset, '\n')) != NULL) {
if(client->read_discard) {
client->read_discard = 0;
} else {
*nl_pos = '\0';
char *line = client->read_buffer + offset;
if(*line != '\0' && *line != '#') {
message_clear(client->server->curr_output);
char *errstr;
if(parse_rc_line(client->server, line, &errstr) != 0) {
if(errstr != NULL) {
message_printf(client->server->curr_output, "%s",
errstr);
wlr_log(WLR_ERROR, "%s", errstr);
free(errstr);
}
wlr_log(WLR_ERROR, "Error parsing input from IPC socket");
}
}
}
offset = (nl_pos - client->read_buffer) + 1;
}
if(offset < client->read_buf_len) {
memmove(client->read_buffer, client->read_buffer + offset,
client->read_buf_len - offset);
}
client->read_buf_len -= offset;
}
void
ipc_send_event_client(struct nedm_ipc_client *client, const char *payload,
uint32_t payload_length) {
char data[IPC_HEADER_SIZE];
memcpy(data, ipc_magic, sizeof(ipc_magic));
// +1 for terminating null character
while(client->write_buffer_len + IPC_HEADER_SIZE + payload_length + 1 >=
client->write_buffer_size) {
client->write_buffer_size *= 2;
}
if(client->write_buffer_size > 4e6) { // 4 MB
wlr_log(WLR_ERROR,
"Client write buffer too big (%zu), disconnecting client",
client->write_buffer_size);
ipc_client_disconnect(client);
return;
}
char *new_buffer = realloc(client->write_buffer, client->write_buffer_size);
if(!new_buffer) {
wlr_log(WLR_ERROR, "Unable to reallocate ipc client write buffer");
ipc_client_disconnect(client);
return;
}
client->write_buffer = new_buffer;
memcpy(client->write_buffer + client->write_buffer_len, data,
IPC_HEADER_SIZE);
client->write_buffer_len += IPC_HEADER_SIZE;
memcpy(client->write_buffer + client->write_buffer_len, payload,
payload_length);
client->write_buffer_len += payload_length;
memcpy(client->write_buffer + client->write_buffer_len, "\0", 1);
client->write_buffer_len += 1;
}
void
ipc_send_event(struct nedm_server *server, const char *fmt, ...) {
if(server->enable_socket == false) {
return;
}
if(wl_list_empty(&server->ipc.client_list)) {
return;
}
va_list args;
va_start(args, fmt);
char *msg = malloc_vsprintf_va_list(fmt, args);
if(msg == NULL) {
wlr_log(WLR_ERROR, "Unable to allocate memory for ipc event");
va_end(args);
return;
}
va_end(args);
struct nedm_ipc_client *it, *tmp;
uint32_t len = strlen(msg);
wl_list_for_each_safe(it, tmp, &server->ipc.client_list, link) {
if(it->writable_event_source == NULL) {
it->writable_event_source = wl_event_loop_add_fd(
server->event_loop, it->fd, WL_EVENT_WRITABLE,
ipc_client_handle_writable, it);
}
ipc_send_event_client(it, msg, len);
}
free(msg);
}