shithub: purgatorio

ref: 1dbb193077af7ba6ff7fb70a4dd465480764382e
dir: /tools/libstyx/styxserver.c/

View raw version
#include <lib9.h>
#include <styx.h>
#include "styxserver.h"
#include "styxaux.h"

#define MAXSTAT	512
#define EMSGLEN			256		/* %r */

#define TABSZ	32	/* power of 2 */

static	unsigned long		boottime;
static	char*	eve = "inferno";
static	int		Debug = 0;

char Enomem[] =		"out of memory";
char Eperm[] =			"permission denied";
char Enodev[] =		"no free devices";
char Ehungup[] =		"write to hungup channel";
char	Eexist[] =			"file exists";
char Enonexist[] =		"file does not exist";
char Ebadcmd[] =		"bad command";
char Ebadarg[] =			"bad arg in system call";
char Enofid[] =			"no such fid";
char Enotdir[] =		"not a directory";
char	Eopen[] =			"already open";
char	Ebadfid[] =		"bad fid";

/* client state */
enum{
	CDISC = 01,
	CNREAD = 02,
	CRECV = 04,
};

typedef struct Walkqid Walkqid;

struct Fid
{
	Client 	*client;
	Fid *next;
	short	fid;
	ushort	open;
	ushort	mode;	/* read/write */
	ulong	offset;	/* in file */
	int		dri;		/* dirread index */
	Qid		qid;
};

struct Walkqid
{
	Fid	*clone;
	int	nqid;
	Qid	qid[1];
};

#define ASSERT(A,B) styxassert((int)A,B)

static int hash(Path);
static void deletefids(Client *);

static void
styxfatal(char *fmt, ...)
{
	char buf[1024], *out;
	va_list arg;
	out = seprint(buf, buf+sizeof(buf), "Fatal error: ");
	va_start(arg, fmt);
	out = vseprint(out, buf+sizeof(buf), fmt, arg);
	va_end(arg);
	write(2, buf, out-buf);
	styxexit(1);
}

static void
styxassert(int vtrue, char *reason)
{
	if(!vtrue)
		styxfatal("assertion failed: %s\n", reason);
}

void *
styxmalloc(int bytes)
{
	char *m = malloc(bytes);
	if(m == nil)
		styxfatal(Enomem);
	memset(m, 0, bytes);
	return m;
}

void
styxfree(void *p)
{
	free(p);
}

void
styxdebug()
{
	Debug = 1;
}

static Client *
newclient(Styxserver *server, int fd)
{
	Client *c = (Client *)styxmalloc(sizeof(Client));

	if(Debug)
		fprint(2, "New client at %lux\n", (ulong)c);
	c->server = server;
	c->fd = fd;
	c->nread = 0;
	c->nc = 0;
	c->state = 0;
	c->fids = nil;
	c->uname = strdup(eve);
	c->aname = strdup("");
	c->next = server->clients;
	server->clients = c;
	if(server->ops->newclient)
		server->ops->newclient(c);
	return c;
}

static void
freeclient(Client *c)
{
	Client **p;
	Styxserver *server;

	if(Debug)
		fprint(2, "Freeing client at %lux\n", (ulong)c);
	server = c->server;
	if(server->ops->freeclient)
		server->ops->freeclient(c);
	for(p = &server->clients; *p; p = &(*p)->next)
		if(*p == c){
			styxclosesocket(c->fd);
			*p = c->next;
			deletefids(c);
			free(c->uname);
			free(c->aname);
			styxfree(c);
			return;
		}
}

static int
nbread(Client *c, int nr)
{
	int nb;

	if(c->state&CDISC)
		return -1;
	nb = styxrecv(c->server, c->fd, c->msg + c->nread, nr, 0);
	if(nb <= 0){
		c->nread = 0;
		c->state |= CDISC;
		return -1;
	}
	c->nread += nb;
	return 0;
}

