leibowitz
Experimental Common Lisp object storage abstraction for Unix file systems
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;
}