/* Copyright (C) 2017, 2021 C. McEnroe * * 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 * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "torus.h" static struct Tile *tiles; static void tilesMap(const char *path) { int fd = open(path, O_CREAT | O_RDWR, 0644); if (fd < 0) err(EX_CANTCREAT, "%s", path); int error = ftruncate(fd, TilesSize); if (error) err(EX_IOERR, "%s", path); tiles = mmap(NULL, TilesSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (tiles == MAP_FAILED) err(EX_OSERR, "mmap"); close(fd); #ifdef MADV_NOCORE error = madvise(tiles, TilesSize, MADV_NOCORE); if (error) err(EX_OSERR, "madvise"); #endif } static struct Tile *tileGet(uint32_t tileX, uint32_t tileY) { struct Tile *tile = &tiles[tileY * TileRows + tileX]; if (!tile->createTime) { memset(tile->cells, ' ', CellsSize); memset(tile->colors, ColorWhite, CellsSize); tile->createTime = time(NULL); } return tile; } static struct Tile *tileAccess(uint32_t tileX, uint32_t tileY) { struct Tile *tile = tileGet(tileX, tileY); tile->accessTime = time(NULL); tile->accessCount++; #ifndef NO_DECAY uint8_t y = arc4random_uniform(CellRows); uint8_t x = arc4random_uniform(CellCols); uint8_t b = 1 << arc4random_uniform(8); if (arc4random_uniform(2)) { tile->cells[y][x] &= ~b; } else { tile->colors[y][x] &= ~b; } #endif return tile; } static struct Tile *tileModify(uint32_t tileX, uint32_t tileY) { struct Tile *tile = tileGet(tileX, tileY); tile->modifyTime = time(NULL); tile->modifyCount++; return tile; } static struct Tile *tileSync(struct Tile *tile) { int error = msync(tile, sizeof(*tile), MS_ASYNC); if (error) err(EX_IOERR, "msync"); return tile; } enum { ClientsCap = 256 }; static struct Client { int fd; uint32_t tileX; uint32_t tileY; uint8_t cellX; uint8_t cellY; } clients[ClientsCap]; static size_t clientsLen; static struct Client *clientAdd(int fd) { 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; return client; } 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 = tileSync( tileAccess(client->tileX, client->tileY) ); len = send(client->fd, tile, sizeof(*tile), 0); if (len < 0) return -1; } return 0; } 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(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, }, }; clientCast(&client, &msg); } static int clientCursors(const struct Client *client) { struct ServerMessage msg = { .type = ServerCursor, .cursor.oldCellX = CursorNone, .cursor.oldCellY = CursorNone, }; 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 0; } static int clientUpdate(struct Client *new, const struct Client *old) { struct ServerMessage msg = { .type = ServerMove, .move.cellX = new->cellX, .move.cellY = new->cellY, }; if (clientSend(new, &msg) < 0) return -1; if (new->tileX != old->tileX || new->tileY != old->tileY) { msg.type = ServerTile; if (clientSend(new, &msg) < 0) return -1; if (clientCursors(new) < 0) return -1; msg = (struct ServerMessage) { .type = ServerCursor, .cursor = { .oldCellX = old->cellX, .oldCellY = old->cellY, .newCellX = CursorNone, .newCellY = CursorNone, }, }; clientCast(old, &msg); msg = (struct ServerMessage) { .type = ServerCursor, .cursor = { .oldCellX = CursorNone, .oldCellY = CursorNone, .newCellX = new->cellX, .newCellY = new->cellY, }, }; clientCast(new, &msg); } else { msg = (struct ServerMessage) { .type = ServerCursor, .cursor = { .oldCellX = old->cellX, .oldCellY = old->cellY, .newCellX = new->cellX, .newCellY = new->cellY, }, }; clientCast(new, &msg); } return 0; } 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; if (dx < -client->cellX - 1) dx = -client->cellX - 1; if (dy > CellRows - client->cellY) dy = CellRows - client->cellY; if (dy < -client->cellY - 1) dy = -client->cellY - 1; client->cellX += dx; client->cellY += dy; if (client->cellX == CellCols) { client->tileX++; client->cellX = 0; } if (client->cellX == UINT8_MAX) { client->tileX--; client->cellX = CellCols - 1; } if (client->cellY == CellRows) { client->tileY++; client->cellY = 0; } if (client->cellY == UINT8_MAX) { client->tileY--; client->cellY = CellRows - 1; } 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 == UINT32_MAX) client->tileY = TileRows - 1; assert(client->cellX < CellCols); assert(client->cellY < CellRows); assert(client->tileX < TileCols); assert(client->tileY < TileRows); return clientUpdate(client, &old); } 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 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; tileSync(tile); struct ServerMessage msg = { .type = ServerPut, .put = { .cellX = client->cellX, .cellY = client->cellY, .color = color, .cell = cell, }, }; int error = clientSend(client, &msg); clientCast(client, &msg); return error; } 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; time_t now = time(NULL); struct Map map = { .now = now, .min = { .createTime = now, .modifyTime = now, .accessTime = now, .modifyCount = UINT32_MAX, .accessCount = UINT32_MAX, }, }; for (int32_t y = 0; y < MapRows; ++y) { for (int32_t x = 0; x < MapCols; ++x) { uint32_t tileY = ((mapY + y) % TileRows + TileRows) % TileRows; uint32_t tileX = ((mapX + x) % TileCols + TileCols) % TileCols; struct Meta meta = tileMeta(&tiles[tileY * TileRows + tileX]); if (meta.createTime > 1) { if (meta.createTime < map.min.createTime) { map.min.createTime = meta.createTime; } if (meta.createTime > map.max.createTime) { map.max.createTime = meta.createTime; } } if (meta.modifyTime) { if (meta.modifyTime < map.min.modifyTime) { map.min.modifyTime = meta.modifyTime; } if (meta.modifyTime > map.max.modifyTime) { map.max.modifyTime = meta.modifyTime; } } if (meta.accessTime) { if (meta.accessTime < map.min.accessTime) { map.min.accessTime = meta.accessTime; } if (meta.accessTime > map.max.accessTime) { map.max.accessTime = meta.accessTime; } } if (meta.modifyCount < map.min.modifyCount) { map.min.modifyCount = meta.modifyCount; } if (meta.modifyCount > map.max.modifyCount) { map.max.modifyCount = meta.modifyCount; } if (meta.accessCount < map.min.accessCount) { map.min.accessCount = meta.accessCount; } if (meta.accessCount > map.max.accessCount) { map.max.accessCount = meta.accessCount; } map.meta[y][x] = meta; } } struct ServerMessage msg = { .type = ServerMap }; if (clientSend(client, &msg) < 0) return -1; if (send(client->fd, &map, sizeof(map), 0) < 0) return -1; return 0; } 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; client->cellX = CellInitX; client->cellY = CellInitY; return clientUpdate(client, &old); } int main(int argc, char *argv[]) { int error; const char *dataPath = DefaultDataPath; const char *sockPath = DefaultSockPath; const char *pidPath = NULL; for (int opt; 0 < (opt = getopt(argc, argv, "d:p:s:"));) { switch (opt) { break; case 'd': dataPath = optarg; break; case 'p': pidPath = optarg; break; case 's': sockPath = optarg; break; default: return EX_USAGE; } } int pidFile = -1; if (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); } tilesMap(dataPath); int server = socket(PF_UNIX, SOCK_STREAM, 0); if (server < 0) err(EX_OSERR, "socket"); error = unlink(sockPath); if (error && errno != ENOENT) err(EX_CANTCREAT, "%s", sockPath); 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 __OpenBSD__ error = pledge("stdio unix", NULL); if (error) err(EX_OSERR, "pledge"); #endif error = listen(server, -1); if (error) err(EX_OSERR, "listen"); 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 ClientMessage msg; ssize_t len = recv(clients[i].fd, &msg, sizeof(msg), 0); if (len != sizeof(msg)) { clientRemove(i); continue; } 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 (fds[0].revents) { int fd = accept(server, NULL, NULL); if (fd < 0) err(EX_IOERR, "accept"); fcntl(fd, F_SETFL, O_NONBLOCK); 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); if (!client) { close(fd); continue; } struct ServerMessage msg = { .type = ServerTile }; error = 0 || clientSend(client, &msg) || clientMove(client, 0, 0) || clientCursors(client); if (error) clientRemove(clientsLen - 1); } } }