shithub: 9pro

Download patch

ref: 507bd8e86d9f0f9d225d5a0ae7d6c3be0dbac085
parent: 1cf6d5da7e85735f5a152e38405d919aed837763
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Tue May 19 13:21:24 EDT 2020

add 9gridchan client

--- a/.gitignore
+++ b/.gitignore
@@ -1,1 +1,2 @@
 9pex
+9gc
--- /dev/null
+++ b/9gc.c
@@ -1,0 +1,412 @@
+#define _DEFAULT_SOURCE
+#define _FILE_OFFSET_BITS 64
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "c9.h"
+#include "parg.h"
+
+enum {
+	Msize = 8192,
+
+	Rootfid = 0,
+	Indexfid,
+	Chatfid,
+
+	Error = 1<<0,
+	Eof = 1<<1,
+	Joined = 1<<2,
+};
+
+#define max(a,b) ((a)>(b)?(a):(b))
+#define used(x) ((void)(x))
+#define nelem(x) (int)(sizeof(x)/sizeof((x)[0]))
+
+typedef struct C9aux C9aux;
+typedef struct REntry REntry;
+
+struct C9aux {
+	C9ctx;
+	int f;
+	int flags;
+	uint8_t rdbuf[Msize];
+	uint8_t wrbuf[Msize];
+	uint32_t wroff;
+	uint32_t wrend;
+	uint64_t chatoff;
+	const char *nick;
+};
+
+static uint8_t *
+ctxread(C9ctx *ctx, uint32_t size, int *err)
+{
+	uint32_t n;
+	int r;
+	C9aux *a;
+
+	a = ctx->aux;
+	r = 0;
+	*err = 0;
+	for (n = 0; n < size; n += r) {
+		errno = 0;
+		if ((r = read(a->f, a->rdbuf+n, size-n)) <= 0) {
+			if (r == EINTR)
+				continue;
+			if (r == 0)
+				a->flags |= Eof;
+			else
+				*err = 1;
+			return NULL;
+		}
+	}
+
+	return a->rdbuf;
+}
+
+static int
+wrsend(C9aux *a)
+{
+	uint32_t n;
+	int w;
+
+	if (a->wrend == 0)
+		return 0;
+	for (n = 0; n < a->wrend; n += w) {
+		errno = 0;
+		if ((w = write(a->f, a->wrbuf+n, a->wrend-n)) <= 0) {
+			if (errno == EINTR)
+				continue;
+			if (errno != EPIPE) /* remote end closed */
+				perror("write");
+			return -1;
+		}
+	}
+	memmove(a->wrbuf, a->wrbuf+a->wrend, a->wroff-a->wrend);
+	a->wroff = a->wroff - a->wrend;
+	a->wrend = 0;
+
+	return 0;
+}
+
+static uint8_t *
+ctxbegin(C9ctx *ctx, uint32_t size)
+{
+	uint8_t *b;
+	C9aux *a;
+
+	a = ctx->aux;
+	if (a->wroff + size > sizeof(a->wrbuf)) {
+		if (wrsend(a) != 0 || a->wroff + size > sizeof(a->wrbuf))
+			return NULL;
+	}
+	b = a->wrbuf + a->wroff;
+	a->wroff += size;
+
+	return b;
+}
+
+static int
+ctxend(C9ctx *ctx)
+{
+	C9aux *a;
+
+	a = ctx->aux;
+	a->wrend = a->wroff;
+	return 0;
+}
+
+static int
+dial(char *s)
+{
+	struct addrinfo *r, *a, hint = {.ai_flags = AI_ADDRCONFIG, .ai_family = AF_UNSPEC, 0};
+	char host[64], *port;
+	int e, f;
+
+	if (strncmp(s, "udp!", 4) == 0) {
+		hint.ai_socktype = SOCK_DGRAM;
+		hint.ai_protocol = IPPROTO_UDP;
+	} else if (strncmp(s, "tcp!", 4) == 0) {
+		hint.ai_socktype = SOCK_STREAM;
+		hint.ai_protocol = IPPROTO_TCP;
+	} else {
+		fprintf(stderr, "invalid dial string: %s\n", s);
+		return -1;
+	}
+	if ((port = strchr(s+4, '!')) == NULL) {
+		fprintf(stderr, "invalid dial string: %s\n", s);
+		return -1;
+	}
+	if (snprintf(host, sizeof(host), "%.*s", (int)(port-s-4), s+4) >= (int)sizeof(host)) {
+		fprintf(stderr, "host name too large: %s\n", s);
+		return -1;
+	}
+	port++;
+	if ((e = getaddrinfo(host, port, &hint, &r)) != 0) {
+		fprintf(stderr, "%s: %s\n", s, gai_strerror(e));
+		return -1;
+	}
+	f = -1;
+	for (a = r; a != NULL; a = a->ai_next) {
+		if ((f = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) < 0)
+			continue;
+		if (connect(f, a->ai_addr, a->ai_addrlen) != 0) {
+			close(f);
+			f = -1;
+			continue;
+		}
+	}
+	freeaddrinfo(r);
+	if (f < 0)
+		fprintf(stderr, "%s: connection failed\n", s);
+
+	return f;
+}
+
+static void
+ctxchatR(C9ctx *ctx, C9r *r)
+{
+	C9aux *a;
+	C9tag tag;
+	const char *path[2];
+	char buf[64];
+
+	a = ctx->aux;
+	switch (r->type) {
+	case Rversion:
+		c9attach(ctx, &tag, Rootfid, C9nofid, "none", NULL);
+		path[0] = "chat";
+		path[1] = NULL;
+		c9walk(ctx, &tag, Rootfid, Chatfid, path);
+		c9open(ctx, &tag, Chatfid, C9rdwr);
+		break;
+
+	case Rread:
+		write(1, r->read.data, r->read.size);
+		a->chatoff += r->read.size;
+	case Ropen:
+		if ((a->flags & Joined) == 0) {
+			c9write(ctx, &tag, Chatfid, 0, buf, snprintf(buf, sizeof(buf), "JOIN %s to chat\n", a->nick));
+			a->flags |= Joined;
+		}
+		c9read(ctx, &tag, Chatfid, a->chatoff, Msize);
+		break;
+
+	case Rerror:
+		fprintf(stderr, "chat error: %s\n", r->error);
+		a->flags |= Error;
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void
+ctxregistryR(C9ctx *ctx, C9r *r)
+{
+	char *s, *b;
+	C9aux *a;
+	C9tag tag;
+	const char *path[2];
+
+	a = ctx->aux;
+	switch (r->type) {
+	case Rversion:
+		c9attach(ctx, &tag, Rootfid, C9nofid, "none", NULL);
+		path[0] = "index";
+		path[1] = NULL;
+		c9walk(ctx, &tag, Rootfid, Indexfid, path);
+		c9open(ctx, &tag, Indexfid, C9read);
+		break;
+
+	case Ropen:
+		c9read(ctx, &tag, Indexfid, 0, Msize);
+		break;
+
+	case Rread:
+		r->read.data[r->read.size] = 0;
+		for (s = (char*)r->read.data;;) {
+			if ((s = strstr(s, "gridchat")) == NULL)
+				break;
+			for (b = s; b != (char*)r->read.data && *b != '\n'; b--);
+			if (*b == '\n')
+				b++;
+			if ((s = strchr(s, '\n')) == NULL)
+				s = (char*)&r->read.data[r->read.size];
+			*s = 0;
+			if (strstr(b, "tlssrv") == NULL && (s = strchr(b, ' ')) != NULL) {
+				*s = 0;
+				printf("chat is at %s\n", b);
+				close(a->f);
+				if ((a->f = dial(b)) < 0)
+					exit(1);
+				a->flags = 0;
+				a->r = ctxchatR;
+				a->wroff = a->wrend = 0;
+				c9version(ctx, &tag, Msize);
+				wrsend(a);
+				break;
+			}
+			
+		}
+		break;
+
+	case Rerror:
+		fprintf(stderr, "registry error: %s\n", r->error);
+		a->flags |= Error;
+		break;
+
+	default:
+		break;
+	}
+}
+
+__attribute__ ((format (printf, 1, 2)))
+static void
+ctxerror(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, "\n");
+	va_end(ap);
+}
+
+static C9aux *
+srv(char *s)
+{
+	C9aux *c;
+	int f;
+
+	if ((f = dial(s)) < 0)
+		return NULL;
+	c = calloc(1, sizeof(*c));
+	c->f = f;
+	c->read = ctxread;
+	c->begin = ctxbegin;
+	c->end = ctxend;
+	c->error = ctxerror;
+	c->aux = c;
+
+	return c;
+}
+
+static C9aux *
+registry(void)
+{
+	C9aux *a;
+	C9tag tag;
+
+	if ((a = srv("tcp!registry.9gridchan.org!6675")) == NULL)
+		return NULL;
+	a->r = ctxregistryR;
+	c9version((C9ctx*)a, &tag, Msize);
+	wrsend(a);
+	while (c9proc((C9ctx*)a) == 0 && a->r == ctxregistryR)
+		wrsend(a);
+
+	return a;
+}
+
+static int
+chatrw(C9aux *a)
+{
+	fd_set r, e;
+	int n, fl, sz;
+	C9tag tag;
+	C9ctx *ctx;
+	char *s;
+
+	FD_ZERO(&r);
+	FD_SET(a->f, &r);
+	FD_SET(0, &r);
+	FD_ZERO(&e);
+	FD_SET(a->f, &e);
+	FD_SET(0, &e);
+	for (;;) {
+		errno = 0;
+		if ((n = select(a->f + 1, &r, NULL, &e, NULL)) < 0 || FD_ISSET(a->f, &e) || FD_ISSET(0, &e)) {
+			if (errno == EINTR)
+				continue;
+			return -1;
+		}
+		break;
+	}
+
+	ctx = (C9ctx*)a;
+	fl = 0;
+	if (FD_ISSET(a->f, &r))
+		c9proc(ctx);
+	if (FD_ISSET(0, &r)) {
+		s = (char*)a->rdbuf;
+		sz = sprintf(s, "%s → ", a->nick);
+		for (;;) {
+			if ((n = read(0, s+sz, sizeof(a->rdbuf)-sz)) > 0)
+				sz += n;
+			if (s[sz-1] != '\n'){
+				s[sz-1] = '\n';
+			}else{
+				c9write(ctx, &tag, Chatfid, 0, s, sz);
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	struct parg_state ps;
+	C9aux *a;
+	const char *nick;
+	int c;
+
+	parg_init(&ps);
+
+	nick = NULL;
+	while ((c = parg_getopt(&ps, argc, argv, "dh")) >= 0) {
+		switch (c) {
+		case 1:
+			if (nick != NULL) {
+				fprintf(stderr, "only one nickname can be specified\n");
+				return 1;
+			}
+			nick = ps.optarg;
+			break;
+		case 'h':
+			fprintf(stderr, "usage: 9gc NICKNAME\n");
+			return 0;
+			break;
+		case '?':
+			fprintf(stderr, "unknown option -%c\n", ps.optopt);
+			return 1;
+			break;
+		default:
+			fprintf(stderr, "unhandled option -%c\n", c);
+			return 1;
+			break;
+		}
+	}
+
+	if (nick == NULL) {
+		fprintf(stderr, "no nickname specified\n");
+		return 1;
+	}
+
+	if ((a = registry()) == NULL)
+		return 1;
+	a->nick = nick;
+	while (chatrw(a) == 0 && wrsend(a) == 0);
+
+	return 0;
+}
--- a/9pex.c
+++ b/9pex.c
@@ -559,7 +559,7 @@
 	*err = 0;
 	for (n = 0; n < size; n += r) {
 		errno = 0;
-		if ((r = read(in, rdbuf, size)) <= 0) {
+		if ((r = read(in, rdbuf+n, size-n)) <= 0) {
 			if (r == EINTR)
 				continue;
 			if (r == 0)
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
 Plan9-related tools for Unix-like operating systems.
 
  * 9pex - share a directory over stdin/stdout, can be used with socat/inetd
+ * 9gc  - a very simple stdin/stdout 9gridchan client
 
 This is all _WIP_ still.  9pex is working in read-only mode so far but
 lacks proper auth, async IO, some more error control etc.
@@ -19,4 +20,4 @@
 
 # Notes/todo
 
- * chrooting with musl makes `realpath` not work as it requires /proc to be mounted, get rid of `realpath`?
+ * 9pex: chrooting with musl makes `realpath` not work as it requires /proc to be mounted, get rid of `realpath`?
--- a/build.sh
+++ b/build.sh
@@ -1,4 +1,5 @@
 #!/bin/sh
 set -e
 set -x
-${CC:-gcc} -std=c99 -fms-extensions -DC9_NO_CLIENT -O -ggdb -Wall -Wextra -Wshadow -Werror $CFLAGS c9/*.c parg/*.c -Ic9 -Iparg 9pex.c crc32.c -o 9pex
+${CC:-gcc} -std=c99 -fms-extensions -DC9_NO_SERVER -O0 -g -Wall -Wextra -Wshadow -Werror $CFLAGS c9/*.c parg/*.c -Ic9 -Iparg 9gc.c -o 9gc || rm -f 9gc
+${CC:-gcc} -std=c99 -fms-extensions -DC9_NO_CLIENT -O0 -g -Wall -Wextra -Wshadow -Werror $CFLAGS c9/*.c parg/*.c -Ic9 -Iparg 9pex.c crc32.c -o 9pex || rm -f 9pex