static int
rd(Client *c, Fcall *r)
{
	if(c->nc > 0){	/* last convM2S consumed nc bytes */
		c->nread -= c->nc;
		if((int)c->nread < 0){
			r->ename = "negative size in rd";
			return -1;
		}
		memmove(c->msg, c->msg+c->nc, c->nread);
		c->nc = 0;
	}
	if(c->state&CRECV){
		if(nbread(c, MSGMAX - c->nread) != 0){
			r->ename = "unexpected EOF";
			return -1;
		}
		c->state &= ~CRECV;
	}
	c->nc = convM2S((uchar*)(c->msg), c->nread, r);
	if(c->nc < 0){
		r->ename = "bad message format";
		return -1;
	}
	if(c->nc == 0 && c->nread > 0){
		c->nread = 0;
		c->state &= ~CNREAD;
		return 0;
	}
	if(c->nread > c->nc)
		c->state |= CNREAD;
	else
		c->state &= ~CNREAD;
	if(c->nc == 0)
		return 0;
	/* fprint(2, "rd: %F\n", r); */
	return 1;
}

static int
wr(Client *c, Fcall *r)
{
	int n;
	char buf[MSGMAX];

	n = convS2M(r, (uchar*)buf, sizeof(buf));
	if(n < 0){
		r->ename = "bad message type in wr";
		return -1;
	}
	/* fprint(2, "wr: %F\n", r); */
	return styxsend(c->server, c->fd, buf, n, 0);
}

static void
sremove(Styxserver *server, Styxfile *f)
{
	Styxfile *s, *next, **p;

	if(f == nil)
		return;
	if(Debug)
		fprint(2, "Remove file %s Qid=%llx\n", f->d.name, f->d.qid.path);
	if(f->d.qid.type&QTDIR)
		for(s = f->child; s != nil; s = next){
			next = s->sibling;
			sremove(server, s);
	}
	for(p = &server->ftab[hash(f->d.qid.path)]; *p; p = &(*p)->next)
		if(*p == f){
			*p = f->next;
			break;
		}
	for(p = &f->parent->child; *p; p = &(*p)->sibling)
		if(*p == f){
			*p = f->sibling;
			break;
		}
	styxfree(f->d.name);
	styxfree(f->d.uid);
	styxfree(f->d.gid);
	styxfree(f);
}

int
styxrmfile(Styxserver *server, Path qid)
{
	Styxfile *f;

	f = styxfindfile(server, qid);
	if(f != nil){
		if(f->parent == nil)
			return -1;
		sremove(server, f);
		return 0;
	}
	return -1;
}

static void
incref(Styxfile *f)
{
	if(f != nil)
		f->ref++;
}

static void
decref(Styxfile *f)
{
	if(f != nil)
		--f->ref;
}

static void
increff(Fid *f)
{
	incref(styxfindfile(f->client->server, f->qid.path));
}

static void
decreff(Fid *f)
{
	decref(styxfindfile(f->client->server, f->qid.path));
}

static void
incopen(Fid *f)
{
	Styxfile *file;

	if(f->open && (file = styxfindfile(f->client->server, f->qid.path)) != nil)
		file->open++;
}

static void
decopen(Fid *f)
{
	Styxfile *file;

	if(f->open && (file = styxfindfile(f->client->server, f->qid.path)) != nil)
		file->open--;
}

int
styxperm(Styxfile *f, char *uid, int mode)
{
	int m, p;

	p = 0;
	switch(mode&3){
	case OREAD:	p = AREAD;	break;
	case OWRITE:	p = AWRITE;	break;
	case ORDWR:	p = AREAD+AWRITE;	break;
	case OEXEC:	p = AEXEC;	break;
	}
	if(mode&OTRUNC)
		p |= AWRITE;
	m = f->d.mode&7;
	if((p&m) == p)
		return 1;
	if(strcmp(f->d.uid, uid) == 0){
		m |= (f->d.mode>>6)&7;
		if((p&m) == p)
			return 1;
	}
	if(strcmp(f->d.gid, uid) == 0){
		m |= (f->d.mode>>3)&7;
		if((p&m) == p)
			return 1;
	}
	return 0;
}

