ulthar.xyz > Repos

leibowitz

Experimental Common Lisp object storage abstraction for Unix file systems
About Files Commits git clone https://ulthar.xyz/repos/leibowitz/leibowitz.git

leibowitz/client/leibowitz-client.c

Download raw file: client/leibowitz-client.c

/* Main function and core logic leibowitz-client */

/* Required to use getaddrinfo(3) API with -std=c11 */
#define _POSIX_C_SOURCE 200112L

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ctype.h>

#include "asprintf.h"
#include "utils.h"

/* Return the offset to the first non-space character in buf. */
size_t
chomp(const char *const buf)
{
	size_t i;
	for (i = 0; isspace(buf[i]) && buf[i] != '\0'; i++);
	return i;
}

/* Given buf as a pointer to the first char in an atom, return the
 * offset to the final char of the atom. */
size_t
chomp_atom(const char *const buf)
{
	size_t i;
	/* FIXME: handle quoted forms! */
	for (i = 0; !isspace(buf[i])
		     && buf[i] != '\0'
		     && buf[i] != '('
		     && buf[i] != ')';
	     i++);
	return i;
}

/* Given buf as a pointer to the beginning " of a string, return the
 * offset to the terminating " */
size_t
chomp_string(const char *const buf)
{
	size_t i = 1; /* Skip initial " */
	while (buf[i] != '"' && buf[i] != '\0')
		i += (buf[i] == '\\' && buf[i + 1] == '"') ? 2 : 1;
	if (buf[i] == '\0') {
		WARN("parser error: unterminated string\n");
		exit(1);
	}
	return i;
}

void
slynk_parse_message(char *const _msg)
{
	char msg[] = "(:new-features (:slynk :plump-utf-32 :osicat-fd-streams :cl-who :hunchentoot :sbcl-debug-print-variable-alist :split-sequence :flexi-streams :cl-ppcre :cl-fad :bordeaux-threads :global-vars :chunga cffi-features:flat-namespace cffi-features:x86-64 cffi-features:unix :cffi cffi-sys::flat-namespace alexandria::sequence-emptyp :thread-support :quicklisp :asdf3.3 :asdf3.2 :asdf3.1 :asdf3 :asdf2 :asdf :os-unix :non-base-chars-exist-p :asdf-unicode :arena-allocator :x86-64 :gencgc :64-bit :ansi-cl :common-lisp :elf :ieee-floating-point :linux :little-endian :package-local-nicknames :sb-ldb :sb-package-locks :sb-thread \"An interleaved string!\" :sb-unicode :sbcl :unix \"Another string!\")) \"parse error";
	char *cursor = msg + (uintptr_t)chomp(msg);
	while (*cursor != '\0') {
		/* hurr durr label followed by declaration is a c23
		 * extension */
		size_t offset = 0;
		switch (*cursor) {
		case '(':
			puts("{");
			break;
		case ')':
			puts("}");
			break;
		case ' ':
			cursor += (uintptr_t)chomp(cursor);
			puts("");
			goto continue_no_increment;
		case '"':
			if ((offset = chomp_string(cursor)) == '\0')
				goto continue_no_increment;
			printf("``%.*s''\n", (int)--offset, ++cursor);
			cursor += (uintptr_t)offset;
			break;
		/* case '.': */
		default:
			offset = chomp_atom(cursor);
			for (size_t i = 0; i < offset; i++)
				putchar(cursor[i]);
			cursor += (uintptr_t)offset;
			goto continue_no_increment;
		}
		++cursor;
	continue_no_increment:
		/* drrrrrr label at the end of a compound statement is
		 * a c23 extension */
		continue;
	}
	/* free(msg); */
}

int
slynk_connect(const char *host, const char *port)
{
	int sock, rc;
	struct addrinfo *servinfo, hints = {
		.ai_family = AF_UNSPEC,	   /* IP v agnostic */
		.ai_socktype = SOCK_STREAM /* TCP */
	};
	if ((rc = getaddrinfo(host, port, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
		return -1;
	}
	if (servinfo == NULL) {
		WARN("getaddrinfo didn't return anything\n");
		return -1;
	}
	for (struct addrinfo *i = servinfo; i != NULL; i = i->ai_next) {
		if ((sock = socket(i->ai_family, i->ai_socktype, i->ai_protocol)) == -1) {
			perror("socket");
			continue;
		}
		if (connect(sock, i->ai_addr, i->ai_addrlen) == -1) {
			close(sock);
			perror("connect");
			continue;
		}
		break;
	}
	freeaddrinfo(servinfo);
	return sock;
}

void
slynk_send(const int sock, const char *msg)
{
	char *raw;
	asprintf(&raw, "%06x%s", (int)strlen(msg), msg);
	INFO("Sending %s\n", raw);
	if (send(sock, raw, strlen(raw), 0) == -1) {
		perror("send");
		exit(1);
	}
	free(raw);
}

char *
slynk_recv(const int sock)
{
	size_t bytes, hdr_len = 6, body_len = 0;
	char hdr[hdr_len + 1], *body = NULL;
	if ((bytes = recv(sock, hdr, hdr_len, 0)) == (size_t)-1) {
		perror("recv");
		exit(1);
	}
	if (bytes != hdr_len)
		WARN("Received invalid header of length %li \"%s\", expected %li bytes\n",
		     bytes, hdr, hdr_len);
	body_len = xstrtol(hdr, NULL, 16, "Header is not a valid hex number");
	body = (char *)xcalloc(body_len + 1, sizeof(char), NULL);
	if ((bytes = recv(sock, body, body_len, 0)) == (size_t)-1) {
		perror("recv");
		free(body);
		exit(1);
	}
	if (bytes != body_len)
		WARN("Received invalid body of %li bytes, expected %li\n", bytes,
		     body_len);
	return body;
}

void
slynk_disconnect(int sock)
{
	INFO("Disconnecting...\n");
	slynk_send(sock, "(:emacs-rex (cl:format T \"Goodbye, cruel world~%\") nil t 1)");
	slynk_parse_message(slynk_recv(sock));
	slynk_send(sock, "(:emacs-channel-send 1 (:teardown))");
	slynk_parse_message(slynk_recv(sock));
}

/*
 * (let ((msg "(:emacs-rex (cl:format T \"Hello, world!~%\") nil t 1)"))
 *   (format T "~6,'0X~A" (length msg) msg))
 *
 * (:emacs-channel-send 1 (:process "(format T \"hello!~%\")"))
 * (:channel-send 1 (:write-values (("NIL" 0 "'nil"))))
 */

int
main(int argc, char **argv)
{
	int sock;
	const char *const host = "127.0.0.1", *const port = "4005";
	INFO("Connecting to %s:%s\n", host, port);

	if ((sock = slynk_connect(host, port)) < 0) {
		fprintf(stderr, "Failed to connect to %s:%s\n", host, port);
		return 1;
	}
	slynk_parse_message(NULL);
	/* slynk_send(sock, "(:emacs-rex (cl:format T \"Hello, world!~%\") nil t 1)"); */
	/* slynk_parse_message(slynk_recv(sock)); */
	/* sleep(5); */
	/* slynk_disconnect(sock); */

	close(sock);
	return 0;
}
Generated 2024-06-10 19:24:14 -0700 by RepoRat