#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" #define MIN(a,b) ((a)<=(b)?(a):(b))#define USED(x) (void)(x) enum { Msize = 8192, Rootfid = 0, Indexfid, Chatfid, Error = 1<<0, Joined = 1<<1, Disconnected = 1<<2,}; typedef struct REntry REntry; static C9ctx ctx;static uint8_t rdbuf[Msize], wrbuf[Msize];static uint32_t wroff;static const char *nick;static int f, flags, printjoin, startlen, inflight;static uint64_t chatoff, skipuntil;static char start[256]; static uint8_t *ctxread(C9ctx *c, uint32_t size, int *err){ uint32_t n; int r; USED(c); *err = 0; for (n = 0; n < size; n += r) { if ((r = read(f, rdbuf+n, size-n)) <= 0) { if (errno == EINTR) continue; flags |= Disconnected; close(f); f = -1; return NULL; } } return rdbuf;} static intwrsend(void){ uint32_t n; int w; for (n = 0; n < wroff; n += w) { if ((w = write(f, wrbuf+n, wroff-n)) <= 0) { if (errno == EINTR) continue; if (errno != EPIPE){ /* remote end closed */ flags |= Disconnected; close(f); f = -1; } return -1; } } wroff = 0; return 0;} static uint8_t *ctxbegin(C9ctx *c, uint32_t size){ uint8_t *b; USED(c); if (wroff + size > sizeof(wrbuf)) { if (wrsend() != 0 || wroff+size > sizeof(wrbuf)) return NULL; } b = &wrbuf[wroff]; wroff += size; return b;} static intctxend(C9ctx *c){ USED(c); return 0;} static intdial(char *s){ struct addrinfo *r, *a, hint = {.ai_flags = AI_ADDRCONFIG, .ai_family = AF_UNSPEC, 0}; char host[64], *port; int e, fd; 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) return -1; fd = -1; for (a = r; a != NULL; a = a->ai_next) { if ((fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) < 0) continue; if (connect(fd, a->ai_addr, a->ai_addrlen) != 0) { close(fd); fd = -1; continue; } } freeaddrinfo(r); return fd;} static voidctxchatR(C9ctx *c, C9r *r){ C9tag tag; const char *path[2]; char buf[64]; uint64_t sz, n; int i; switch (r->type) { case Rversion: c9attach(c, &tag, Rootfid, C9nofid, "none", NULL); path[0] = "chat"; path[1] = NULL; c9walk(c, &tag, Rootfid, Chatfid, path); c9open(c, &tag, Chatfid, C9rdwr); break; case Rread: inflight--; if (chatoff == 0 && skipuntil > 0 && memcmp(start, r->read.data, MIN((int)r->read.size, startlen)) != 0) { skipuntil = 0; startlen = 0; } if (startlen < (int)sizeof(start)) { n = MIN(r->read.size, sizeof(start)-startlen); memmove(start+startlen, r->read.data, n); startlen += n; } if (chatoff >= skipuntil) write(1, r->read.data, r->read.size); chatoff += r->read.size; /* fallthrough */ case Ropen: if ((flags & Joined) == 0 && printjoin) { c9write(c, &tag, Chatfid, 0, buf, snprintf(buf, sizeof(buf), "JOIN %s to chat\n", nick)); flags |= Joined; } /* gotta read fast */ n = chatoff; for (i = 0; i < 4-inflight; i++) { sz = n < skipuntil ? skipuntil-n : Msize; c9read(c, &tag, Chatfid, n, sz); n += sz; } inflight = 4; break; case Rerror: fprintf(stderr, "chat error: %s\n", r->error); flags |= Error; break; default: break; }} static voidctxregistryR(C9ctx *c, C9r *r){ char *s, *b; C9tag tag; const char *path[2]; int i; switch (r->type) { case Rversion: c9attach(c, &tag, Rootfid, C9nofid, "none", NULL); path[0] = "index"; path[1] = NULL; c9walk(c, &tag, Rootfid, Indexfid, path); c9open(c, &tag, Indexfid, C9read); break; case Ropen: c9read(c, &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; close(f); for (i = 0; i < 10 && (f = dial(b)) < 0; i++) sleep(5); if (f < 0) { fprintf(stderr, "failed to connect to %s, giving up\n", b); exit(1); } flags = 0; ctx.r = ctxchatR; wroff = 0; if (c9version(c, &tag, Msize) != 0 || wrsend() != 0) exit(1); return; } } fprintf(stderr, "chat hasn't been found in the registry\n"); exit(1); break; case Rerror: fprintf(stderr, "registry error: %s\n", r->error); flags |= Error; break; default: break; }} __attribute__ ((format (printf, 1, 2)))static voidctxerror(const char *fmt, ...){ va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap);} static intsrv(char *s){ if ((f = dial(s)) < 0) return -1; ctx.read = ctxread; ctx.begin = ctxbegin; ctx.end = ctxend; ctx.error = ctxerror; return 0;} static intregistry(void){ C9tag tag; int i; for (i = 0; i < 10; i++) { if (srv("tcp!registry.9gridchan.org!6675") == 0) { ctx.r = ctxregistryR; if (c9version(&ctx, &tag, Msize) == 0 && wrsend() == 0) { while (c9proc(&ctx) == 0 && ctx.r == ctxregistryR) { if (wrsend() != 0) return -1; } } return 0; } sleep(5); } return -1;} static intchatrw(void){ struct timeval t; fd_set r, e; int n, sz, res; C9tag tag; char *s; if (f < 0) { flags |= Error; return -1; } FD_ZERO(&r); FD_SET(f, &r); FD_SET(0, &r); FD_ZERO(&e); FD_SET(f, &e); FD_SET(0, &e); memset(&t, 0, sizeof(t)); t.tv_sec = 10; for (;;) { errno = 0; if ((n = select(f+1, &r, NULL, &e, &t)) < 0 || FD_ISSET(f, &e) || FD_ISSET(0, &e)) { if (errno == EINTR) continue; flags |= Error; return -1; } break; } if (FD_ISSET(f, &r)) { res = c9proc(&ctx); } else if (FD_ISSET(0, &r)) { s = (char*)rdbuf; sz = sprintf(s, "%s → ", nick); for (;;) { if ((n = read(0, s+sz, sizeof(rdbuf)-sz)) > 0) sz += n; else exit(0); if (s[sz-1] != '\n'){ s[sz-1] = '\n'; }else{ res = c9write(&ctx, &tag, Chatfid, 0, s, sz); break; } } } else { const char *path[] = {NULL}; res = c9walk(&ctx, &tag, Rootfid, Rootfid, path); } return res;} intmain(int argc, char **argv){ struct parg_state ps; struct termios t; int c, noecho; parg_init(&ps); noecho = 0; while ((c = parg_getopt(&ps, argc, argv, "hjed")) >= 0) { switch (c) { case 1: if (nick != NULL) { fprintf(stderr, "only one nickname can be specified\n"); return 1; } nick = ps.optarg; break; case 'j': printjoin = 1; break; case 'e': noecho = 1; break; case 'h': fprintf(stderr, "usage: 9gc [-j] [-e] 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 (registry() != 0) return 1; while (chatrw() == 0 && wrsend() == 0); if (flags & (Disconnected|Error)) { flags &= ~(Disconnected|Error); skipuntil = chatoff; wroff = 0; chatoff = 0; inflight = 0; } else { printf("exiting\n"); exit(1); } } return 0;}
#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"
#define MIN(a,b) ((a)<=(b)?(a):(b))
#define USED(x) (void)(x)
enum {
Msize = 8192,
Rootfid = 0,
Indexfid,
Chatfid,
Error = 1<<0,
Joined = 1<<1,
Disconnected = 1<<2,
};
typedef struct REntry REntry;
static C9ctx ctx;
static uint8_t rdbuf[Msize], wrbuf[Msize];
static uint32_t wroff;
static const char *nick;
static int f, flags, printjoin, startlen, inflight;
static uint64_t chatoff, skipuntil;
static char start[256];
static uint8_t *
ctxread(C9ctx *c, uint32_t size, int *err)
{
uint32_t n;
int r;
USED(c);
*err = 0;
for (n = 0; n < size; n += r) {
if ((r = read(f, rdbuf+n, size-n)) <= 0) {
if (errno == EINTR)
continue;
flags |= Disconnected;
close(f);
f = -1;
return NULL;
}
}
return rdbuf;
}
static int
wrsend(void)
{
uint32_t n;
int w;
for (n = 0; n < wroff; n += w) {
if ((w = write(f, wrbuf+n, wroff-n)) <= 0) {
if (errno == EINTR)
continue;
if (errno != EPIPE){ /* remote end closed */
flags |= Disconnected;
close(f);
f = -1;
}
return -1;
}
}
wroff = 0;
return 0;
}
static uint8_t *
ctxbegin(C9ctx *c, uint32_t size)
{
uint8_t *b;
USED(c);
if (wroff + size > sizeof(wrbuf)) {
if (wrsend() != 0 || wroff+size > sizeof(wrbuf))
return NULL;
}
b = &wrbuf[wroff];
wroff += size;
return b;
}
static int
ctxend(C9ctx *c)
{
USED(c);
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, fd;
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)
return -1;
fd = -1;
for (a = r; a != NULL; a = a->ai_next) {
if ((fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) < 0)
continue;
if (connect(fd, a->ai_addr, a->ai_addrlen) != 0) {
close(fd);
fd = -1;
continue;
}
}
freeaddrinfo(r);
return fd;
}
static void
ctxchatR(C9ctx *c, C9r *r)
{
C9tag tag;
const char *path[2];
char buf[64];
uint64_t sz, n;
int i;
switch (r->type) {
case Rversion:
c9attach(c, &tag, Rootfid, C9nofid, "none", NULL);
path[0] = "chat";
path[1] = NULL;
c9walk(c, &tag, Rootfid, Chatfid, path);
c9open(c, &tag, Chatfid, C9rdwr);
break;
case Rread:
inflight--;
if (chatoff == 0 && skipuntil > 0 && memcmp(start, r->read.data, MIN((int)r->read.size, startlen)) != 0) {
skipuntil = 0;
startlen = 0;
}
if (startlen < (int)sizeof(start)) {
n = MIN(r->read.size, sizeof(start)-startlen);
memmove(start+startlen, r->read.data, n);
startlen += n;
}
if (chatoff >= skipuntil)
write(1, r->read.data, r->read.size);
chatoff += r->read.size;
/* fallthrough */
case Ropen:
if ((flags & Joined) == 0 && printjoin) {
c9write(c, &tag, Chatfid, 0, buf, snprintf(buf, sizeof(buf), "JOIN %s to chat\n", nick));
flags |= Joined;
}
/* gotta read fast */
n = chatoff;
for (i = 0; i < 4-inflight; i++) {
sz = n < skipuntil ? skipuntil-n : Msize;
c9read(c, &tag, Chatfid, n, sz);
n += sz;
}
inflight = 4;
break;
case Rerror:
fprintf(stderr, "chat error: %s\n", r->error);
flags |= Error;
break;
default:
break;
}
}
static void
ctxregistryR(C9ctx *c, C9r *r)
{
char *s, *b;
C9tag tag;
const char *path[2];
int i;
switch (r->type) {
case Rversion:
c9attach(c, &tag, Rootfid, C9nofid, "none", NULL);
path[0] = "index";
path[1] = NULL;
c9walk(c, &tag, Rootfid, Indexfid, path);
c9open(c, &tag, Indexfid, C9read);
break;
case Ropen:
c9read(c, &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;
close(f);
for (i = 0; i < 10 && (f = dial(b)) < 0; i++)
sleep(5);
if (f < 0) {
fprintf(stderr, "failed to connect to %s, giving up\n", b);
exit(1);
}
flags = 0;
ctx.r = ctxchatR;
wroff = 0;
if (c9version(c, &tag, Msize) != 0 || wrsend() != 0)
exit(1);
return;
}
}
fprintf(stderr, "chat hasn't been found in the registry\n");
exit(1);
break;
case Rerror:
fprintf(stderr, "registry error: %s\n", r->error);
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 int
srv(char *s)
{
if ((f = dial(s)) < 0)
return -1;
ctx.read = ctxread;
ctx.begin = ctxbegin;
ctx.end = ctxend;
ctx.error = ctxerror;
return 0;
}
static int
registry(void)
{
C9tag tag;
int i;
for (i = 0; i < 10; i++) {
if (srv("tcp!registry.9gridchan.org!6675") == 0) {
ctx.r = ctxregistryR;
if (c9version(&ctx, &tag, Msize) == 0 && wrsend() == 0) {
while (c9proc(&ctx) == 0 && ctx.r == ctxregistryR) {
if (wrsend() != 0)
return -1;
}
}
return 0;
}
sleep(5);
}
return -1;
}
static int
chatrw(void)
{
struct timeval t;
fd_set r, e;
int n, sz, res;
C9tag tag;
char *s;
if (f < 0) {
flags |= Error;
return -1;
}
FD_ZERO(&r);
FD_SET(f, &r);
FD_SET(0, &r);
FD_ZERO(&e);
FD_SET(f, &e);
FD_SET(0, &e);
memset(&t, 0, sizeof(t));
t.tv_sec = 10;
for (;;) {
errno = 0;
if ((n = select(f+1, &r, NULL, &e, &t)) < 0 || FD_ISSET(f, &e) || FD_ISSET(0, &e)) {
if (errno == EINTR)
continue;
flags |= Error;
return -1;
}
break;
}
if (FD_ISSET(f, &r)) {
res = c9proc(&ctx);
} else if (FD_ISSET(0, &r)) {
s = (char*)rdbuf;
sz = sprintf(s, "%s → ", nick);
for (;;) {
if ((n = read(0, s+sz, sizeof(rdbuf)-sz)) > 0)
sz += n;
else
exit(0);
if (s[sz-1] != '\n'){
s[sz-1] = '\n';
}else{
res = c9write(&ctx, &tag, Chatfid, 0, s, sz);
break;
}
}
} else {
const char *path[] = {NULL};
res = c9walk(&ctx, &tag, Rootfid, Rootfid, path);
}
return res;
}
int
main(int argc, char **argv)
{
struct parg_state ps;
struct termios t;
int c, noecho;
parg_init(&ps);
noecho = 0;
while ((c = parg_getopt(&ps, argc, argv, "hjed")) >= 0) {
switch (c) {
case 1:
if (nick != NULL) {
fprintf(stderr, "only one nickname can be specified\n");
return 1;
}
nick = ps.optarg;
break;
case 'j':
printjoin = 1;
break;
case 'e':
noecho = 1;
break;
case 'h':
fprintf(stderr, "usage: 9gc [-j] [-e] 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 (registry() != 0)
return 1;
while (chatrw() == 0 && wrsend() == 0);
if (flags & (Disconnected|Error)) {
flags &= ~(Disconnected|Error);
skipuntil = chatoff;
wroff = 0;
chatoff = 0;
inflight = 0;
} else {
printf("exiting\n");
exit(1);
}
}
return 0;
}