static int
hash(Path path)
{
	return path&(TABSZ-1);
}

Styxfile *
styxfindfile(Styxserver *server, Path path)
{
	Styxfile *f;

	for(f = server->ftab[hash(path)]; f != nil; f = f->next){
		if(f->d.qid.path == path)
			return f;
	}
	return nil;
}

static Fid *
findfid(Client *c, short fid)
{
	Fid *f;
	for(f = c->fids; f && f->fid != fid; f = f->next)
		;
	return f;
}

static void
deletefid(Client *c, Fid *d)
{
	/* TODO: end any outstanding reads on this fid */
	Fid **f;

	for(f = &c->fids; *f; f = &(*f)->next)
		if(*f == d){
			decreff(d);
			decopen(d);
			*f = d->next;
			styxfree(d);
			return;
		}
}

static void
deletefids(Client *c)
{
	Fid *f, *g;

	for(f = c->fids; f; f = g){
		decreff(f);
		decopen(f);
		g = f->next;
		styxfree(f);
	}
}

Fid *
newfid(Client *c, short fid, Qid qid){
	Fid *f;

	f = styxmalloc(sizeof(Fid));
	ASSERT(f, "newfid");
	f->client = c;
	f->fid = fid;
	f->open = 0;
	f->dri = 0;
	f->qid = qid;
	f->next = c->fids;
	c->fids = f;
	increff(f);
	return f;
}

static void
flushtag(int oldtag)
{
	USED(oldtag);
}

int
eqqid(Qid a, Qid b)
{
	return a.path == b.path && a.vers == b.vers;
}

static Fid *
fidclone(Fid *old, short fid)
{
	Fid *new;

	new = newfid(old->client, fid, old->qid);
	return new;
}

static Walkqid*
devwalk(Client *c, Styxfile *file, Fid *fp, Fid *nfp, char **name, int nname, char **err)
{
	Styxserver *server;
	long j;
	Walkqid *wq;
	char *n;
	Styxfile *p, *f;
	Styxops *ops;
	Qid qid;

	*err = nil;
	server = c->server;
	ops = server->ops;

	wq = styxmalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
	wq->nqid = 0;

	p = file;
	qid = p != nil ? p->d.qid : fp->qid;
	for(j = 0; j < nname; j++){
		if(!(qid.type&QTDIR)){
			if(j == 0)
				styxfatal("devwalk error");
			*err = Enotdir;
			goto Done;
		}
		if(p != nil && !styxperm(p, c->uname, OEXEC)){
			*err = Eperm;
			goto Done;
		}
		n = name[j];
		if(strcmp(n, ".") == 0){
    Accept:
			wq->qid[wq->nqid++] = nfp->qid;
			continue;
		}
		if(p != nil && strcmp(n, "..") == 0 && p->parent){
			decref(p);
			nfp->qid.path = p->parent->d.qid.path;
			nfp->qid.type = p->parent->d.qid.type;
			nfp->qid.vers = 0;
			incref(p->parent);
			p = p->parent;
			qid = p->d.qid;
			goto Accept;
		}
		
		if(ops->walk != nil){
			char *e;

			e = ops->walk(&qid, n);
			if(e == nil){
				decreff(nfp);
				nfp->qid = qid;
				increff(nfp);
				p = styxfindfile(server, qid.path);
				if(server->needfile && p == nil)
					goto Done;
				qid = p != nil ? p->d.qid : nfp->qid;
				goto Accept;
			}
		}

		if(p != nil)
		for(f = p->child; f != nil; f = f->sibling){
			if(strcmp(n, f->d.name) == 0){
				decref(p);
				nfp->qid.path = f->d.qid.path;
				nfp->qid.type = f->d.qid.type;
				nfp->qid.vers = 0;
				incref(f);
				p = f;
				qid = p->d.qid;
				goto Accept;
			}
		}
		if(j == 0 && *err == nil)
			*err = Enonexist;
		goto Done;
	}
Done:
	if(*err != nil){
		styxfree(wq);
		return nil;
	}
	return wq;
}

