/** * coopgammad -- Cooperative gamma server * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "server.h" #include "util.h" #include "communication.h" #include "state.h" #include "crtc-server/server.h" #include "gamma-server/server.h" #include "coopgamma-server/server.h" #include #include #include #include #include #include #include #include /** * Sets the file descriptor set that includes * the server socket and all connections * * @param fds The file descritor set * @return The highest set file descritor plus 1 */ GCC_ONLY(__attribute__((nonnull))) static int update_fdset(fd_set* restrict fds) { int fdmax = socketfd; size_t i; FD_ZERO(fds); FD_SET(socketfd, fds); for (i = 0; i < connections_used; i++) if (connections[i] >= 0) { FD_SET(connections[i], fds); if (fdmax < connections[i]) fdmax = connections[i]; } return fdmax + 1; } /** * 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; fd = accept(socketfd, NULL, NULL); if (fd < 0) switch (errno) { case EINTR: return 0; case ECONNABORTED: case EINVAL: terminate = 1; return 0; default: return -1; } flags = fcntl(fd, F_GETFL); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) goto fail; if (connections_ptr == connections_alloc) { void* new; new = realloc(connections, (connections_alloc + 10) * sizeof(*connections)); if (new == NULL) goto fail; connections = new; connections[connections_ptr] = fd; new = realloc(outbound, (connections_alloc + 10) * sizeof(*outbound)); if (new == NULL) goto fail; outbound = new; ring_initialise(outbound + connections_ptr); new = realloc(inbound, (connections_alloc + 10) * sizeof(*inbound)); if (new == NULL) goto fail; inbound = new; connections_alloc += 10; if (message_initialise(inbound + connections_ptr)) goto fail; } connections_ptr++; while (connections_ptr < connections_used) if (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]; 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; size_t i; again: switch (message_read(msg, fd)) { default: break; case -1: switch (errno) { case EINTR: case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif return 0; default: return -1; case ECONNRESET:; /* Fall throught to `case -2` in outer switch */ } case -2: shutdown(fd, SHUT_RDWR); close(fd); connections[conn] = -1; if (conn < connections_ptr) connections_ptr = conn; if (conn == connections_used) connections_used -= 1; message_destroy(msg); if (connection_closed(fd) < 0) return -1; return 1; } for (i = 0; i < msg->header_count; i++) { char* header = msg->headers[i]; if (strstr(header, "Command: ") == header) command = strstr(header, ": ") + 2; else if (strstr(header, "CRTC: ") == header) crtc = strstr(header, ": ") + 2; else if (strstr(header, "Coalesce: ") == header) coalesce = strstr(header, ": ") + 2; else if (strstr(header, "High priority: ") == header) high_priority = strstr(header, ": ") + 2; else if (strstr(header, "Low priority: ") == header) low_priority = strstr(header, ": ") + 2; else if (strstr(header, "Priority: ") == header) priority = strstr(header, ": ") + 2; else if (strstr(header, "Class: ") == header) class = strstr(header, ": ") + 2; else if (strstr(header, "Lifespan: ") == header) lifespan = strstr(header, ": ") + 2; else if (strstr(header, "Message ID: ") == header) message_id = strstr(header, ": ") + 2; else fprintf(stderr, "%s: ignoring unrecognised header: %s\n", argv0, header); } r = 0; if (command == NULL) fprintf(stderr, "%s: ignoring message without Command header\n", argv0); else if (message_id == NULL) 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); if (r) 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) { fd_set fds_orig, fds_rd, fds_wr, fds_ex; int i, r, update, fdn = update_fdset(&fds_orig); size_t j; while (!reexec && !terminate) { if (connection) { connection = 0; if ((connection == 1 ? disconnect() : reconnect()) < 0) return -1; } memcpy(&fds_rd, &fds_orig, sizeof(fd_set)); memcpy(&fds_ex, &fds_orig, sizeof(fd_set)); FD_ZERO(&fds_wr); for (j = 0; j < connections_used; j++) if ((connections[j] >= 0) && ring_have_more(outbound + j)) FD_SET(connections[j], &fds_wr); if (select(fdn, &fds_rd, &fds_wr, &fds_ex, NULL) < 0) { if (errno == EINTR) continue; return -1; } update = 0; for (i = 0; i < fdn; i++) { int do_read = FD_ISSET(i, &fds_rd) || FD_ISSET(i, &fds_ex); int do_write = FD_ISSET(i, &fds_wr); if (do_read || do_write) { if (i == socketfd) r = handle_server(); else { for (j = 0;; j++) if (connections[j] == i) break; r = do_read ? handle_connection(j) : 0; } switch (r) { case 0: break; case 1: update = 1; break; default: return -1; } if (do_write) switch (continue_send(j)) { case 0: break; case 1: update = 1; break; default: return -1; } } } if (update) update_fdset(&fds_orig); } return 0; }