ref: f9e21a66f6594e62a494da24f52957187390b6a2
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.only9fans.com!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);
if(wrsend(a) != 0){
free(a);
a = NULL;
continue;
}
return a;
}
// }else{
// break;
// }
sleep(10);
}
if(a == NULL)
return NULL;
a->c.r = ctxregistryR;
c9version(&a->c, &tag, Msize);
do{
if(wrsend(a) != 0){
free(a);
a = NULL;
break;
}
}while(c9proc(&a->c) == 0 && a->c.r == ctxregistryR);
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;
}