ref: ed4fa7190493a132e7f2d340b4c7c0ac228795e5
dir: /9gc.c/
#define _DEFAULT_SOURCE #define _FILE_OFFSET_BITS 64 #include <errno.h> #include <netdb.h> #include <netinet/in.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 <termios.h> #include <unistd.h> #include "c9.h" #include "parg.h" enum { Msize = 8192, Rootfid = 0, Indexfid, Chatfid, Error = 1<<0, Joined = 1<<1, Disconnected = 1<<2, }; typedef struct C9aux C9aux; typedef struct REntry REntry; struct C9aux { C9ctx c; int f; int flags; uint8_t rdbuf[Msize]; uint8_t wrbuf[Msize]; uint32_t wroff; }; static const char *nick; static int printjoin; static uint64_t chatoff, skipuntil; //static char *regsrv = "tcp!registry.9p.zone!6675"; static char *chatsrv = "tcp!chat.9p.zone!9990"; static const char *channel = "chat"; static uint8_t * ctxread(C9ctx *ctx, uint32_t size, int *err) { uint32_t n; int r; C9aux *a; a = ctx->aux; *err = 0; for (n = 0; n < size; n += r) { if ((r = read(a->f, a->rdbuf+n, size-n)) <= 0) { if (errno == EINTR) continue; a->flags |= Disconnected; close(a->f); return NULL; } } return a->rdbuf; } static int wrsend(C9aux *a) { uint32_t n; int w; for (n = 0; n < a->wroff; n += w) { if ((w = write(a->f, a->wrbuf+n, a->wroff-n)) <= 0) { if (errno == EINTR) continue; if (errno != EPIPE) /* remote end closed */ perror("write"); return -1; } } a->wroff = 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) { (void)ctx; 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", gai_strerror(e), s); 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); return f; } static void output(uint8_t *d, int sz) { int i, j; for (i = j = 0; i < sz; i++) { d[j] = d[i]; if (d[j] > 31 || d[j] == '\t' || d[j] == '\n') j++; } write(1, d, j); } 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] = channel; path[1] = NULL; c9walk(ctx, &tag, Rootfid, Chatfid, path); c9open(ctx, &tag, Chatfid, C9rdwr); break; case Rread: if (chatoff >= skipuntil) output(r->read.data, r->read.size); chatoff += r->read.size; /* fallthrough */ case Ropen: if ((a->flags & Joined) == 0 && printjoin) { c9write(ctx, &tag, Chatfid, 0, buf, snprintf(buf, sizeof(buf), "JOIN %s to chat\n", nick)); a->flags |= Joined; } c9read(ctx, &tag, Chatfid, chatoff, chatoff < skipuntil ? skipuntil-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, "chat")) == 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]; else *s++ = 0; if (strstr(b, "tlssrv") == NULL && (s = strchr(b, ' ')) != NULL) { *s = 0; fallback: close(a->f); if ((a->f = dial(b)) < 0) exit(1); a->flags = 0; ctx->r = ctxchatR; a->wroff = 0; c9version(ctx, &tag, Msize); if (wrsend(a) != 0) exit(1); return; } } b = chatsrv; goto fallback; 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->c.read = ctxread; c->c.begin = ctxbegin; c->c.end = ctxend; c->c.error = ctxerror; c->c.aux = c; return c; } static C9aux * registry(void) { C9aux *a; C9tag tag; int i; for (i = 0; i < 10; i++) { // if ((a = srv(regsrv)) == NULL) { if ((a = srv(chatsrv)) != NULL) { a->c.r = ctxchatR; c9version(&a->c, &tag, Msize); wrsend(a); return a; } // } else { // break; // } sleep(10); } if (a == NULL) return NULL; a->c.r = ctxregistryR; c9version(&a->c, &tag, Msize); wrsend(a); while (c9proc(&a->c) == 0 && a->c.r == ctxregistryR) wrsend(a); return a; } static int chatrw(C9aux *a) { struct timeval t; fd_set r, e; int n, 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); memset(&t, 0, sizeof(t)); t.tv_sec = 10; for (;;) { errno = 0; if (select(a->f + 1, &r, NULL, &e, &t) < 0 || FD_ISSET(a->f, &e) || FD_ISSET(0, &e)) { if (errno == EINTR) continue; return -1; } break; } ctx = &a->c; if (FD_ISSET(a->f, &r)) { c9proc(ctx); } else if (FD_ISSET(0, &r)) { s = (char*)a->rdbuf; sz = sprintf(s, "%s → ", nick); for (;;) { if ((n = read(0, s+sz, sizeof(a->rdbuf)-sz)) > 0) sz += n; else exit(0); if (s[sz-1] != '\n'){ s[sz-1] = '\n'; }else{ c9write(ctx, &tag, Chatfid, 0, s, sz); break; } } } else { const char *path[] = {NULL}; c9walk(ctx, &tag, Rootfid, Rootfid, path); } return 0; } int main(int argc, char **argv) { struct parg_state ps; struct termios t; C9aux *a; int c, noecho; parg_init(&ps); noecho = 0; while ((c = parg_getopt(&ps, argc, argv, "hjedc:")) >= 0) { switch (c) { case 1: if (nick != NULL) { fprintf(stderr, "only one nickname can be specified\n"); return 1; } nick = ps.optarg; break; case 'c': channel = ps.optarg; break; case 'j': printjoin = 1; break; case 'e': noecho = 1; break; case 'h': fprintf(stderr, "usage: 9gc [-j] [-e] [-c CHANNEL] 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 (noecho && tcgetattr(0, &t) == 0) { t.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &t); } for (;;) { if ((a = registry()) == NULL) return 1; while (chatrw(a) == 0 && wrsend(a) == 0); if (a->flags & (Disconnected|Error)) { a->flags &= ~(Disconnected|Error); skipuntil = chatoff; chatoff = 0; free(a); } else { printf("exiting\n"); exit(1); } } return 0; }