about summary refs log tree commit diff homepage
path: root/server.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--server.c331
1 files changed, 152 insertions, 179 deletions
diff --git a/server.c b/server.c
index bf18fce..300322f 100644
--- a/server.c
+++ b/server.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
+/* Copyright (C) 2017, 2021  C. McEnroe <june@causal.agency>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
@@ -14,27 +14,25 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <sys/types.h>
-
+#include <assert.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <poll.h>
 #include <signal.h>
-#include <stdbool.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/event.h>
+#include <sys/file.h>
 #include <sys/mman.h>
 #include <sys/socket.h>
-#include <sys/time.h>
 #include <sys/un.h>
 #include <sysexits.h>
 #include <time.h>
 #include <unistd.h>
 
 #ifdef __FreeBSD__
-#include <libutil.h>
 #include <sys/capsicum.h>
 #endif
 
@@ -53,9 +51,6 @@ static void tilesMap(const char *path) {
 	if (tiles == MAP_FAILED) err(EX_OSERR, "mmap");
 	close(fd);
 
-	error = madvise(tiles, TilesSize, MADV_RANDOM);
-	if (error) err(EX_OSERR, "madvise");
-
 #ifdef MADV_NOCORE
 	error = madvise(tiles, TilesSize, MADV_NOCORE);
 	if (error) err(EX_OSERR, "madvise");
@@ -72,7 +67,7 @@ static struct Tile *tileGet(uint32_t tileX, uint32_t tileY) {
 	return tile;
 }
 
-static struct Tile *tileAccess(uint32_t tileX, uint32_t tileY) {
+static const struct Tile *tileAccess(uint32_t tileX, uint32_t tileY) {
 	struct Tile *tile = tileGet(tileX, tileY);
 	tile->accessTime = time(NULL);
 	tile->accessCount++;
@@ -86,110 +81,92 @@ static struct Tile *tileModify(uint32_t tileX, uint32_t tileY) {
 	return tile;
 }
 
+enum { ClientsCap = 256 };
 static struct Client {
 	int fd;
-
 	uint32_t tileX;
 	uint32_t tileY;
 	uint8_t cellX;
 	uint8_t cellY;
-
-	struct Client *prev;
-	struct Client *next;
-} *clientHead;
+} clients[ClientsCap];
+static size_t clientsLen;
 
 static struct Client *clientAdd(int fd) {
-	struct Client *client = malloc(sizeof(*client));
-	if (!client) err(EX_OSERR, "malloc");
-
+	if (clientsLen == ClientsCap) return NULL;
+	struct Client *client = &clients[clientsLen++];
 	client->fd = fd;
 	client->tileX = TileInitX;
 	client->tileY = TileInitY;
 	client->cellX = CellInitX;
 	client->cellY = CellInitY;
-
-	client->prev = NULL;
-	if (clientHead) {
-		clientHead->prev = client;
-		client->next = clientHead;
-	} else {
-		client->next = NULL;
-	}
-	clientHead = client;
-
 	return client;
 }
 
-static bool clientSend(const struct Client *client, struct ServerMessage msg) {
-	ssize_t size = send(client->fd, &msg, sizeof(msg), 0);
-	if (size < 0) return false;
-
-	if (msg.type == ServerTile) {
-		struct Tile *tile = tileAccess(client->tileX, client->tileY);
-		size = send(client->fd, tile, sizeof(*tile), 0);
-		if (size < 0) return false;
+static int
+clientSend(const struct Client *client, const struct ServerMessage *msg) {
+	ssize_t len = send(client->fd, msg, sizeof(*msg), 0);
+	if (len < 0) return -1;
+	if (msg->type == ServerTile) {
+		const struct Tile *tile = tileAccess(client->tileX, client->tileY);
+		len = send(client->fd, tile, sizeof(*tile), 0);
+		if (len < 0) return -1;
 	}
-
-	return true;
+	return 0;
 }
 
-static void clientCast(const struct Client *origin, struct ServerMessage msg) {
-	for (struct Client *client = clientHead; client; client = client->next) {
-		if (client == origin) continue;
-		if (client->tileX != origin->tileX) continue;
-		if (client->tileY != origin->tileY) continue;
-		clientSend(client, msg);
+static void
+clientCast(const struct Client *source, const struct ServerMessage *msg) {
+	for (size_t i = 0; i < clientsLen; ++i) {
+		if (&clients[i] == source) continue;
+		if (clients[i].tileX != source->tileX) continue;
+		if (clients[i].tileY != source->tileY) continue;
+		clientSend(&clients[i], msg);
 	}
 }
 
-static void clientRemove(struct Client *client) {
-	if (client->prev) client->prev->next = client->next;
-	if (client->next) client->next->prev = client->prev;
-	if (clientHead == client) clientHead = client->next;
-
+static void clientRemove(size_t i) {
+	struct Client client = clients[i];
+	clients[i] = clients[--clientsLen];
+	close(client.fd);
 	struct ServerMessage msg = {
 		.type = ServerCursor,
 		.cursor = {
-			.oldCellX = client->cellX, .oldCellY = client->cellY,
-			.newCellX = CursorNone,    .newCellY = CursorNone,
+			.oldCellX = client.cellX, .oldCellY = client.cellY,
+			.newCellX = CursorNone, .newCellY = CursorNone,
 		},
 	};
-	clientCast(client, msg);
-
-	close(client->fd);
-	free(client);
+	clientCast(&client, &msg);
 }
 
-static bool clientCursors(const struct Client *client) {
+static int clientCursors(const struct Client *client) {
 	struct ServerMessage msg = {
 		.type = ServerCursor,
-		.cursor = { .oldCellX = CursorNone, .oldCellY = CursorNone },
+		.cursor.oldCellX = CursorNone,
+		.cursor.oldCellY = CursorNone,
 	};
-
-	for (struct Client *friend = clientHead; friend; friend = friend->next) {
-		if (friend == client) continue;
-		if (friend->tileX != client->tileX) continue;
-		if (friend->tileY != client->tileY) continue;
-
-		msg.cursor.newCellX = friend->cellX;
-		msg.cursor.newCellY = friend->cellY;
-		if (!clientSend(client, msg)) return false;
+	for (size_t i = 0; i < clientsLen; ++i) {
+		if (&clients[i] == client) continue;
+		if (clients[i].tileX != client->tileX) continue;
+		if (clients[i].tileY != client->tileY) continue;
+		msg.cursor.newCellX = clients[i].cellX;
+		msg.cursor.newCellY = clients[i].cellY;
+		if (clientSend(client, &msg) < 0) return -1;
 	}
-	return true;
+	return 0;
 }
 
-static bool clientUpdate(struct Client *client, const struct Client *old) {
+static int clientUpdate(struct Client *new, const struct Client *old) {
 	struct ServerMessage msg = {
 		.type = ServerMove,
-		.move = { .cellX = client->cellX, .cellY = client->cellY },
+		.move.cellX = new->cellX,
+		.move.cellY = new->cellY,
 	};
-	if (!clientSend(client, msg)) return false;
+	if (clientSend(new, &msg) < 0) return -1;
 
-	if (client->tileX != old->tileX || client->tileY != old->tileY) {
+	if (new->tileX != old->tileX || new->tileY != old->tileY) {
 		msg.type = ServerTile;
-		if (!clientSend(client, msg)) return false;
-
-		if (!clientCursors(client)) return false;
+		if (clientSend(new, &msg) < 0) return -1;
+		if (clientCursors(new) < 0) return -1;
 
 		msg = (struct ServerMessage) {
 			.type = ServerCursor,
@@ -198,32 +175,31 @@ static bool clientUpdate(struct Client *client, const struct Client *old) {
 				.newCellX = CursorNone, .newCellY = CursorNone,
 			},
 		};
-		clientCast(old, msg);
+		clientCast(old, &msg);
 
 		msg = (struct ServerMessage) {
 			.type = ServerCursor,
 			.cursor = {
-				.oldCellX = CursorNone,    .oldCellY = CursorNone,
-				.newCellX = client->cellX, .newCellY = client->cellY,
+				.oldCellX = CursorNone, .oldCellY = CursorNone,
+				.newCellX = new->cellX, .newCellY = new->cellY,
 			},
 		};
-		clientCast(client, msg);
+		clientCast(new, &msg);
 
 	} else {
 		msg = (struct ServerMessage) {
 			.type = ServerCursor,
 			.cursor = {
-				.oldCellX = old->cellX,    .oldCellY = old->cellY,
-				.newCellX = client->cellX, .newCellY = client->cellY,
+				.oldCellX = old->cellX, .oldCellY = old->cellY,
+				.newCellX = new->cellX, .newCellY = new->cellY,
 			},
 		};
-		clientCast(client, msg);
+		clientCast(new, &msg);
 	}
-
-	return true;
+	return 0;
 }
 
-static bool clientMove(struct Client *client, int8_t dx, int8_t dy) {
+static int clientMove(struct Client *client, int8_t dx, int8_t dy) {
 	struct Client old = *client;
 
 	if (dx > CellCols - client->cellX) dx = CellCols - client->cellX;
@@ -251,9 +227,9 @@ static bool clientMove(struct Client *client, int8_t dx, int8_t dy) {
 		client->cellY = CellRows - 1;
 	}
 
-	if (client->tileX == TileCols)  client->tileX = 0;
+	if (client->tileX == TileCols)   client->tileX = 0;
 	if (client->tileX == UINT32_MAX) client->tileX = TileCols - 1;
-	if (client->tileY == TileRows)  client->tileY = 0;
+	if (client->tileY == TileRows)   client->tileY = 0;
 	if (client->tileY == UINT32_MAX) client->tileY = TileRows - 1;
 
 	assert(client->cellX < CellCols);
@@ -264,18 +240,17 @@ static bool clientMove(struct Client *client, int8_t dx, int8_t dy) {
 	return clientUpdate(client, &old);
 }
 
-static bool clientFlip(struct Client *client) {
+static int clientFlip(struct Client *client) {
 	struct Client old = *client;
 	client->tileX = (client->tileX + TileCols / 2) % TileCols;
 	client->tileY = (client->tileY + TileRows / 2) % TileRows;
 	return clientUpdate(client, &old);
 }
 
-static bool clientPut(const struct Client *client, uint8_t color, uint8_t cell) {
+static int clientPut(const struct Client *client, uint8_t color, uint8_t cell) {
 	struct Tile *tile = tileModify(client->tileX, client->tileY);
 	tile->colors[client->cellY][client->cellX] = color;
 	tile->cells[client->cellY][client->cellX] = cell;
-
 	struct ServerMessage msg = {
 		.type = ServerPut,
 		.put = {
@@ -285,12 +260,12 @@ static bool clientPut(const struct Client *client, uint8_t color, uint8_t cell)
 			.cell = cell,
 		},
 	};
-	bool success = clientSend(client, msg);
-	clientCast(client, msg);
-	return success;
+	int error = clientSend(client, &msg);
+	clientCast(client, &msg);
+	return error;
 }
 
-static bool clientMap(const struct Client *client) {
+static int clientMap(const struct Client *client) {
 	int32_t mapY = (int32_t)client->tileY - MapRows / 2;
 	int32_t mapX = (int32_t)client->tileX - MapCols / 2;
 
@@ -354,13 +329,13 @@ static bool clientMap(const struct Client *client) {
 	}
 
 	struct ServerMessage msg = { .type = ServerMap };
-	if (!clientSend(client, msg)) return false;
-	if (0 > send(client->fd, &map, sizeof(map), 0)) return false;
-	return true;
+	if (clientSend(client, &msg) < 0) return -1;
+	if (send(client->fd, &map, sizeof(map), 0) < 0) return -1;
+	return 0;
 }
 
-static bool clientTele(struct Client *client, uint8_t port) {
-	if (port >= ARRAY_LEN(Ports)) return false;
+static int clientTele(struct Client *client, uint8_t port) {
+	if (port >= ARRAY_LEN(Ports)) return -1;
 	struct Client old = *client;
 	client->tileX = Ports[port].tileX;
 	client->tileY = Ports[port].tileY;
@@ -375,8 +350,8 @@ int main(int argc, char *argv[]) {
 	const char *dataPath = DefaultDataPath;
 	const char *sockPath = DefaultSockPath;
 	const char *pidPath = NULL;
-	int opt;
-	while (0 < (opt = getopt(argc, argv, "d:p:s:"))) {
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "d:p:s:"));) {
 		switch (opt) {
 			break; case 'd': dataPath = optarg;
 			break; case 'p': pidPath = optarg;
@@ -385,27 +360,38 @@ int main(int argc, char *argv[]) {
 		}
 	}
 
-#ifdef __FreeBSD__
-	struct pidfh *pid = NULL;
+	int pidFile = -1;
 	if (pidPath) {
-		pid = pidfile_open(pidPath, 0600, NULL);
-		if (!pid) err(EX_CANTCREAT, "%s", pidPath);
+		pidFile = open(pidPath, O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
+		if (pidFile < 0) err(EX_CANTCREAT, "%s", pidPath);
+
+		error = flock(pidFile, LOCK_EX | LOCK_NB);
+		if (error && errno != EWOULDBLOCK) err(EX_IOERR, "%s", pidPath);
+		if (error) errx(EX_CANTCREAT, "%s: file is locked", pidPath);
+
+		error = ftruncate(pidFile, 0);
+		if (error) err(EX_IOERR, "%s", pidPath);
 	}
-#endif
 
 	tilesMap(dataPath);
 
-	int server = socket(PF_LOCAL, SOCK_STREAM, 0);
+	int server = socket(PF_UNIX, SOCK_STREAM, 0);
 	if (server < 0) err(EX_OSERR, "socket");
 
 	error = unlink(sockPath);
-	if (error && errno != ENOENT) err(EX_IOERR, "%s", sockPath);
+	if (error && errno != ENOENT) err(EX_CANTCREAT, "%s", sockPath);
 
-	struct sockaddr_un addr = { .sun_family = AF_LOCAL };
-	strlcpy(addr.sun_path, sockPath, sizeof(addr.sun_path));
+	struct sockaddr_un addr = { .sun_family = AF_UNIX };
+	snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", sockPath);
 	error = bind(server, (struct sockaddr *)&addr, SUN_LEN(&addr));
 	if (error) err(EX_CANTCREAT, "%s", sockPath);
 
+	if (pidPath) {
+		error = daemon(0, 0);
+		if (error) err(EX_OSERR, "daemon");
+		dprintf(pidFile, "%ju", (uintmax_t)getpid());
+	}
+
 #ifdef __FreeBSD__
 	error = cap_enter();
 	if (error) err(EX_OSERR, "cap_enter");
@@ -418,93 +404,80 @@ int main(int argc, char *argv[]) {
 	);
 	error = cap_rights_limit(server, &rights);
 	if (error) err(EX_OSERR, "cap_rights_limit");
-
-	if (pid) {
-		cap_rights_init(&rights, CAP_PWRITE, CAP_FSTAT, CAP_FTRUNCATE);
-		error = cap_rights_limit(pidfile_fileno(pid), &rights);
-		if (error) err(EX_OSERR, "cap_rights_limit");
-
-		// FIXME: daemon(3) can't chdir or open /dev/null in capability mode.
-		error = daemon(0, 0);
-		if (error) err(EX_OSERR, "daemon");
-		pidfile_write(pid);
-	}
 #endif
 
-	error = listen(server, 0);
+	error = listen(server, -1);
 	if (error) err(EX_OSERR, "listen");
 
-	int kq = kqueue();
-	if (kq < 0) err(EX_OSERR, "kqueue");
+	signal(SIGPIPE, SIG_IGN);
+	struct pollfd fds[1 + ClientsCap] = {
+		{ .fd = server, .events = POLLIN },
+	};
+	for (;;) {
+		for (size_t i = 0; i < clientsLen; ++i) {
+			fds[1 + i].fd = clients[i].fd;
+			fds[1 + i].events = POLLIN;
+		}
+		int nfds = poll(fds, 1 + clientsLen, -1);
+		if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll");
+		if (nfds < 0) continue;
+
+		for (size_t i = clientsLen - 1; i < clientsLen; --i) {
+			if (!fds[1 + i].revents) continue;
+			if (fds[1 + i].revents & (POLLHUP | POLLERR)) {
+				clientRemove(i);
+				continue;
+			}
 
-	struct kevent event;
-	EV_SET(&event, server, EVFILT_READ, EV_ADD, 0, 0, 0);
-	int nevents = kevent(kq, &event, 1, NULL, 0, NULL);
-	if (nevents < 0) err(EX_OSERR, "kevent");
+			struct ClientMessage msg;
+			ssize_t len = recv(clients[i].fd, &msg, sizeof(msg), 0);
+			if (len != sizeof(msg)) {
+				clientRemove(i);
+				continue;
+			}
 
-	for (;;) {
-		nevents = kevent(kq, NULL, 0, &event, 1, NULL);
-		if (nevents < 0) err(EX_IOERR, "kevent");
+			switch (msg.type) {
+				break; case ClientMove: {
+					error = clientMove(&clients[i], msg.move.dx, msg.move.dy);
+				}
+				break; case ClientFlip: {
+					error = clientFlip(&clients[i]);
+				}
+				break; case ClientPut: {
+					error = clientPut(&clients[i], msg.put.color, msg.put.cell);
+				}
+				break; case ClientMap: {
+					error = clientMap(&clients[i]);
+				}
+				break; case ClientTele: {
+					error = clientTele(&clients[i], msg.port);
+				}
+				break; default: error = -1;
+			}
+			if (error) clientRemove(i);
+		}
 
-		if (!event.udata) {
+		if (fds[0].revents) {
 			int fd = accept(server, NULL, NULL);
 			if (fd < 0) err(EX_IOERR, "accept");
 			fcntl(fd, F_SETFL, O_NONBLOCK);
 
-			int on = 1;
-			error = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
-			if (error) err(EX_IOERR, "setsockopt");
-
 			int size = 2 * sizeof(struct Tile);
 			error = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
 			if (error) err(EX_IOERR, "setsockopt");
 
 			struct Client *client = clientAdd(fd);
-
-			EV_SET(&event, fd, EVFILT_READ, EV_ADD, 0, 0, client);
-			nevents = kevent(kq, &event, 1, NULL, 0, NULL);
-			if (nevents < 0) err(EX_IOERR, "kevent");
+			if (!client) {
+				close(fd);
+				continue;
+			}
 
 			struct ServerMessage msg = { .type = ServerTile };
-			bool success = clientSend(client, msg)
-				&& clientMove(client, 0, 0)
-				&& clientCursors(client);
-			if (!success) clientRemove(client);
-
-			continue;
-		}
-
-		struct Client *client = (struct Client *)event.udata;
-		if (event.flags & EV_EOF) {
-			clientRemove(client);
-			continue;
-		}
-
-		struct ClientMessage msg;
-		ssize_t size = recv(client->fd, &msg, sizeof(msg), 0);
-		if (size != sizeof(msg)) {
-			clientRemove(client);
-			continue;
-		}
-
-		bool success = false;
-		switch (msg.type) {
-			break; case ClientMove: {
-				success = clientMove(client, msg.move.dx, msg.move.dy);
-			}
-			break; case ClientFlip: {
-				success = clientFlip(client);
-			}
-			break; case ClientPut: {
-				success = clientPut(client, msg.put.color, msg.put.cell);
-			}
-			break; case ClientMap: {
-				success = clientMap(client);
-			}
-			break; case ClientTele: {
-				success = clientTele(client, msg.port);
-			}
+			error = 0
+				|| clientSend(client, &msg)
+				|| clientMove(client, 0, 0)
+				|| clientCursors(client);
+			if (error) clientRemove(clientsLen - 1);
 		}
-		if (!success) clientRemove(client);
 	}
 }