ref: 2c1651bd5b23d04b203daf52801dd9b84bb9f6a9
dir: /9gc.c/
#define _DEFAULT_SOURCE #define _FILE_OFFSET_BITS 64 #include <errno.h> #include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.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, needopen; 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 hint = { .ai_flags = AI_ADDRCONFIG, .ai_family = AF_UNSPEC, 0 }, *r, *a; char host[64], *port; int e, f, yes; 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; yes = 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){ setsockopt(f, SOL_SOCKET, TCP_NODELAY, &yes, sizeof(yes)); break; } close(f); f = -1; } 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]; int n; a = ctx->aux; switch(r->type){ case Rversion: c9attach(ctx, &tag, Rootfid, C9nofid, "none", NULL); break; case Rattach: path[0] = channel; path[1] = NULL; c9walk(ctx, &tag, Rootfid, Chatfid, path); needopen = 1; break; case Rwalk: needopen = needopen && 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){ n = snprintf(buf, sizeof(buf), "JOIN %s to chat\n", nick); c9write(ctx, &tag, Chatfid, 0, buf, n); 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, *e; C9aux *a; C9tag tag; const char *path[2]; a = ctx->aux; switch(r->type){ case Rversion: c9attach(ctx, &tag, Rootfid, C9nofid, "none", NULL); break; case Rattach: path[0] = "index"; path[1] = NULL; c9walk(ctx, &tag, Rootfid, Indexfid, path); break; case Rwalk: 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 && (e = strchr(b, ' ')) != NULL){ *e = 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; if((c = calloc(1, sizeof(*c))) == NULL){ close(f); return NULL; } 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; int n, sz, sz0; fd_set r, e; 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 = sz0 = 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{ if(s[sz0] != '\n') 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; }