static long
devdirread(Fid *fp, Styxfile *file, char *d, long n)
{
	long dsz, m;
	Styxfile *f;
	int i;

	struct{
		Dir d;
		char slop[100];	/* TO DO */
	}dir;

	f = file->child;
	for(i = 0; i < fp->dri; i++)
		if(f == 0)
			return 0;
		else
			f = f->sibling;
	for(m = 0; m < n; fp->dri++){
		if(f == nil)
			break;
		dir.d = f->d;
		dsz = convD2M(&dir.d, (uchar*)d, n-m);
		m += dsz;
		d += dsz;
		f = f->sibling;
	}

	return m;
}

static char*
nilconv(char *s)
{
	if(s != nil && s[0] == '\0')
		return nil;
	return s;
}

static Styxfile *
newfile(Styxserver *server, Styxfile *parent, int isdir, Path qid, char *name, int mode, char *owner)
{
	Styxfile *file;
	Dir d;
	int h;

	if(qid == -1)
		qid = server->qidgen++;
	file = styxfindfile(server, qid);
	if(file != nil)
		return nil;
	if(parent != nil){
		for(file = parent->child; file != nil; file = file->sibling)
			if(strcmp(name, file->d.name) == 0)
				return nil;
	}
	file = (Styxfile *)styxmalloc(sizeof(Styxfile));
	file->parent = parent;
	file->child = nil;
	h = hash(qid);
	file->next = server->ftab[h];
	server->ftab[h] = file;
	if(parent){
		file->sibling = parent->child;
		parent->child = file;
	}else
		file->sibling = nil;

	d.type = 'X';
	d.dev = 'x';
	d.qid.path = qid;
	d.qid.type = 0;
	d.qid.vers = 0;
	d.mode = mode;
	d.atime = time(0);
	d.mtime = boottime;
	d.length = 0;
	d.name = strdup(name);
	d.uid = strdup(owner);
	d.gid = strdup(eve);
	d.muid = "";

	if(isdir){
		d.qid.type |= QTDIR;
		d.mode |= DMDIR;
	}
	else{
		d.qid.type &= ~QTDIR;
		d.mode &= ~DMDIR;
	}

	file->d = d;
	file->ref = 0;
	file->open = 0;
	if(Debug)
		fprint(2, "New file %s Qid=%llx\n", name, qid);
	return file;
}

