From 0017ad48c5bfc3a8d52306e03df05760b356bcba Mon Sep 17 00:00:00 2001 From: project-repo Date: Thu, 21 May 2020 23:04:45 +0200 Subject: [PATCH] Add ipc capability --- cagebreak.c | 14 +-- ipc_server.c | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++ ipc_server.h | 44 +++++++++ meson.build | 2 + parse.h | 2 + server.h | 3 + 6 files changed, 307 insertions(+), 6 deletions(-) create mode 100644 ipc_server.c create mode 100644 ipc_server.h diff --git a/cagebreak.c b/cagebreak.c index e9690a1..0f0a539 100644 --- a/cagebreak.c +++ b/cagebreak.c @@ -55,6 +55,7 @@ #include "server.h" #include "view.h" #include "xdg_shell.h" +#include "ipc_server.h" #if CG_HAS_XWAYLAND #include "xwayland.h" #endif @@ -189,10 +190,9 @@ set_configuration(struct cg_server *server, config_file_path); return -1; } - size_t max_line_size = 256; - char line[max_line_size * sizeof(char)]; + char line[MAX_LINE_SIZE * sizeof(char)]; for(unsigned int line_num = 1; - fgets(line, max_line_size, config_file) != NULL; ++line_num) { + fgets(line, MAX_LINE_SIZE, config_file) != NULL; ++line_num) { line[strcspn(line, "\n")] = '\0'; if(*line != '\0' && *line != '#') { if(parse_rc_line(server, line) != 0) { @@ -286,9 +286,9 @@ main(int argc, char *argv[]) { server.modes[1] = strdup("root"); server.modes[2] = strdup("resize"); server.modes[3] = NULL; - if(!server.modes[0] || !server.modes[1] || server.modes[2]) { + if(server.modes[0] == NULL || server.modes[1] == NULL || server.modes[2] == NULL) { wlr_log(WLR_ERROR, "Error allocating default modes"); - goto end; + return 1; } server.nws = 1; @@ -533,7 +533,9 @@ main(int argc, char *argv[]) { } } - /* Place the cursor to the topl left of the output layout. */ + ipc_init(&server); + + /* Place the cursor to the top left of the output layout. */ wlr_cursor_warp(server.seat->cursor, NULL, 0, 0); wl_display_run(server.wl_display); diff --git a/ipc_server.c b/ipc_server.c new file mode 100644 index 0000000..2c415c1 --- /dev/null +++ b/ipc_server.c @@ -0,0 +1,248 @@ +/* + * Cagebreak: A Wayland tiling compositor. + * + * Copyright (C) 2020 The Cagebreak Authors + * Copyright (C) 2018-2020 Jente Hidskes + * + * See the LICENSE file accompanying this file. + */ + +#define _POSIX_C_SOURCE 200112L + +#include "ipc_server.h" +#include "server.h" +#include "parse.h" +#include "message.h" + +#include +#include +#include +#include +#include +#include +#include + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct cg_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 cg_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 cg_server *server) { + struct cg_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"); + 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"); + return -1; + } + + if (listen(ipc->socket, 3) == -1) { + wlr_log(WLR_ERROR, "Unable to listen on IPC socket"); + } + + 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_handle_connection(int fd, uint32_t mask, void *data) { + (void) fd; + struct cg_server *server = data; + struct cg_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 cg_ipc_client *client = malloc(sizeof(struct cg_ipc_client)); + if (!client) { + wlr_log(WLR_ERROR, "Unable to allocate ipc client"); + close(client_fd); + return 0; + } + // +1 for \n and +1 for \0 + client->read_buffer = malloc(sizeof(char)*(MAX_LINE_SIZE+2)); + 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 = NULL; + + 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 cg_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) == -1) { + wlr_log(WLR_ERROR, "Unable to read IPC socket buffer size"); + ipc_client_disconnect(client); + return 0; + } + + int read_size = read_available < MAX_LINE_SIZE+1-client->read_buf_len ? read_available:MAX_LINE_SIZE+1-client->read_buf_len; + // Append to buffer + ssize_t received = recv(client_fd, client->read_buffer+client->read_buf_len, read_size, 0); + if (received == -1) { + wlr_log(WLR_ERROR, "Unable to receive data from IPC client"); + ipc_client_disconnect(client); + return 0; + } + client->read_buf_len+=received; + + ipc_client_handle_command(client); + + return 0; +} + +void ipc_client_disconnect(struct cg_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); + free(client->write_buffer); + free(client->read_buffer); + close(client->fd); + free(client); +} + +void ipc_client_handle_command(struct cg_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; + int 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); + if(parse_rc_line(client->server, line) != 0) { + wlr_log(WLR_ERROR, "Error parsing input from IPC socket."); + } + } + } + offset+=(nl_pos-client->read_buffer)+1; + } + if(client->read_buf_len-offset == MAX_LINE_SIZE+1) { + wlr_log(WLR_ERROR, "Line received was longer that %d, discarding it",MAX_LINE_SIZE); + client->read_buf_len =0; + client->read_discard = 1; + return; + } + if(offsetread_buf_len) { + memmove(client->read_buffer, client->read_buffer+offset, client->read_buf_len-offset); + } + client->read_buf_len-=offset; +} diff --git a/ipc_server.h b/ipc_server.h new file mode 100644 index 0000000..0e5aadd --- /dev/null +++ b/ipc_server.h @@ -0,0 +1,44 @@ +#ifndef CG_IPC_SERVER_H +#define CG_IPC_SERVER_H + +#include "config.h" + +#include +#include +#include + +struct cg_server; + +struct cg_ipc_client { + struct wl_event_source *event_source; + struct wl_event_source *writable_event_source; + struct cg_server *server; + struct wl_list link; + int fd; + uint32_t security_policy; + size_t write_buffer_len; + size_t write_buffer_size; + char *write_buffer; + // The following is for storing data between event_loop calls + uint8_t read_buf_len; + uint8_t read_discard;// 1 if the current line is to be discarded + char *read_buffer; +}; + +struct cg_ipc_handle { + int socket; + struct wl_event_source *event_source; + struct wl_list client_list; + struct wl_listener display_destroy; + struct sockaddr_un *sockaddr; +}; + +int ipc_init(struct cg_server *server); +int ipc_handle_connection(int fd, uint32_t mask, void *data); +int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); +//int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data); +void ipc_client_disconnect(struct cg_ipc_client *client); +void ipc_client_handle_command(struct cg_ipc_client *client); +//bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length); + +#endif diff --git a/meson.build b/meson.build index b9ff4ed..23e6367 100644 --- a/meson.build +++ b/meson.build @@ -121,6 +121,7 @@ conf_data.set_quoted('CG_VERSION', version) cagebreak_main_file = [ 'cagebreak.c', ] cagebreak_source_strings = [ 'idle_inhibit_v1.c', + 'ipc_server.c', 'keybinding.c', 'workspace.c', 'output.c', @@ -137,6 +138,7 @@ cagebreak_source_strings = [ cagebreak_header_strings = [ 'idle_inhibit_v1.h', + 'ipc_server.h', 'keybinding.h', 'workspace.h', 'output.h', diff --git a/parse.h b/parse.h index 622697f..f170b0d 100644 --- a/parse.h +++ b/parse.h @@ -2,6 +2,8 @@ #define PARSE_H +# define MAX_LINE_SIZE 256 + struct cg_server; int diff --git a/server.h b/server.h index 930cb56..24c2fb1 100644 --- a/server.h +++ b/server.h @@ -2,6 +2,7 @@ #define CG_SERVER_H #include "config.h" +#include "ipc_server.h" #include #include @@ -40,6 +41,8 @@ struct cg_server { enum wl_output_transform output_transform; + struct cg_ipc_handle ipc; + bool running; char **modes; uint16_t nws;