~ft/c9

e5e39adf2d8810e8a0aca2acf4e825ebcb21e3ed — Sigrid Solveig Haflínudóttir 1 year, 2 months ago aa98d28 master
wip revamping
9 files changed, 676 insertions(+), 66 deletions(-)

M README.md
A examples/dial.c
A examples/fs9ptest.c
A examples/sockets.c
A fs9p.c
A include/c9/fs.h
A include/c9/fs9p.h
R c9.h => include/c9/proto.h
R c9.c => proto.c
M README.md => README.md +21 -4
@@ 1,8 1,25 @@
# c9

Low level 9p client and server.
NOTE (2022-06-27): the library's (yep, it's going to be a library)
API is getting completely revamped, and examples are being added.
Use either an older version, or expect API breakage.

## Examples
A lightweight library for implmeneting 9p clients and servers in C.

Until I have time to write a minimal example you could take a look at 
https://git.sr.ht/~ft/9pro/blob/master/9pex.c
This is 9p client and server implementation which aims to be correct,
small and secure, targetting mainly low-resource MCUs.

`proto.[ch]`: lowest level of the library, contains nothing but
(de)serealization of the protocol messages and tag (de)allocation.

`fs.h`: higher level, blocking file API based on POSIX.

`fs9p.[ch]`: blocking file API implementation for 9p - having a
context created with `proto.[ch]` one can use POSIX-like file API to
work with a remote filesystem.

Goals:

* portability: zero external dependencies, no POSIX, just C
* no dynamic memory allocation
* security: no crashes due to incoming data

A examples/dial.c => examples/dial.c +50 -0
@@ 0,0 1,50 @@
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

int
dial(const char *s)
{
	struct addrinfo *r, *a, hint = {.ai_flags = AI_ADDRCONFIG, .ai_family = AF_UNSPEC, 0};
	char host[128], *port;
	int e, f;

	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;
	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)
			break;
		close(f);
		f = -1;
	}
	freeaddrinfo(r);

	return f;
}

A examples/fs9ptest.c => examples/fs9ptest.c +52 -0
@@ 0,0 1,52 @@
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include "c9/proto.h"
#include "c9/fs.h"
#include "c9/fs9p.h"

int dial(const char *s);
C9ctx *init9p(int f);

static FS9pfd fds[8];

static void
fserror(FS *fs, const char *fmt, ...)
{
	va_list ap;

	(void)fs;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

int
main(int argc, char **argv)
{
	C9ctx *ctx;
	FSstat st;
	FS fs;
	FS9p fs9 = {
		.fds = fds,
		.numfds = sizeof(fds)/sizeof(fds[0]),
	};
	int f;

	(void)argc; (void)argv;

	fs.error = fserror;
	fs.aux = (void*)&fs9;
	if((f = dial("tcp!ftrv.se!564")) < 0)
		return 1;
	ctx = init9p(f);
	fs9.ctx = ctx;
	if(fs9pinit(&fs, 8192) != 0)
		return 1;
	if(fs.fstat(&fs, FS9proot, &st) != 0)
		return 1;
	printf("%s %d %s %o\n", st.name, (int)st.size, st.uid, st.mode&0777);

	return 0;
}

A examples/sockets.c => examples/sockets.c +127 -0
@@ 0,0 1,127 @@
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include "c9/proto.h"

enum {
	Msize = 8192,

	Disconnected = 1<<0,
};

typedef struct C9aux C9aux;

struct C9aux {
	C9ctx c;
	int f;
	int flags;
	uint8_t rdbuf[Msize];
	uint8_t wrbuf[Msize];
	uint32_t wroff;
};

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)
{
	C9aux *a;

	/*
	 * To batch up requests and instead flush them all at once just return 0
	 * here and call wrsend yourself when needed.
	 */
	a = ctx->aux;
	return wrsend(a);
}