static void
run(Client *c)
{
	Fcall f;
	Fid *fp, *nfp;
	int i, open, mode;
	char ebuf[EMSGLEN];
	Walkqid *wq;
	Styxfile *file;
	Dir dir;
	Qid qid;
	Styxops *ops;
	char strs[128];

	ebuf[0] = 0;
	if(rd(c, &f) <= 0)
		return;
	if(f.type == Tflush){
		flushtag(f.oldtag);
		f.type = Rflush;
		wr(c, &f);
		return;
	}
	ops = c->server->ops;
	file = nil;
	fp = findfid(c, f.fid);
	if(f.type != Tversion && f.type != Tauth && f.type != Tattach){
		if(fp == nil){
			f.type = Rerror;
			f.ename = Enofid;
			wr(c, &f);
			return;
		}
		else{
			file = styxfindfile(c->server, fp->qid.path);
			if(c->server->needfile && file == nil){
				f.type = Rerror;
				f.ename = Enonexist;
				wr(c, &f);
				return;
			}
		}
	}
	/* if(fp == nil) fprint(2, "fid not found for %d\n", f.fid); */
	switch(f.type){
	case	Twalk:
		if(Debug){
			fprint(2, "Twalk %d %d", f.fid, f.newfid);
			for(i = 0; i < f.nwname; i++)
				fprint(2, " %s", f.wname[i]);
			fprint(2, "\n");
		}
		nfp = findfid(c, f.newfid);
		f.type = Rerror;
		if(nfp){
			deletefid(c, nfp);
			nfp = nil;
		}
		if(nfp){
			f.ename = "fid in use";
			if(Debug) fprint(2, "walk: %s\n", f.ename);
			wr(c, &f);
			break;
		}else if(fp->open){
			f.ename = "can't clone";
			wr(c, &f);
			break;
		}
		if(f.newfid != f.fid)
			nfp = fidclone(fp, f.newfid);
		else
			nfp = fp;
		if((wq = devwalk(c, file, fp, nfp, f.wname, f.nwname, &f.ename)) == nil){
			if(nfp != fp)
				deletefid(c, nfp);
			f.type = Rerror;
		}else{
			if(nfp != fp){
				if(wq->nqid != f.nwname)
					deletefid(c, nfp);
			}
			f.type = Rwalk;
			f.nwqid = wq->nqid;
			for(i = 0; i < wq->nqid; i++)
				f.wqid[i] = wq->qid[i];
			styxfree(wq);
		}
		wr(c, &f);
		break;
	case	Topen:
		if(Debug)
			fprint(2, "Topen %d\n", f.fid);
		f.ename = nil;
		if(fp->open)
			f.ename = Eopen;
		else if((fp->qid.type&QTDIR) && (f.mode&(OWRITE|OTRUNC|ORCLOSE)))
			f.ename = Eperm;
		else if(file != nil && !styxperm(file, c->uname, f.mode))
			f.ename = Eperm;
		else if((f.mode&ORCLOSE) && file != nil && file->parent != nil && !styxperm(file->parent, c->uname, OWRITE))
			f.ename = Eperm;
		if(f.ename != nil){
			f.type = Rerror;
			wr(c, &f);
			break;
		}
		f.ename = Enonexist;
		decreff(fp);
		if(ops->open == nil || (f.ename = ops->open(&fp->qid, f.mode)) == nil){
			f.type = Ropen;
			f.qid = fp->qid;
			fp->mode = f.mode;
			fp->open = 1;
			fp->offset = 0;
			incopen(fp);
		}
		else
			f.type = Rerror;
		increff(fp);
		wr(c, &f);
		break;
	case	Tcreate:
		if(Debug)
			fprint(2, "Tcreate %d %s\n", f.fid, f.name);
		f.ename = nil;
		if(fp->open)
			f.ename = Eopen;
		else if(!(fp->qid.type&QTDIR))
			f.ename = Enotdir;
		else if((f.perm&DMDIR) && (f.mode&(OWRITE|OTRUNC|ORCLOSE)))
			f.ename = Eperm;
		else if(file != nil && !styxperm(file, c->uname, OWRITE))
			f.ename = Eperm;
		if(f.ename != nil){
			f.type = Rerror;
			wr(c, &f);
			break;
		}
		f.ename = Eperm;
		decreff(fp);
		if(file != nil){
			if(f.perm&DMDIR)
				f.perm = (f.perm&~0777) | (file->d.mode&f.perm&0777) | DMDIR;
			else
				f.perm = (f.perm&(~0777|0111)) | (file->d.mode&f.perm&0666);
		}
		if(ops->create && (f.ename = ops->create(&fp->qid, f.name, f.perm, f.mode)) == nil){
			f.type = Rcreate;
			f.qid = fp->qid;
			fp->mode = f.mode;
			fp->open = 1;
			fp->offset = 0;
			incopen(fp);
		}
		else
			f.type = Rerror;
		increff(fp);
		wr(c, &f);
		break;
	case	Tread:
		if(Debug)
			fprint(2, "Tread %d\n", f.fid);
		if(!fp->open){
			f.type = Rerror;
			f.ename = Ebadfid;
			wr(c, &f);
			break;
		}
		if(fp->qid.type&QTDIR || (file != nil && file->d.qid.type&QTDIR)){
			f.type = Rread;
			if(file == nil){
				f.ename = Eperm;
				if(ops->read && (f.ename = ops->read(fp->qid, c->data, (ulong*)(&f.count), fp->dri)) == nil){
					f.data = c->data;
				}
				else
					f.type = Rerror;
			}
			else{
				f.count = devdirread(fp, file, c->data, f.count);
				f.data = c->data;
			}		
		}else{
			f.ename = Eperm;
			f.type = Rerror;
			if(ops->read && (f.ename = ops->read(fp->qid, c->data, (ulong*)(&f.count), f.offset)) == nil){
				f.type = Rread;
				f.data = c->data;			
			}
		}
		wr(c, &f);
		break;
	case	Twrite:
		if(Debug)
			fprint(2, "Twrite %d\n", f.fid);
		if(!fp->open){
			f.type = Rerror;
			f.ename = Ebadfid;
			wr(c, &f);
			break;
		}
		f.ename = Eperm;
		f.type = Rerror;
		if(ops->write && (f.ename = ops->write(fp->qid, f.data, (ulong*)(&f.count), f.offset)) == nil){
			f.type = Rwrite;
		}
		wr(c, &f);
		break;
	case	Tclunk:
		if(Debug)
			fprint(2, "Tclunk %d\n", f.fid);
		open = fp->open;
		mode = fp->mode;
		qid = fp->qid;
		deletefid(c, fp);
		f.type = Rclunk;
		if(open && ops->close && (f.ename = ops->close(qid, mode)) != nil)
			f.type = Rerror;
		wr(c, &f);
		break;
	case	Tremove:
		if(Debug)
			fprint(2, "Tremove %d\n", f.fid);
		if(file != nil && file->parent != nil && !styxperm(file->parent, c->uname, OWRITE)){
			f.type = Rerror;
			f.ename = Eperm;
			deletefid(c, fp);
			wr(c, &f);
			break;
		}
		f.ename = Eperm;
		if(ops->remove && (f.ename = ops->remove(fp->qid)) == nil) 
			f.type = Rremove;
		else
			f.type = Rerror;
		deletefid(c, fp);
		wr(c, &f);
		break;
	case	Tstat:
		if(Debug)
			fprint(2, "Tstat %d qid=%llx\n", f.fid, fp->qid.path);
		f.stat = styxmalloc(MAXSTAT);
		f.ename = "stat error";
		if(ops->stat == nil && file != nil){
			f.type = Rstat;
			f.nstat = convD2M(&file->d, f.stat, MAXSTAT);
		}
		else if(ops->stat && (f.ename = ops->stat(fp->qid, &dir)) == nil){
			f.type = Rstat;
			f.nstat = convD2M(&dir, f.stat, MAXSTAT);
		}
		else
			f.type = Rerror;
		wr(c, &f);
		styxfree(f.stat);
		break;
	case	Twstat:
		if(Debug)
			fprint(2, "Twstat %d\n", f.fid);
		f.ename = Eperm;
		convM2D(f.stat, f.nstat, &dir, strs);
		dir.name = nilconv(dir.name);
		dir.uid = nilconv(dir.uid);
		dir.gid = nilconv(dir.gid);
		dir.muid = nilconv(dir.muid);
		if(ops->wstat && (f.ename = ops->wstat(fp->qid, &dir)) == nil)
			f.type = Rwstat;
		else
			f.type = Rerror;
		wr(c, &f);
		break;
	case	Tversion:
		if(Debug)
			fprint(2, "Tversion\n");
		f.type = Rversion;
		f.tag = NOTAG;
		wr(c, &f);
		break;
	case Tauth:
		if(Debug)
			fprint(2, "Tauth\n");
		f.type = Rauth;
		wr(c, &f);
		break;
	case	Tattach:
		if(Debug)
			fprint(2, "Tattach %d %s %s\n", f.fid, f.uname[0] ? f.uname : c->uname, f.aname[0]? f.aname: c->aname);
		if(fp){
			f.type = Rerror;
			f.ename = "fid in use";
		}else{
			Qid q;

			if(f.uname[0]){
				free(c->uname);
				c->uname = strdup(f.uname);
			}
			if(f.aname[0]){
				free(c->aname);
				c->aname = strdup(f.aname);
			}
			q.path = Qroot;
			q.type = QTDIR;
			q.vers = 0;
			fp = newfid(c, f.fid, q);
			f.type = Rattach;
			f.fid = fp->fid;
			f.qid = q;
			if(ops->attach && (f.ename = ops->attach(c->uname, c->aname)) != nil)
				f.type = Rerror;
		}
		wr(c, &f);
		break;
	}
}

