diff options
Diffstat (limited to 'servers-master.c')
-rw-r--r-- | servers-master.c | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/servers-master.c b/servers-master.c new file mode 100644 index 0000000..f6ebfee --- /dev/null +++ b/servers-master.c @@ -0,0 +1,370 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-master.h" +#include "servers-crtc.h" +#include "servers-gamma.h" +#include "servers-coopgamma.h" +#include "util.h" +#include "communication.h" +#include "state.h" + +#include <sys/socket.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +/** + * All poll(3p) events that are not for writing + */ +#define NON_WR_POLL_EVENTS (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL) + + +/** + * Extract headers from an inbound message and pass + * them on to appropriate message handling function + * + * @param conn The index of the connection + * @param msg The inbound message + * @return 1: The connection as closed + * 0: Successful + * -1: Failure + */ +static int +dispatch_message(size_t conn, struct message *restrict msg) +{ + size_t i; + int r = 0; + const char *header; + const char *value; + const char *command = NULL; + const char *crtc = NULL; + const char *coalesce = NULL; + const char *high_priority = NULL; + const char *low_priority = NULL; + const char *priority = NULL; + const char *class = NULL; + const char *lifespan = NULL; + const char *message_id = NULL; + + for (i = 0; i < msg->header_count; i++) { + value = strstr((header = msg->headers[i]), ": ") + 2; + if (strstr(header, "Command: ") == header) command = value; + else if (strstr(header, "CRTC: ") == header) crtc = value; + else if (strstr(header, "Coalesce: ") == header) coalesce = value; + else if (strstr(header, "High priority: ") == header) high_priority = value; + else if (strstr(header, "Low priority: ") == header) low_priority = value; + else if (strstr(header, "Priority: ") == header) priority = value; + else if (strstr(header, "Class: ") == header) class = value; + else if (strstr(header, "Lifespan: ") == header) lifespan = value; + else if (strstr(header, "Message ID: ") == header) message_id = value; + else if (strstr(header, "Length: ") == header) ;/* Handled transparently */ + else + fprintf(stderr, "%s: ignoring unrecognised header: %s\n", argv0, header); + } + + if (!command) { + fprintf(stderr, "%s: ignoring message without Command header\n", argv0); + + } else if (!message_id) { + fprintf(stderr, "%s: ignoring message without Message ID header\n", argv0); + + } else if (!strcmp(command, "enumerate-crtcs")) { + if (crtc || coalesce || high_priority || low_priority || priority || class || lifespan) + fprintf(stderr, "%s: ignoring superfluous headers in Command: enumerate-crtcs message\n", argv0); + r = handle_enumerate_crtcs(conn, message_id); + + } else if (!strcmp(command, "get-gamma-info")) { + if (coalesce || high_priority || low_priority || priority || class || lifespan) + fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma-info message\n", argv0); + r = handle_get_gamma_info(conn, message_id, crtc); + + } else if (!strcmp(command, "get-gamma")) { + if (priority || class || lifespan) + fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma message\n", argv0); + r = handle_get_gamma(conn, message_id, crtc, coalesce, high_priority, low_priority); + + } else if (!strcmp(command, "set-gamma")) { + if (coalesce || high_priority || low_priority) + fprintf(stderr, "%s: ignoring superfluous headers in Command: set-gamma message\n", argv0); + r = handle_set_gamma(conn, message_id, crtc, priority, class, lifespan); + + } else { + fprintf(stderr, "%s: ignoring unrecognised command: Command: %s\n", argv0, command); + } + + return r; +} + + +/** + * Sets the file descriptor set that includes + * the server socket and all connections + * + * The file descriptor will be ordered as in + * the array `connections`, `socketfd` will + * be last. + * + * @param fds Reference parameter for the array of file descriptors + * @param fdn Output parameter for the number of file descriptors + * @param fds_alloc Reference parameter for the allocation size of `fds`, in elements + * @return Zero on success, -1 on error + */ +static int +update_fdset(struct pollfd **restrict fds, nfds_t *restrict fdn, nfds_t *restrict fds_alloc) +{ + size_t i; + nfds_t j = 0; + void *new; + + if (connections_used + 1 > *fds_alloc) { + new = realloc(*fds, (connections_used + 1) * sizeof(**fds)); + if (!new) + return -1; + *fds = new; + *fds_alloc = connections_used + 1; + } + + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + (*fds)[j].fd = connections[i]; + (*fds)[j].events = NON_WR_POLL_EVENTS; + j++; + } + } + + (*fds)[j].fd = socketfd; + (*fds)[j].events = NON_WR_POLL_EVENTS; + j++; + + *fdn = j; + return 0; +} + + +/** + * Handle event on the server socket + * + * @return 1: New connection accepted + * 0: Successful + * -1: Failure + */ +static int +handle_server(void) +{ + int fd, flags, saved_errno; + void *new; + + fd = accept(socketfd, NULL, NULL); + if (fd < 0) { + switch (errno) { + case ECONNABORTED: + case EINVAL: + terminate = 1; + /* fall through */ + case EINTR: + return 0; + default: + return -1; + } + } + + flags = fcntl(fd, F_GETFL); + if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + goto fail; + + if (connections_ptr == connections_alloc) { + new = realloc(connections, (connections_alloc + 10) * sizeof(*connections)); + if (!new) + goto fail; + connections = new; + connections[connections_ptr] = fd; + + new = realloc(outbound, (connections_alloc + 10) * sizeof(*outbound)); + if (!new) + goto fail; + outbound = new; + ring_initialise(&outbound[connections_ptr]); + + new = realloc(inbound, (connections_alloc + 10) * sizeof(*inbound)); + if (!new) + goto fail; + inbound = new; + connections_alloc += 10; + if (message_initialise(&inbound[connections_ptr])) + goto fail; + } else { + connections[connections_ptr] = fd; + ring_initialise(&outbound[connections_ptr]); + if (message_initialise(&inbound[connections_ptr])) + goto fail; + } + + connections_ptr++; + while (connections_ptr < connections_used && connections[connections_ptr] >= 0) + connections_ptr++; + if (connections_used < connections_ptr) + connections_used = connections_ptr; + + return 1; +fail: + saved_errno = errno; + shutdown(fd, SHUT_RDWR); + close(fd); + errno = saved_errno; + return -1; +} + + +/** + * Handle event on a connection to a client + * + * @param conn The index of the connection + * @return 1: The connection as closed + * 0: Successful + * -1: Failure + */ +static int +handle_connection(size_t conn) +{ + struct message *restrict msg = &inbound[conn]; + int r, fd = connections[conn]; + +again: + errno = 0; + switch (message_read(msg, fd)) { + default: + break; + + case -1: + switch (errno) { + case EINTR: +#if defined(EAGAIN) + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN != EWOULDBLOCK) + case EWOULDBLOCK: +#endif + return 0; + default: + return -1; + case ECONNRESET:; + /* Fall through to `case -2` in outer switch */ + } + + case -2: + shutdown(fd, SHUT_RDWR); + close(fd); + connections[conn] = -1; + if (conn < connections_ptr) + connections_ptr = conn; + while (connections_used > 0 && connections[connections_used - 1] < 0) + connections_used -= 1; + message_destroy(msg); + ring_destroy(&outbound[conn]); + if (connection_closed(fd) < 0) + return -1; + return 1; + } + + if ((r = dispatch_message(conn, msg))) + return r; + + goto again; +} + + +/** + * Disconnect all clients + */ +void +disconnect_all(void) +{ + size_t i; + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + shutdown(connections[i], SHUT_RDWR); + close(connections[i]); + } + } +} + + +/** + * The program's main loop + * + * @return Zero on success, -1 on error + */ +int +main_loop(void) +{ + struct pollfd *fds = NULL; + nfds_t i, fdn = 0, fds_alloc = 0; + int r, update, do_read, do_write, fd; + size_t j; + + if (update_fdset(&fds, &fdn, &fds_alloc) < 0) + goto fail; + + while (!reexec && !terminate) { + if (connection) { + if ((connection == 1 ? disconnect() : reconnect()) < 0) { + connection = 0; + goto fail; + } + connection = 0; + } + + for (j = 0, i = 0; j < connections_used; j++) { + if (connections[j] >= 0) { + fds[i].revents = 0; + if (ring_have_more(outbound + j)) + fds[(size_t)i++ + j].events |= POLLOUT; + else + fds[(size_t)i++ + j].events &= ~POLLOUT; + } + } + fds[i].revents = 0; + + if (poll(fds, fdn, -1) < 0) { + if (errno == EAGAIN) + perror(argv0); + else if (errno != EINTR) + goto fail; + } + + update = 0; + for (i = 0; i < fdn; i++) { + do_read = fds[i].revents & NON_WR_POLL_EVENTS; + do_write = fds[i].revents & POLLOUT; + fd = fds[i].fd; + if (!do_read && !do_write) + continue; + + if (fd == socketfd) { + r = handle_server(); + } else { + for (j = 0; connections[j] != fd; j++); + r = do_read ? handle_connection(j) : 0; + } + + if (r >= 0 && do_write) + r |= continue_send(j); + if (r < 0) + goto fail; + update |= r > 0; + } + if (update && update_fdset(&fds, &fdn, &fds_alloc) < 0) + goto fail; + } + + free(fds); + return 0; + +fail: + free(fds); + return -1; +} |