ref: 507bd8e86d9f0f9d225d5a0ae7d6c3be0dbac085
parent: 1cf6d5da7e85735f5a152e38405d919aed837763
author: Sigrid Haflínudóttir <[email protected]>
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