// 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 #include #include #include #include #include #include #include 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); }