static void
ctxerror(C9ctx *ctx, const char *fmt, ...)
{
	va_list ap;

	(void)ctx;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

C9aux *
init9p(int f)
{
	C9aux *c;

	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;
}

A fs9p.c => fs9p.c +301 -0
@@ 0,0 1,301 @@
#include <stdint.h>
#include <string.h>
#include "c9/proto.h"
#include "c9/fs.h"
#include "c9/fs9p.h"

enum
{
	Flinit = 1<<0,
	Flerror = 1<<1,

	F9init = 0,
	F9chdir,
	F9open,
	F9create,
	F9read,
	F9readdir,
	F9write,
	F9seek,
	F9stat,
	F9fstat,
	F9close,
	F9remove,
	F9rename,
};

static const FS9pfd freefd =
{
	.offset = ~0ULL,
};

#define isfree(fd) ((fd).offset == ~0ULL)

static const char *f2s[] =
{
	[F9init] = "init",
	[F9chdir] = "chdir",
	[F9open] = "open",
	[F9create] = "create",
	[F9read] = "read",
	[F9readdir] = "readdir",
	[F9write] = "write",
	[F9seek] = "seek",
	[F9stat] = "stat",
	[F9fstat] = "fstat",
	[F9close] = "close",
	[F9remove] = "remove",
	[F9rename] = "rename",
};

static int
fs9pchdir(FS *fs, const char *path)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9chdir;

	return -1;
}

static int
fs9popen(FS *fs, const char *name, FSmode mode)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9open;

	return -1;
}

static int
fs9pcreate(FS *fs, const char *path, int perm)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9create;

	return -1;
}

static int64_t
fs9pread(FS *fs, int fd, void *buf, uint64_t count)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9read;

	return -1;
}

static int64_t
fs9preaddir(FS *fs, int fd, FSstat *st, int nst)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9readdir;

	return -1;
}

static int64_t
fs9pwrite(FS *fs, int fd, const void *buf, uint64_t count)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9write;

	return -1;
}

static int64_t
fs9pseek(FS *fs, int fd, int64_t offset, int whence)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9seek;

	return -1;
}

static int
fs9pstat(FS *fs, const char *path, FSstat *st)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9stat;

	return -1;
}

static int
fs9pfstat(FS *fs, int fd, FSstat *st)
{
	FS9p *aux = (void*)fs->aux;
	C9tag tag;
	int e;

	aux->f = F9fstat;
	aux->p = st;
	if((e = c9stat(aux->ctx, &tag, fd)) < 0)
		return e;
	while(aux->f != -F9fstat)
		c9proc(aux->ctx);

	return 0;
}

static int
fs9pclose(FS *fs, int fd)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9close;

	return -1;
}

static int
fs9premove(FS *fs, const char *name)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9remove;

	return -1;
}

static int
fs9prename(FS *fs, const char *oldpath, const char *newpath)
{
	FS9p *aux = (void*)fs->aux;

	aux->f = F9rename;

	return -1;
}

static void
fs9pr(C9ctx *ctx, C9r *r)
{
	FSstat *st;
	C9tag tag;
	FS9p *aux;
	FS *fs;

	fs = ctx->paux;
	aux = (void*)fs->aux;

	switch(r->type){
	case Rversion:
		c9attach(ctx, &tag, FS9proot, C9nofid, "none", NULL); /* FIXME those need to be configurable */
		break;

	case Rauth:
		break;

	case Rattach:
		aux->flags |= Flinit;
		break;

	case Rerror:
		fs->error(fs, "%s: %s", f2s[aux->f], r->error);
		aux->flags |= Flerror;
		break;

	case Rflush:
		break;

	case Rwalk:
		break;

	case Ropen:
		break;

	case Rcreate:
		break;

	case Rread:
		break;

	case Rwrite:
		break;

	case Rclunk:
		break;

	case Rremove:
		break;

	case Rstat:
		if(aux->f == F9stat || aux->f == F9fstat){
			st = aux->p;
			st->size = r->stat.size;
			st->name = r->stat.name;
			st->uid = r->stat.uid;
			st->gid = r->stat.gid;
			st->mode = r->stat.mode;
			st->mtime = r->stat.mtime;
			aux->f = -aux->f;
		}
		break;

	case Rwstat:
		break;
	}
}

