Sun, 29 Mar 2026 15:17:28 +0200
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; }