char *
styxinit(Styxserver *server, Styxops *ops, char *port, int perm, int needfile)
{
	int i;

	if(Debug)
		fprint(2, "Initialising Styx server on port %s\n", port);
	if(perm == -1)
		perm = 0555;
	server->ops = ops;
	server->clients = nil;
	server->root = nil;
	server->ftab = (Styxfile**)malloc(TABSZ*sizeof(Styxfile*));
	for(i = 0; i < TABSZ; i++)
		server->ftab[i] = nil;
	server->qidgen = Qroot+1;
	if(styxinitsocket() < 0)
		return "styxinitsocket failed";
	server->connfd = styxannounce(server, port);
	if(server->connfd < 0)
		return "can't announce on network port";
	styxinitwait(server);
	server->root = newfile(server, nil, 1, Qroot, "/", perm|DMDIR, eve);
	server->needfile = needfile;
	return nil;
}

char*
styxend(Styxserver *server)
{
	USED(server);
	styxendsocket();
	return nil;
}

char *
styxwait(Styxserver *server)
{
	return styxwaitmsg(server);
}

char *
styxprocess(Styxserver *server)
{
	Client *c;
	int s;

	if(styxnewcall(server)){
		s = styxaccept(server);
		if(s >= 0){
			newclient(server, s);
			styxnewclient(server, s);
		}
	}
	for(c = server->clients; c != nil; ){
		Client *next = c->next;

		server->curc = c;
		if(c->fd >= 0 && styxnewmsg(server, c->fd))
			c->state |= CRECV;
		if(c->state&(CNREAD|CRECV)){
			if(c->state&CDISC){
				styxfreeclient(server, c->fd);
				freeclient(c);
			}else
				do
					run(c);
				while(c->state&CNREAD);
		}
		c = next;
	}
	
	return nil;
}