int
fs9pinit(FS *fs, uint32_t msize)
{
	FS9p *aux = (void*)fs->aux;
	C9error err;
	C9tag tag;
	int i;

	if(fs->error == NULL)
		return -1;
	if(aux == NULL || aux->ctx == NULL || aux->fds == NULL || aux->numfds < 1){
		fs->error(fs, "fs9pinit: invalid aux");
		return -1;
	}
	if(aux->ctx == NULL){
		fs->error(fs, "fs9pinit: invalid ctx");
		return -1;
	}

	fs->chdir = fs9pchdir;
	fs->open = fs9popen;
	fs->create = fs9pcreate;
	fs->read = fs9pread;
	fs->readdir = fs9preaddir;
	fs->write = fs9pwrite;
	fs->seek = fs9pseek;
	fs->stat = fs9pstat;
	fs->fstat = fs9pfstat;
	fs->close = fs9pclose;
	fs->remove = fs9premove;
	fs->rename = fs9prename;

	aux->root.offset = 0;
	for(i = 0; i < aux->numfds; i++)
		aux->fds[i] = freefd;

	aux->ctx->r = fs9pr;
	aux->ctx->paux = fs;
	aux->f = F9init;
	aux->flags = 0;

	if((err = c9version(aux->ctx, &tag, msize)) != 0)
		return err;
	for(;;){
		if((err = c9proc(aux->ctx)) != 0)
			return err;
		if(aux->flags & Flerror)
			return -1;
		if(aux->flags & Flinit)
			break;
	}

	return 0;
}

A include/c9/fs.h => include/c9/fs.h +48 -0
@@ 0,0 1,48 @@
typedef struct FS FS;
typedef struct FSstat FSstat;

typedef enum
{
	FS_OREAD,
	FS_OWRITE,
	FS_ORDWR,
	FS_OEXEC,
	FS_OTRUNC = 0x10,
	FS_ORCLOSE = 0x40,
	FS_OEXCL = 0x1000,
	FS_DIR = 0x80000000U, /* open/create: it's supposed to be a directory. */
}FSmode;

struct FSstat
{
	uint64_t size; /* Size of the file (in bytes). */
	char *name;  /* Name of the file. */
	char *uid;   /* Owner of the file. */
	char *gid;   /* Group of the file. */
	uint32_t mode;   /* Permissions. See C9st* and C9perm. */
	uint64_t mtime;  /* Last modification time. Nanoseconds since epoch. */
};

struct FSaux;

struct FS
{
	/* Callback for error messages. */
	void (*error)(FS *fs, const char *fmt, ...) __attribute__((nonnull(1), format(printf, 2, 3)));

	/* Do not set these. */
	int (*chdir)(FS *fs, const char *path) __attribute__((nonnull(1, 2)));
	int (*open)(FS *fs, const char *name, FSmode mode) __attribute__((nonnull(1, 2)));
	int (*create)(FS *fs, const char *path, int perm) __attribute__((nonnull(1, 2))); 
	int64_t (*read)(FS *fs, int fd, void *buf, uint64_t count) __attribute__((nonnull(1, 3)));
	int64_t (*readdir)(FS *fs, int fd, FSstat *st, int nst);
	int64_t (*write)(FS *fs, int fd, const void *buf, uint64_t count) __attribute__((nonnull(1, 3)));
	int64_t (*seek)(FS *fs, int fd, int64_t offset, int whence) __attribute__((nonnull(1)));
	int (*stat)(FS *fs, const char *path, FSstat *st) __attribute__((nonnull(1, 2, 3)));
	int (*fstat)(FS *fs, int fd, FSstat *st) __attribute__((nonnull(1, 3)));
	int (*close)(FS *fs, int fd) __attribute__((nonnull(1)));
	int (*remove)(FS *fs, const char *name) __attribute__((nonnull(1, 2)));
	int (*rename)(FS *fs, const char *oldpath, const char *newpath) __attribute__((nonnull(1, 2, 3)));

	struct FSaux *aux;
};

A include/c9/fs9p.h => include/c9/fs9p.h +31 -0
@@ 0,0 1,31 @@
enum
{
	FS9proot = 3, /* '/' file descriptor. */
};

typedef struct FS9p FS9p;
typedef struct FS9pfd FS9pfd;

struct FS9pfd
{
	uint64_t offset;
};

struct FS9p
{
	/* The following three fields need to be set before calling fs9pinit. */

