aboutsummaryrefslogtreecommitdiffstats
path: root/servers-master.c
diff options
context:
space:
mode:
Diffstat (limited to 'servers-master.c')
-rw-r--r--servers-master.c370
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;
+}