Client*
styxclient(Styxserver *server)
{
	return server->curc;
}

Styxfile*
styxaddfile(Styxserver *server, Path pqid, Path qid, char *name, int mode, char *owner)
{
	Styxfile *f, *parent;

	parent = styxfindfile(server, pqid);
	if(parent == nil || (parent->d.qid.type&QTDIR) == 0)
		return nil;
	f = newfile(server, parent, 0, qid, name, mode, owner);
	return f;
}

Styxfile*
styxadddir(Styxserver *server, Path pqid, Path qid, char *name, int mode, char *owner)
{
	Styxfile *f, *parent;

	parent = styxfindfile(server, pqid);
	if(parent == nil || (parent->d.qid.type&QTDIR) == 0)
		return nil;
	f = newfile(server, parent, 1, qid, name, mode|DMDIR, owner);
	return f;
}

long
styxreadstr(ulong off, char *buf, ulong n, char *str)
{
	int size;

	size = strlen(str);
	if(off >= size)
		return 0;
	if(off+n > size)
		n = size-off;
	memmove(buf, str+off, n);
	return n;
}

Qid
styxqid(int path, int isdir)
{
	Qid q;

	q.path = path;
	q.vers = 0;
	if(isdir)
		q.type = QTDIR;
	else
		q.type = 0;
	return q;
}

void
styxsetowner(char *name)
{
	eve = name;
}