	C9ctx *ctx; /* Set to a full set up context, except "r" and "t" fields. */
	FS9pfd *fds; /* Point at the allocated fds array. */
	int numfds; /* Set to the number of entries available in fds array. */
	void *aux; /* Optional, user-supplied aux value. */

	/* Private, do not touch. */
	FS9pfd root;
	FS9pfd cwd;
	int f;
	void *p;
	int flags;
};

extern int fs9pinit(FS *fs, uint32_t msize) __attribute__((nonnull(1)));

R c9.h => include/c9/proto.h +6 -10
@@ 1,12 1,3 @@
#ifdef __plan9__
typedef u64int uint64_t;
typedef u32int uint32_t;
typedef u16int uint16_t;
typedef u8int uint8_t;
#define __attribute__(...)
#define NULL nil
#endif

struct C9aux;

typedef struct C9r C9r;


@@ 295,12 286,13 @@ struct C9ctx
	void (*t)(C9ctx *ctx, C9t *t) __attribute__((nonnull(1, 2)));

	/* Callback for error messages. */
	void (*error)(const char *fmt, ...) __attribute__((nonnull(1), format(printf, 1, 2)));
	void (*error)(C9ctx *ctx, const char *fmt, ...) __attribute__((nonnull(1), format(printf, 2, 3)));

	/* Auxiliary data, can be used by any of above callbacks. */
	struct C9aux *aux;

	/* private stuff */
	void *paux;
	uint32_t msize;
#ifndef C9_NO_CLIENT
	uint32_t flush[C9maxflush];


@@ 308,8 300,12 @@ struct C9ctx
#endif
	union
	{
#ifndef C9_NO_CLIENT
		C9tag lowfreetag;
#endif
#ifndef C9_NO_SERVER
		uint16_t svflags;
#endif
	};
};


R c9.c => proto.c +40 -52
@@ 1,18 1,6 @@
/*
 * This is 9p client and server implementation which aims to be
 * correct, small and secure. It's the lowest level implementation.
 * It doesn't have much comments, mostly because it doesn't make
 * any sense to copy-paste protocol documentation, which
 * you can read at http://man.cat-v.org/plan_9/5/, see 'intro'.
 */
#ifdef __plan9__
#include <u.h>
#include <libc.h>
#else
#include <stdint.h>
#include <string.h>
#endif
#include "c9.h"
#include "c9/proto.h"

enum
{


@@ 138,7 126,7 @@ newtag(C9ctx *c, C9ttype type, C9tag *tag)
		}
	}

	c->error("newtag: no free tags");
	c->error(c,  "newtag: no free tags");
	return C9Etag;
}



