plan9fox/sys/src/cmd/9nfs/server.c
2011-03-30 19:35:09 +03:00

619 lines
11 KiB
C

#include "all.h"
#include <ndb.h>
static int alarmflag;
static int Iconv(Fmt*);
static void openudp(int);
static void cachereply(Rpccall*, void*, int);
static int replycache(int, Rpccall*, long (*)(int, void*, long));
static void udpserver(int, Progmap*);
static void tcpserver(int, Progmap*);
static void getendpoints(Udphdr*, char*);
static long readtcp(int, void*, long);
static long writetcp(int, void*, long);
static int servemsg(int, long (*)(int, void*, long), long (*)(int, void*, long),
int, Progmap*);
void (*rpcalarm)(void);
int rpcdebug;
int rejectall;
int p9debug;
int nocache;
uchar buf[9000];
uchar rbuf[9000];
uchar resultbuf[9000];
static int tcp;
char *commonopts = "[-9CDrtv]"; /* for usage() messages */
/*
* this recognises common, nominally rcp-related options.
* they may not take arguments.
*/
int
argopt(int c)
{
switch(c){
case '9':
++p9debug;
return 0;
case 'C':
++nocache;
return 0;
case 'D':
++rpcdebug;
return 0;
case 'r':
++rejectall;
return 0;
case 't':
tcp = 1;
return 0;
case 'v':
++chatty;
return 0;
default:
return -1;
}
}
/*
* all option parsing is now done in (*pg->init)(), which can call back
* here to argopt for common options.
*/
void
server(int argc, char **argv, int myport, Progmap *progmap)
{
Progmap *pg;
fmtinstall('I', Iconv);
fmtinstall('F', fcallfmt);
fmtinstall('D', dirfmt);
switch(rfork(RFNOWAIT|RFENVG|RFNAMEG|RFNOTEG|RFFDG|RFPROC)){
case -1:
panic("fork");
default:
_exits(0);
case 0:
break;
}
switch(rfork(RFMEM|RFPROC)){
case 0:
for(;;){
sleep(30*1000);
alarmflag = 1;
}
case -1:
sysfatal("rfork: %r");
}
for(pg=progmap; pg->init; pg++)
(*pg->init)(argc, argv);
if(tcp)
tcpserver(myport, progmap);
else
udpserver(myport, progmap);
}
static void
udpserver(int myport, Progmap *progmap)
{
char service[128];
char data[128];
char devdir[40];
int ctlfd, datafd;
snprint(service, sizeof service, "udp!*!%d", myport);
ctlfd = announce(service, devdir);
if(ctlfd < 0)
panic("can't announce %s: %r\n", service);
if(fprint(ctlfd, "headers") < 0)
panic("can't set header mode: %r\n");
snprint(data, sizeof data, "%s/data", devdir);
datafd = open(data, ORDWR);
if(datafd < 0)
panic("can't open udp data: %r\n");
close(ctlfd);
chatsrv(0);
clog("%s: listening to port %d\n", argv0, myport);
while (servemsg(datafd, read, write, myport, progmap) >= 0)
continue;
exits(0);
}
static void
tcpserver(int myport, Progmap *progmap)
{
char adir[40];
char ldir[40];
char ds[40];
int actl, lctl, data;
snprint(ds, sizeof ds, "tcp!*!%d", myport);
chatsrv(0);
actl = -1;
for(;;){
if(actl < 0){
actl = announce(ds, adir);
if(actl < 0){
clog("%s: listening to tcp port %d\n",
argv0, myport);
clog("announcing: %r");
break;
}
}
lctl = listen(adir, ldir);
if(lctl < 0){
close(actl);
actl = -1;
continue;
}
switch(fork()){
case -1:
clog("%s!%d: %r\n", argv0, myport);
/* fall through */
default:
close(lctl);
continue;
case 0:
close(actl);
data = accept(lctl, ldir);
close(lctl);
if(data < 0)
exits(0);
/* pretend it's udp; fill in Udphdr */
getendpoints((Udphdr*)buf, ldir);
while (servemsg(data, readtcp, writetcp, myport,
progmap) >= 0)
continue;
close(data);
exits(0);
}
}
exits(0);
}
static int
servemsg(int fd, long (*readmsg)(int, void*, long), long (*writemsg)(int, void*, long),
int myport, Progmap * progmap)
{
int i, n, nreply;
Rpccall rcall, rreply;
int vlo, vhi;
Progmap *pg;
Procmap *pp;
char errbuf[ERRMAX];
if(alarmflag){
alarmflag = 0;
if(rpcalarm)
(*rpcalarm)();
}
n = (*readmsg)(fd, buf, sizeof buf);
if(n < 0){
errstr(errbuf, sizeof errbuf);
if(strcmp(errbuf, "interrupted") == 0)
return 0;
clog("port %d: error: %s\n", myport, errbuf);
return -1;
}
if(n == 0){
clog("port %d: EOF\n", myport);
return -1;
}
if(rpcdebug == 1)
fprint(2, "%s: rpc from %d.%d.%d.%d/%d\n",
argv0, buf[12], buf[13], buf[14], buf[15],
(buf[32]<<8)|buf[33]);
i = rpcM2S(buf, &rcall, n);
if(i != 0){
clog("udp port %d: message format error %d\n",
myport, i);
return 0;
}
if(rpcdebug > 1)
rpcprint(2, &rcall);
if(rcall.mtype != CALL)
return 0;
if(replycache(fd, &rcall, writemsg))
return 0;
nreply = 0;
rreply.host = rcall.host;
rreply.port = rcall.port;
rreply.lhost = rcall.lhost;
rreply.lport = rcall.lport;
rreply.xid = rcall.xid;
rreply.mtype = REPLY;
if(rcall.rpcvers != 2){
rreply.stat = MSG_DENIED;
rreply.rstat = RPC_MISMATCH;
rreply.rlow = 2;
rreply.rhigh = 2;
goto send_reply;
}
if(rejectall){
rreply.stat = MSG_DENIED;
rreply.rstat = AUTH_ERROR;
rreply.authstat = AUTH_TOOWEAK;
goto send_reply;
}
i = n - (((uchar *)rcall.args) - buf);
if(rpcdebug > 1)
fprint(2, "arg size = %d\n", i);
rreply.stat = MSG_ACCEPTED;
rreply.averf.flavor = 0;
rreply.averf.count = 0;
rreply.results = resultbuf;
vlo = 0x7fffffff;
vhi = -1;
for(pg=progmap; pg->pmap; pg++){
if(pg->progno != rcall.prog)
continue;
if(pg->vers == rcall.vers)
break;
if(pg->vers < vlo)
vlo = pg->vers;
if(pg->vers > vhi)
vhi = pg->vers;
}
if(pg->pmap == 0){
if(vhi < 0)
rreply.astat = PROG_UNAVAIL;
else{
rreply.astat = PROG_MISMATCH;
rreply.plow = vlo;
rreply.phigh = vhi;
}
goto send_reply;
}
for(pp = pg->pmap; pp->procp; pp++)
if(rcall.proc == pp->procno){
if(rpcdebug > 1)
fprint(2, "process %d\n", pp->procno);
rreply.astat = SUCCESS;
nreply = (*pp->procp)(i, &rcall, &rreply);
goto send_reply;
}
rreply.astat = PROC_UNAVAIL;
send_reply:
if(nreply >= 0){
i = rpcS2M(&rreply, nreply, rbuf);
if(rpcdebug > 1)
rpcprint(2, &rreply);
(*writemsg)(fd, rbuf, i);
cachereply(&rreply, rbuf, i);
}
return 0;
}
static void
getendpoint(char *dir, char *file, uchar *addr, uchar *port)
{
int fd, n;
char buf[128];
char *sys, *serv;
sys = serv = 0;
snprint(buf, sizeof buf, "%s/%s", dir, file);
fd = open(buf, OREAD);
if(fd >= 0){
n = read(fd, buf, sizeof(buf)-1);
if(n>0){
buf[n-1] = 0;
serv = strchr(buf, '!');
if(serv){
*serv++ = 0;
serv = strdup(serv);
}
sys = strdup(buf);
}
close(fd);
}
if(serv == 0)
serv = strdup("unknown");
if(sys == 0)
sys = strdup("unknown");
parseip(addr, sys);
n = atoi(serv);
hnputs(port, n);
}
/* set Udphdr values from protocol dir local & remote files */
static void
getendpoints(Udphdr *ep, char *dir)
{
getendpoint(dir, "local", ep->laddr, ep->lport);
getendpoint(dir, "remote", ep->raddr, ep->rport);
}
static long
readtcp(int fd, void *vbuf, long blen)
{
uchar mk[4];
int n, m, sofar;
ulong done;
char *buf;
buf = vbuf;
buf += Udphdrsize;
blen -= Udphdrsize;
done = 0;
for(sofar = 0; !done; sofar += n){
m = readn(fd, mk, 4);
if(m < 4)
return 0;
done = (mk[0]<<24)|(mk[1]<<16)|(mk[2]<<8)|mk[3];
m = done & 0x7fffffff;
done &= 0x80000000;
if(m > blen-sofar)
return -1;
n = readn(fd, buf+sofar, m);
if(m != n)
return 0;
}
return sofar + Udphdrsize;
}
static long
writetcp(int fd, void *vbuf, long len)
{
char *buf;
buf = vbuf;
buf += Udphdrsize;
len -= Udphdrsize;
buf -= 4;
buf[0] = 0x80 | (len>>24);
buf[1] = len>>16;
buf[2] = len>>8;
buf[3] = len;
len += 4;
return write(fd, buf, len);
}
/*
*long
*niwrite(int fd, void *buf, long count)
*{
* char errbuf[ERRLEN];
* long n;
*
* for(;;){
* n = write(fd, buf, count);
* if(n < 0){
* errstr(errbuf);
* if(strcmp(errbuf, "interrupted") == 0)
* continue;
* clog("niwrite error: %s\n", errbuf);
* werrstr(errbuf);
* }
* break;
* }
* return n;
*}
*/
long
niwrite(int fd, void *buf, long n)
{
// int savalarm;
// savalarm = alarm(0);
n = write(fd, buf, n);
// if(savalarm > 0)
// alarm(savalarm);
return n;
}
typedef struct Namecache Namecache;
struct Namecache {
char dom[256];
ulong ipaddr;
Namecache *next;
};
Namecache *dnscache;
static Namecache*
domlookupl(void *name, int len)
{
Namecache *n, **ln;
if(len >= sizeof(n->dom))
return nil;
for(ln=&dnscache, n=*ln; n; ln=&(*ln)->next, n=*ln) {
if(strncmp(n->dom, name, len) == 0 && n->dom[len] == 0) {
*ln = n->next;
n->next = dnscache;
dnscache = n;
return n;
}
}
return nil;
}
static Namecache*
domlookup(void *name)
{
return domlookupl(name, strlen(name));
}
static Namecache*
iplookup(ulong ip)
{
Namecache *n, **ln;
for(ln=&dnscache, n=*ln; n; ln=&(*ln)->next, n=*ln) {
if(n->ipaddr == ip) {
*ln = n->next;
n->next = dnscache;
dnscache = n;
return n;
}
}
return nil;
}
static Namecache*
addcacheentry(void *name, int len, ulong ip)
{
Namecache *n;
if(len >= sizeof(n->dom))
return nil;
n = malloc(sizeof(*n));
if(n == nil)
return nil;
strncpy(n->dom, name, len);
n->dom[len] = 0;
n->ipaddr = ip;
n->next = dnscache;
dnscache = n;
return nil;
}
int
getdnsdom(ulong ip, char *name, int len)
{
char buf[128];
Namecache *nc;
char *p;
if(nc=iplookup(ip)) {
strncpy(name, nc->dom, len);
name[len-1] = 0;
return 0;
}
clog("getdnsdom: %I\n", ip);
snprint(buf, sizeof buf, "%I", ip);
p = csgetvalue("/net", "ip", buf, "dom", nil);
if(p == nil)
return -1;
strncpy(name, p, len-1);
name[len] = 0;
free(p);
addcacheentry(name, strlen(name), ip);
return 0;
}
int
getdom(ulong ip, char *dom, int len)
{
int i;
static char *prefix[] = { "", "gate-", "fddi-", "u-", 0 };
char **pr;
if(getdnsdom(ip, dom, len)<0)
return -1;
for(pr=prefix; *pr; pr++){
i = strlen(*pr);
if(strncmp(dom, *pr, i) == 0) {
memmove(dom, dom+i, len-i);
break;
}
}
return 0;
}
#define MAXCACHE 64
static Rpccache *head, *tail;
static int ncache;
static void
cachereply(Rpccall *rp, void *buf, int len)
{
Rpccache *cp;
if(nocache)
return;
if(ncache >= MAXCACHE){
if(rpcdebug)
fprint(2, "%s: drop %I/%ld, xid %uld, len %d\n",
argv0, tail->host,
tail->port, tail->xid, tail->n);
tail = tail->prev;
free(tail->next);
tail->next = 0;
--ncache;
}
cp = malloc(sizeof(Rpccache)+len-4);
if(cp == 0){
clog("cachereply: malloc %d failed\n", len);
return;
}
++ncache;
cp->prev = 0;
cp->next = head;
if(head)
head->prev = cp;
else
tail = cp;
head = cp;
cp->host = rp->host;
cp->port = rp->port;
cp->xid = rp->xid;
cp->n = len;
memmove(cp->data, buf, len);
if(rpcdebug)
fprint(2, "%s: cache %I/%ld, xid %uld, len %d\n",
argv0, cp->host, cp->port, cp->xid, cp->n);
}
static int
replycache(int fd, Rpccall *rp, long (*writemsg)(int, void*, long))
{
Rpccache *cp;
for(cp=head; cp; cp=cp->next)
if(cp->host == rp->host &&
cp->port == rp->port &&
cp->xid == rp->xid)
break;
if(cp == 0)
return 0;
if(cp->prev){ /* move to front */
cp->prev->next = cp->next;
if(cp->next)
cp->next->prev = cp->prev;
else
tail = cp->prev;
cp->prev = 0;
cp->next = head;
head->prev = cp;
head = cp;
}
(*writemsg)(fd, cp->data, cp->n);
if(rpcdebug)
fprint(2, "%s: reply %I/%ld, xid %uld, len %d\n",
argv0, cp->host, cp->port, cp->xid, cp->n);
return 1;
}
static int
Iconv(Fmt *f)
{
char buf[16];
ulong h;
h = va_arg(f->args, ulong);
snprint(buf, sizeof buf, "%ld.%ld.%ld.%ld",
(h>>24)&0xff, (h>>16)&0xff,
(h>>8)&0xff, h&0xff);
return fmtstrcpy(f, buf);
}