implement option to play via Unix domain socket

Sun, 29 Mar 2026 15:17:28 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 29 Mar 2026 15:17:28 +0200
changeset 94
864f59271974
parent 93
9b64437262a2
child 95
2624e7ebb525

implement option to play via Unix domain socket

resolves #816

src/client.c file | annotate | diff | comparison | revisions
src/colors.c file | annotate | diff | comparison | revisions
src/colors.h file | annotate | diff | comparison | revisions
src/game.c file | annotate | diff | comparison | revisions
src/game.h file | annotate | diff | comparison | revisions
src/main.c file | annotate | diff | comparison | revisions
src/network.c file | annotate | diff | comparison | revisions
src/network.h file | annotate | diff | comparison | revisions
src/server.c file | annotate | diff | comparison | revisions
--- a/src/client.c	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/client.c	Sun Mar 29 15:17:28 2026 +0200
@@ -34,8 +34,10 @@
 #include <ncurses.h>
 #include <stdlib.h>
 
-static int client_connect(Server *server, char *host, short port) {
-    if (net_find(server, host, port)) {
+static int client_connect(Server *server, Settings *settings) {
+    if (settings->usedomainsocket
+            ? net_find_sock(server, settings->serverhost)
+            : net_find_tcp(server, settings->serverhost, settings->port)) {
         addstr("Can't find server");
         return 1;
     }
@@ -65,7 +67,7 @@
 int client_run(Settings *settings) {
     Server server;
 
-    if (client_connect(&server, settings->serverhost, settings->port)) {
+    if (client_connect(&server, settings)) {
         net_destroy(&server);
         return 1;
     }
--- a/src/colors.c	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/colors.c	Sun Mar 29 15:17:28 2026 +0200
@@ -30,7 +30,7 @@
 #include "colors.h"
 #include <ncurses.h>
 
-void init_colorpairs() {
+void init_colorpairs(void) {
     init_pair(COL_APP, COLOR_WHITE, COLOR_BLACK);
     init_pair(COL_BW, COLOR_BLACK, COLOR_CYAN);
     init_pair(COL_BB, COLOR_BLACK, COLOR_BLUE);
--- a/src/colors.h	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/colors.h	Sun Mar 29 15:17:28 2026 +0200
@@ -36,7 +36,7 @@
 #define COL_BB 4
 #define COL_WW 5
 
-void init_colorpairs();
+void init_colorpairs(void);
 
 #endif /* COLORS_H */
 
--- a/src/game.c	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/game.c	Sun Mar 29 15:17:28 2026 +0200
@@ -541,8 +541,10 @@
 void game_play(Settings *settings, GameState *gamestate, int opponent) {
     inputy = getmaxy(stdscr) - 6;
     
-    uint8_t mycolor = is_server(settings) ? settings->gameinfo.servercolor :
-        opponent_color(settings->gameinfo.servercolor);
+    uint8_t mycolor = settings->gameinfo.servercolor;
+    if (!settings->ishost) {
+        mycolor = opponent_color(mycolor);
+    }
     
     bool myturn = (gamestate->lastmove ?
         (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK) != mycolor;
--- a/src/game.h	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/game.h	Sun Mar 29 15:17:28 2026 +0200
@@ -39,18 +39,30 @@
 
 typedef struct {
     GameInfo gameinfo;
-    char* serverhost; /* NULL, if we are about to start a server */
+    /**
+     * Server host address.
+     * TCP: server address or \c NULL when we are the server
+     * Domain Socket: the path to the domain socket
+     */
+    char* serverhost;
     char* continuepgn;
     short port;
+    bool ishost;
+    bool usedomainsocket;
     bool singlemachine;
     bool disableflip;
     bool unicode;
 } Settings;
 
-#define is_server(settings) !((settings)->serverhost)
-
 void game_play_singlemachine(Settings *settings);
 
+/**
+ * Plays a network game of chess.
+ *
+ * @param settings the game settings
+ * @param gamestate the current game state
+ * @param opponent file descriptor for the opponent
+ */
 void game_play(Settings *settings, GameState *gamestate, int opponent);
 void game_review(Settings* settings, GameState *gamestate);
 
--- a/src/main.c	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/main.c	Sun Mar 29 15:17:28 2026 +0200
@@ -37,14 +37,16 @@
 #include <time.h>
 #include <getopt.h>
 #include <locale.h>
+#include <sys/stat.h>
 
 int get_settings(int argc, char **argv, Settings *settings) {
     char *valid;
     unsigned long int time, port;
     uint8_t timeunit = 60;
     size_t len;
+    bool port_set = false;
     
-    for (int opt ; (opt = getopt(argc, argv, "a:bc:Fhp:rsS:t:Uv")) != -1 ;) {
+    for (int opt ; (opt = getopt(argc, argv, "a:bc:Fhp:rsS:t:uUv")) != -1 ;) {
         switch (opt) {
         case 'c':
             settings->continuepgn = optarg;
@@ -61,6 +63,14 @@
         case 'F':
             settings->disableflip = true;
             break;
+        case 'u':
+            if (port_set) {
+                fprintf(stderr, "Cannot use Unix domain sockets "
+                        "when a TCP port was specified.\n");
+                return 1;
+            }
+            settings->usedomainsocket = true;
+            break;
         case 'U':
             settings->unicode = false;
             break;
@@ -87,6 +97,15 @@
             }
             break;
         case 'p':
+            if (port_set) {
+                fprintf(stderr, "Cannot use -p twice.\n");
+                return 1;
+            }
+            if (settings->usedomainsocket) {
+                fprintf(stderr, "Cannot specify TCP port "
+                        "when using Unix domain sockets.\n");
+                return 1;
+            }
             port = strtol(optarg, &valid, 10);
             if (port < 1025 || port > 65535 || *valid != '\0') {
                 fprintf(stderr,
@@ -96,6 +115,7 @@
                 return 1;
             } else {
                 settings->port = (short) port;
+                port_set = true;
             }
             break;
         case 'v':
@@ -111,6 +131,7 @@
 "  -c <PGN file> Continue the specified game\n"
 "  -h            This help page\n"
 "  -p            TCP port to use (default: 27015)\n"
+"  -u            Use Unix domain socket instead of TCP\n"
 "  -U            Disables unicode pieces\n"
 "  -v            Print version information and exits\n"
 "\nServer options\n"
@@ -119,13 +140,16 @@
 "  -r            Distribute color randomly\n"
 "  -t <time>     Specifies time limit (default: no limit)\n"
 "\nHot seat\n"
-"  -s            Play a hot seat game (no client/server)\n"
+"  -s            Play a hot seat game (network options are ignored)\n"
 "  -F            Do not automatically flip the board in hot seat games\n"
 "\nNotes\n"
 "The time unit for -a is seconds and for -t minutes by default. To "
 "specify\nseconds for the -t option, use the s suffix.\n"
 "Example: -t 150s\n\n"
-"Use '-' for PGN files to read PGN data from standard input\n"
+"Use '-' for PGN files to read PGN data from standard input\n\n"
+"When playing over Unix domain socket, the HOST denotes the socket path.\n"
+"When the path doest not exist, a game is created. Otherwise, the program\n"
+"joins the existing game. When HOST is omitted, /tmp/chess.sock is used.\n"
             );
             exit(0);
         }
@@ -145,22 +169,50 @@
             return 1;
         }
     }
+
+    if (settings->usedomainsocket) {
+        if (!settings->serverhost) {
+            settings->serverhost = "/tmp/chess.sock";
+        }
+        struct stat st;
+        if (stat(settings->serverhost, &st) == 0) {
+            if (S_ISSOCK(st.st_mode)) {
+                settings->ishost = false;
+            } else {
+                fprintf(stderr, "%s is not a Unix domain socket.\n",
+                    settings->serverhost);
+                return 1;
+            }
+        } else {
+            settings->ishost = true;
+        }
+    } else {
+        settings->ishost = !settings->serverhost;
+    }
     
     return 0;
 }
 
-static Settings default_settings() {
-    Settings settings = {0};
+static Settings settings;
+
+static void init_settings(void) {
+    memset(&settings, 0, sizeof(settings));
     settings.gameinfo.servercolor = WHITE;
     settings.port = 27015;
     settings.unicode = !!setlocale(LC_CTYPE, "C.UTF-8");
-    return settings;
+}
+
+static void cleanup() {
+    endwin();
+    if (settings.usedomainsocket && settings.ishost) {
+        remove(settings.serverhost);
+    }
 }
 
 int main(int argc, char **argv) {
     srand(time(NULL));
-    
-    Settings settings = default_settings();
+
+    init_settings();
     if (get_settings(argc, argv, &settings)) {
         return 1;
     }
@@ -177,14 +229,14 @@
         endwin();
         return EXIT_FAILURE;
     }
-    atexit((void(*)())endwin);
+    atexit(cleanup);
     
     int exitcode;
     if (settings.singlemachine) {
         game_play_singlemachine(&settings);
         exitcode = EXIT_SUCCESS;
     } else {
-        exitcode = is_server(&settings) ?
+        exitcode = settings.ishost ?
             server_run(&settings) : client_run(&settings);
     }
     
--- a/src/network.c	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/network.c	Sun Mar 29 15:17:28 2026 +0200
@@ -29,24 +29,22 @@
 
 #include <stdlib.h>
 #include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/un.h>
 #include "network.h"
 
-#include <stdio.h>
-
-#define new_socket() socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-
-int net_create(Server *server, short port) {
-    server->info = NULL;
-    
-    struct sockaddr_in addr;
-    addr.sin_family = AF_INET;
-    addr.sin_addr.s_addr = INADDR_ANY;
-    addr.sin_port = htons(port);
-
-    server->fd = new_socket();
+int net_create_tcp(Server *server, short port) {
+    memset(server, 0, sizeof(Server));
+    server->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (server->fd > -1) {
         int one = 1;
         setsockopt(server->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
+
+        struct sockaddr_in addr;
+        addr.sin_family = AF_INET;
+        addr.sin_addr.s_addr = INADDR_ANY;
+        addr.sin_port = htons(port);
         if (bind(server->fd, (struct sockaddr*)&addr, sizeof(addr))) {
             server->fd = -1;
             return 1;
@@ -58,21 +56,52 @@
     }
 }
 
-static int getaddrinfo_intrnl(char *host, char *port, struct addrinfo **info) {
+int net_find_tcp(Server *server, char *host, short port) {
+    memset(server, 0, sizeof(Server));
+    server->fd = -1;
+
     struct addrinfo hints = {0};
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_protocol = IPPROTO_TCP;
     hints.ai_family = AF_INET;
-    
-    return getaddrinfo(host, port, &hints, info);
+    char portstr[6];
+    sprintf(portstr, "%hd", port);
+    return getaddrinfo(host, portstr, &hints, &server->info);
 }
 
-int net_find(Server *server, char *host, short port) {
+int net_create_sock(Server *server, char * path) {
+    memset(server, 0, sizeof(Server));
+    server->fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (server->fd > -1) {
+        struct sockaddr_un addr;
+        addr.sun_family = AF_UNIX;
+        addr.sun_family = AF_UNIX;
+        strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+        if (bind(server->fd, (struct sockaddr*)&addr, sizeof(addr))) {
+            server->fd = -1;
+            return 1;
+        } else {
+            return 0;
+        }
+    } else {
+        return 1;
+    }
+}
+
+int net_find_sock(Server *server, char *path) {
     memset(server, 0, sizeof(Server));
     server->fd = -1;
-    char portstr[6];
-    sprintf(portstr, "%hd", port);
-    return getaddrinfo_intrnl(host, portstr, &(server->info));
+
+    server->info = calloc(1, sizeof(struct addrinfo));
+    server->info->ai_family = AF_UNIX;
+    server->info->ai_socktype = SOCK_STREAM;
+    server->info->ai_protocol = 0;
+    struct sockaddr_un *addr = malloc(sizeof(struct sockaddr_un));
+    addr->sun_family = AF_UNIX;
+    strncpy(addr->sun_path, path, sizeof(addr->sun_path) - 1);
+    server->info->ai_addr = (struct sockaddr*) addr;
+    server->info->ai_addrlen = sizeof(struct sockaddr_un);
+    return 0;
 }
 
 int net_listen(Server *server) {
@@ -92,7 +121,11 @@
     
     Client* client = calloc(1, sizeof(Client));
     client->fd = -1;
-    server->fd = new_socket();
+    server->fd = socket(
+        server->info->ai_family,
+        server->info->ai_socktype,
+        server->info->ai_protocol
+    );
     server->client = client;
     
     if (server->fd == -1) {
@@ -102,19 +135,21 @@
     return connect(server->fd, server->info->ai_addr, server->info->ai_addrlen);
 }
 
-int net_destroy(Server *server) {
+void net_destroy(Server *server) {
     if (server->info) {
         freeaddrinfo(server->info);
     }
     if (server->client) {
         shutdown(server->client->fd, SHUT_RDWR);
+        close(server->client->fd);
         free(server->client);
+        server->client = NULL;
     }
     if (server->fd > -1) {
-        return shutdown(server->fd, SHUT_RDWR);
+        shutdown(server->fd, SHUT_RDWR);
+        close(server->fd);
+        server->fd = -1;
     }
-    
-    return 0;
 }
 
 void net_send_code(int socket, uint8_t code) {
--- a/src/network.h	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/network.h	Sun Mar 29 15:17:28 2026 +0200
@@ -64,11 +64,14 @@
     Client *client;
 } Server;
 
-int net_create(Server *server, short port);
-int net_find(Server *server, char* host, short port);
+int net_create_tcp(Server *server, short port);
+int net_find_tcp(Server *server, char* host, short port);
+
+int net_create_sock(Server *server, char *path);
+int net_find_sock(Server *server, char* path);
 
 int net_listen(Server *server);
-int net_destroy(Server *server);
+void net_destroy(Server *server);
 int net_connect(Server *server);
 
 void net_send_code(int socket, uint8_t code);
--- a/src/server.c	Sun Mar 29 13:26:00 2026 +0200
+++ b/src/server.c	Sun Mar 29 15:17:28 2026 +0200
@@ -35,19 +35,38 @@
 #include <errno.h>
 #include <string.h>
 #include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
 
-static int server_open(Server *server, short port) {
+static int server_fd = -1;
+static void interrupt_listen(int sig) {
+    if (server_fd > -1) {
+        // this interrupts
+        close(server_fd);
+    }
+}
+
+static int server_open(Server *server, Settings *settings) {
     printw("\nListening for client...\n");
     refresh();
-    if (net_create(server, port)) {
+    if (settings->usedomainsocket
+            ? net_create_sock(server, settings->serverhost)
+            : net_create_tcp(server, settings->port)) {
         addstr("Server creation failed");
         return 1;
     }
 
+    // allow Ctrl+C to interrupt the listening process
+    server_fd = server->fd;
+    signal(SIGINT, interrupt_listen);
+
     if (net_listen(server)) {
-        addstr("Listening for client failed");
+        addstr("Listening for client failed or interrupted");
         return 1;
     }
+
+    // restore default action
+    signal(SIGINT, SIG_DFL);
     
     return 0;
 }
@@ -97,7 +116,7 @@
         }
     }
     
-    if (server_open(&server, settings->port)) {
+    if (server_open(&server, settings)) {
         net_destroy(&server);
         return 1;
     }

mercurial