@@ 148,11 136,11 @@ freetag(C9ctx *c, C9tag tag)
	if(tag != 0xffff){
		uint32_t d = tag / C9tagsbits, m = tag % C9tagsbits;
		if(tag >= C9maxtags){
			c->error("freetag: invalid tag %ud", (uint32_t)tag);
			c->error(c, "freetag: invalid tag %ud", (uint32_t)tag);
			return -1;
		}
		if((c->tags[d] & 1<<m) != 0){
			c->error("freetag: double free for tag %ud", (uint32_t)tag);
			c->error(c, "freetag: double free for tag %ud", (uint32_t)tag);
			return -1;
		}
		if(c->lowfreetag > tag)


@@ 168,12 156,12 @@ T(C9ctx *c, uint32_t size, C9ttype type, C9tag *tag, C9error *err)
	uint8_t *p = NULL;

	if(size > c->msize-4-1-2){
		c->error("T: invalid size %ud", size);
		c->error(c, "T: invalid size %ud", size);
		*err = C9Esize;
	}else if((*err = newtag(c, type, tag)) == 0){
		size += 4+1+2;
		if((p = c->begin(c, size)) == NULL){
			c->error("T: no buffer for %ud bytes", size);
			c->error(c, "T: no buffer for %ud bytes", size);
			freetag(c, *tag);
			*err = C9Ebuf;
		}else{


@@ 193,7 181,7 @@ c9version(C9ctx *c, C9tag *tag, uint32_t msize)
	C9error err;

	if(msize < C9minmsize){
		c->error("c9version: msize too small: %ud", msize);
		c->error(c, "c9version: msize too small: %ud", msize);
		return C9Einit;
	}
	memset(c->tags, 0xff, sizeof(c->tags));


@@ 217,7 205,7 @@ c9auth(C9ctx *c, C9tag *tag, C9fid afid, const char *uname, const char *aname)
	C9error err;

	if(ulen > C9maxstr || alen > C9maxstr){
		c->error("c9auth: string too long: %ud chars", ulen > alen ? ulen : alen);
		c->error(c, "c9auth: string too long: %ud chars", ulen > alen ? ulen : alen);
		return C9Estr;
	}
	if((b = T(c, 4+2+ulen+2+alen, Tauth, tag, &err)) != NULL){


@@ 238,7 226,7 @@ c9flush(C9ctx *c, C9tag *tag, C9tag oldtag)

	for(i = 0; i < C9maxflush && c->flush[i] != (uint32_t)~0; i++);
	if(i == C9maxflush){
		c->error("c9flush: no free flush slots");
		c->error(c, "c9flush: no free flush slots");
		return C9Eflush;
	}
	if((b = T(c, 2, Tflush, tag, &err)) != NULL){


@@ 258,7 246,7 @@ c9attach(C9ctx *c, C9tag *tag, C9fid fid, C9fid afid, const char *uname, const c
	C9error err;

	if(ulen > C9maxstr || alen > C9maxstr){
		c->error("c9attach: string too long: %ud chars", ulen > alen ? ulen : alen);
		c->error(c, "c9attach: string too long: %ud chars", ulen > alen ? ulen : alen);
		return C9Estr;
	}
	if((b = T(c, 4+4+2+ulen+2+alen, Tattach, tag, &err)) != NULL){


@@ 282,13 270,13 @@ c9walk(C9ctx *c, C9tag *tag, C9fid fid, C9fid newfid, const char *path[])
	for(sz = i = 0; i < (int)sizeof(len)/sizeof(len[0]) && path[i] != NULL; i++){
		len[i] = safestrlen(path[i]);
		if(len[i] == 0 || len[i] > C9maxstr){
			c->error("c9walk: invalid path element: %ud chars", len[i]);
			c->error(c, "c9walk: invalid path element: %ud chars", len[i]);
			return C9Epath;
		}
		sz += 2 + len[i];
	}
	if(path[i] != NULL){
		c->error("c9walk: invalid elements !(0 <= %ud <= %ud)", i, C9maxpathel);
		c->error(c, "c9walk: invalid elements !(0 <= %ud <= %ud)", i, C9maxpathel);
		return C9Epath;
	}



@@ 325,7 313,7 @@ c9create(C9ctx *c, C9tag *tag, C9fid fid, const char *name, uint32_t perm, C9mod
	C9error err;

	if(nlen == 0 || nlen > C9maxstr){
		c->error("c9create: invalid name: %ud chars", nlen);
		c->error(c, "c9create: invalid name: %ud chars", nlen);
		return C9Epath;
	}
	if((b = T(c, 4+2+nlen+4+1, Tcreate, tag, &err)) != NULL){


@@ 423,11 411,11 @@ c9wstat(C9ctx *c, C9tag *tag, C9fid fid, const C9stat *s)
	C9error err;

	if(nlen == 0 || nlen > C9maxstr){
		c->error("c9wstat: invalid name: %ud chars", nlen);
		c->error(c, "c9wstat: invalid name: %ud chars", nlen);
		return C9Epath;
	}
	if(ulen > C9maxstr || glen > C9maxstr){
		c->error("c9wstat: string too long: %ud chars", ulen > glen ? ulen : glen);
		c->error(c, "c9wstat: string too long: %ud chars", ulen > glen ? ulen : glen);
		return C9Estr;
	}
	if((b = T(c, 4+2+2+statsz, Twstat, tag, &err)) != NULL){


@@ 460,20 448,20 @@ c9proc(C9ctx *c)
	err = -1;
	if((b = c->read(c, 4, &err)) == NULL){
		if(err != 0)
			c->error("c9proc: short read");
			c->error(c, "c9proc: short read");
		return err == 0 ? 0 : C9Epkt;
	}

	sz = r32(&b);
	if(sz < 7 || sz > c->msize){
		c->error("c9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
		c->error(c, "c9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
		return C9Epkt;
	}
	sz -= 4;
	err = -1;
	if((b = c->read(c, sz, &err)) == NULL){
		if(err != 0)
			c->error("c9proc: short read");
			c->error(c, "c9proc: short read");
		return err == 0 ? 0 : C9Epkt;
	}



@@ 481,7 469,7 @@ c9proc(C9ctx *c)
	r.tag = r16(&b);
	if(r.type != Rversion){
		if(r.tag >= C9maxtags){
			c->error("c9proc: invalid tag %ud", (uint32_t)r.tag);
			c->error(c, "c9proc: invalid tag %ud", (uint32_t)r.tag);
			return C9Epkt;
		}
		if(freetag(c, r.tag) != 0)


@@ 510,7 498,7 @@ c9proc(C9ctx *c)
		if(sz < 2 || (cnt = r16(&b))*13 > sz-2)
			goto error;
		if(cnt > C9maxpathel){
			c->error("c9proc: Rwalk !(%ud <= %ud)", cnt, C9maxpathel);
			c->error(c, "c9proc: Rwalk !(%ud <= %ud)", cnt, C9maxpathel);
			return C9Epath;
		}
		for(i = 0; i < cnt; i++){


@@ 580,7 568,7 @@ c9proc(C9ctx *c)
		if(sz < 4+2 || (msize = r32(&b)) < C9minmsize || (cnt = r16(&b)) > sz-4-2)
			goto error;
		if(cnt < 6 || memcmp(b, "9P2000", 6) != 0){
			c->error("invalid version");
			c->error(c, "invalid version");
			return C9Ever;
		}
		if(msize < c->msize)


@@ 593,7 581,7 @@ c9proc(C9ctx *c)
	}
	return 0;
error:
	c->error("c9proc: invalid packet type %ud", r.type);
	c->error(c, "c9proc: invalid packet type %ud", r.type);
	return C9Epkt;
}



@@ 633,7 621,7 @@ c9parsedir(C9ctx *c, C9stat *stat, uint8_t **t, uint32_t *size)
	*t += sz;
	return 0;
error:
	c->error("c9parsedir: invalid size: size=%ud sz=%ud", *size, sz);
	c->error(c, "c9parsedir: invalid size: size=%ud sz=%ud", *size, sz);
	return C9Epkt;
}



@@ 645,12 633,12 @@ R(C9ctx *c, uint32_t size, C9rtype type, C9tag tag, C9error *err)
	uint8_t *p = NULL;

	if(size > c->msize-4-1-2){
		c->error("R: invalid size %ud", size);
		c->error(c, "R: invalid size %ud", size);
		*err = C9Esize;
	}else{
		size += 4+1+2;
		if((p = c->begin(c, size)) == NULL){
			c->error("R: no buffer for %ud bytes", size);
			c->error(c, "R: no buffer for %ud bytes", size);
			*err = C9Ebuf;
		}else{
			*err = 0;


@@ 699,7 687,7 @@ s9error(C9ctx *c, C9tag tag, const char *ename)
	C9error err;

	if(len > C9maxstr){
		c->error("s9error: invalid ename: %ud chars", len);
		c->error(c, "s9error: invalid ename: %ud chars", len);
		return C9Estr;
	}
	if((b = R(c, 2+len, Rerror, tag, &err)) != NULL){


@@ 743,7 731,7 @@ s9walk(C9ctx *c, C9tag tag, C9qid *qids[])

	for(n = 0; n < C9maxpathel && qids[n] != NULL; n++);
	if(n > C9maxpathel){
		c->error("s9walk: invalid elements !(0 <= %ud <= %ud)", n, C9maxpathel);
		c->error(c, "s9walk: invalid elements !(0 <= %ud <= %ud)", n, C9maxpathel);
		return C9Epath;
	}



@@ 839,13 827,13 @@ s9readdir(C9ctx *c, C9tag tag, C9stat *st[], int *num, uint64_t *offset, uint32_
		mulen = safestrlen(s->muid);

		if(nlen == 0 || nlen > C9maxstr){
			c->error("s9readdir: invalid name: %ud chars", nlen);
			c->error(c, "s9readdir: invalid name: %ud chars", nlen);
			return C9Epath;
		}
		if(ulen > C9maxstr || glen > C9maxstr || mulen > C9maxstr){
			ulen = ulen > glen ? ulen : glen;
			ulen = ulen > mulen ? ulen : mulen;
			c->error("s9readdir: string too long: %ud chars", ulen);
			c->error(c, "s9readdir: string too long: %ud chars", ulen);
			return C9Estr;
		}



@@ 916,13 904,13 @@ s9stat(C9ctx *c, C9tag tag, const C9stat *s)
	C9error err;

	if(nlen == 0 || nlen > C9maxstr){
		c->error("s9stat: invalid name: %ud chars", nlen);
		c->error(c, "s9stat: invalid name: %ud chars", nlen);
		return C9Epath;
	}
	if(ulen > C9maxstr || glen > C9maxstr || mulen > C9maxstr){
		ulen = ulen > glen ? ulen : glen;
		ulen = ulen > mulen ? ulen : mulen;
		c->error("s9stat: string too long: %ud chars", ulen);
		c->error(c, "s9stat: string too long: %ud chars", ulen);
		return C9Estr;
	}



@@ 969,20 957,20 @@ s9proc(C9ctx *c)
	readerr = -1;
	if((b = c->read(c, 4, &readerr)) == NULL){
		if(readerr != 0)
			c->error("s9proc: short read");
			c->error(c, "s9proc: short read");
		return readerr == 0 ? 0 : C9Epkt;
	}

	sz = r32(&b);
	if(sz < 7 || sz > c->msize){
		c->error("s9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
		c->error(c, "s9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
		return C9Epkt;
	}
	sz -= 4;
	readerr = -1;
	if((b = c->read(c, sz, &readerr)) == NULL){
		if(readerr != 0)
			c->error("s9proc: short read");
			c->error(c, "s9proc: short read");
		return readerr == 0 ? 0 : C9Epkt;
	}



@@ 991,7 979,7 @@ s9proc(C9ctx *c)
	sz -= 3;

	if((c->svflags & Svver) == 0 && t.type != Tversion){
		c->error("s9proc: expected Tversion, got %ud", t.type);
		c->error(c, "s9proc: expected Tversion, got %ud", t.type);
		return C9Epkt;
	}



@@ 1035,7 1023,7 @@ s9proc(C9ctx *c)
		t.fid = r32(&b);
		t.walk.newfid = r32(&b);
		if((n = r16(&b)) > 16){
			c->error("s9proc: Twalk !(%ud <= 16)", n);
			c->error(c, "s9proc: Twalk !(%ud <= 16)", n);
			return C9Epath;
		}
		sz -= 4+4+2;


@@ 1044,7 1032,7 @@ s9proc(C9ctx *c)
				if(sz < 2 || (cnt = r16(&b)) > sz-2)
					goto error;
				if(cnt < 1){
					c->error("s9proc: Twalk invalid element [%ud]", i);
					c->error(c, "s9proc: Twalk invalid element [%ud]", i);
					return C9Epath;
				}
				b[-2] = 0;


@@ 1076,7 1064,7 @@ s9proc(C9ctx *c)
		if((cnt = r16(&b)) > sz-4)
			goto error;
		if((err = c9parsedir(c, &t.wstat, &b, &cnt)) != 0){
			c->error("s9proc");
			c->error(c, "s9proc");
			return err;
		}
		c->t(c, &t);


@@ 1111,7 1099,7 @@ s9proc(C9ctx *c)
				w32(&b, 0);
				wcs(&b, "unknown", 7);
				err = c->end(c);
				c->error("s9proc: invalid version");
				c->error(c, "s9proc: invalid version");
			}
			return C9Ever;
		}


@@ 1169,7 1157,7 @@ s9proc(C9ctx *c)
	}
	return 0;
error:
	c->error("s9proc: invalid packet (type=%ud)", t.type);
	c->error(c, "s9proc: invalid packet (type=%ud)", t.type);
	return C9Epkt;
}