shithub: purgatorio

ref: 5611a46d8fb04ec3fd60c05a1486743cea98a311
dir: /emu/port/devlogfs.c/

View raw version
#ifndef EMU
#include "u.h"
#include "../port/lib.h"
#include "../port/error.h"
#else
#include	"error.h"
#endif
#include	<dat.h>
#include	<fns.h>
#include	<kernel.h>
#include	<logfs.h>
#include	<nandfs.h>

#ifndef EMU
#define Sleep sleep
#define Wakeup wakeup
#endif

#ifndef offsetof
#define offsetof(T,X) ((ulong)&(((T*)0)->X))
#endif

typedef struct Devlogfs Devlogfs;
typedef struct DevlogfsSession DevlogfsSession;

//#define CALLTRACE

enum {
	DEVLOGFSDEBUG = 0,
	DEVLOGFSIODEBUG = 0,
	DEVLOGFSBAD = 1,
};

enum {
	Qdir,
	Qctl,
	Qusers,
	Qdump,
	Qfs,
	Qfsboot,
	Qend,
};

typedef enum DevlogfsServerState { Closed, BootOpen, NeedVersion, NeedAttach, Attached, Hungup } DevlogfsServerState;

struct Devlogfs {
	QLock qlock;
	Ref ref;
	int instance;
	int trace;	/* (debugging) trace of read/write actions */
	int nand;
	char *name;
	char *device;
	char *filename[Qend - Qfs];
	LogfsLowLevel *ll;
	Chan *flash, *flashctl;
	QLock bootqlock;
	int logfstrace;
	LogfsBoot *lb;
	/* stuff for server */
	ulong openflags;
	Fcall in;
	Fcall out;
	int reading;
	DevlogfsServerState state;
	Rendez readrendez;
	Rendez writerendez;
	uint readcount;
	ulong readbufsize;
	uchar *readbuf;
	uchar *readp;
	LogfsServer *server;
	Devlogfs *next;
};

#define MAXMSIZE 8192

static struct {
	RWlock rwlock;		/* rlock when walking, wlock when changing */
	QLock configqlock;		/* serialises addition of new configurations */
	Devlogfs *head;
	char *defname;
} devlogfslist;

static LogfsIdentityStore *is;

#ifndef EMU
char Eunknown[] = "unknown user or group id";
#endif

static	void	devlogfsfree(Devlogfs*);

#define SPLITPATH(path, qtype, instance, qid, qt) { instance = path >> 4; qid = path & 0xf; qt = qtype & QTDIR; }
#define DATAQID(q, qt) (!(qt) && (q) >= Qfs && (q) < Qend)
#define MKPATH(instance, qid) ((instance << 4) | qid)

#define PREFIX "logfs"

static char *devlogfsprefix = PREFIX;
static char *devlogfsctlname = PREFIX "ctl";
static char *devlogfsusersname = PREFIX "users";
static char *devlogfsdumpname = PREFIX "dump";
static char *devlogfsbootsuffix = "boot";
static char *devlogfs9pversion = "9P2000";

enum {
	Toshiba = 0x98,
	Samsung = 0xec,
};

static struct {
	uchar manufacturer;
	uchar device;
} nandtab[] = {
	{ 0,	0xe6 },
	{ 0,	0xea },
	{ 0,	0xe3 },
	{ 0,	0xe5 },
	{ 0,	0x73 },
	{ 0,	0x75 },
	{ 0,	0x76 },
};

static void
errorany(char *errmsg)
{
	if (errmsg)
		error(errmsg);
}

static void *
emalloc(ulong size)
{
	void *p;
	p = logfsrealloc(nil, size);
	if (p == nil)
		error(Enomem);
	return p;
}

static char *
estrdup(char *q)
{
	void *p;
	if (q == nil)
		return nil;
	p = logfsrealloc(nil, strlen(q) + 1);
	if (p == nil)
		error(Enomem);
	return strcpy(p, q);
}

static char *
estrconcat(char *a, ...)
{
	va_list l;
	char *p, *r;
	int t;

	t = strlen(a);
	va_start(l, a);
	while ((p = va_arg(l, char *)) != nil)
		t += strlen(p);

	r = logfsrealloc(nil, t + 1);
	if (r == nil)
		error(Enomem);

	strcpy(r, a);
	va_start(l, a);
	while ((p = va_arg(l, char *)) != nil)
		strcat(r, p);

	va_end(l);

	return r;
}

static int
gen(Chan *c, int i, Dir *dp, int lockit)
{
	Devlogfs *l;
	long size;
	Qid qid;
	qid.vers = 0;
	qid.type = 0;

	if (i + Qctl < Qfs) {
		switch (i + Qctl) {
		case Qctl:
			qid.path = Qctl;
			devdir(c, qid, devlogfsctlname, 0, eve, 0666, dp);
			return 1;
		case Qusers:
			qid.path = Qusers;
			devdir(c, qid, devlogfsusersname, 0, eve, 0444, dp);
			return 1;
		case Qdump:
			qid.path = Qdump;
			devdir(c, qid, devlogfsdumpname, 0, eve, 0444, dp);
			return 1;
		}
	}

	i -= Qfs - Qctl;

	if (lockit)
		rlock(&devlogfslist.rwlock);

	if (waserror()) {
		if (lockit)
			runlock(&devlogfslist.rwlock);
		nexterror();
	}

	for (l = devlogfslist.head; l; l = l->next) {
		if (i < Qend - Qfs)
			break;
		i -= Qend - Qfs;
	}

	if (l == nil) {
		poperror();
		if (lockit)
			runlock(&devlogfslist.rwlock);
		return -1;
	}

	switch (Qfs + i) {
	case Qfsboot:
		size = l->lb ? logfsbootgetsize(l->lb) : 0;
		break;
	default:
		size = 0;
		break;
	}
	/* perhaps the user id should come from the underlying file */
	qid.path = MKPATH(l->instance, Qfs + i);
	devdir(c, qid, l->filename[i], size, eve, 0666, dp);

	poperror();
	if (lockit)
		runlock(&devlogfslist.rwlock);

	return 1;
}

static int
devlogfsgen(Chan *c, char *n, Dirtab *tab, int ntab, int i, Dir *dp)
{
	USED(n);
	USED(tab);
	USED(ntab);
	return gen(c, i, dp, 1);
}

static int
devlogfsgennolock(Chan *c, char *n, Dirtab *tab, int ntab, int i, Dir *dp)
{
	USED(n);
	USED(tab);
	USED(ntab);
	return gen(c, i, dp, 0);
}

/* called under lock */
static Devlogfs *
devlogfsfind(int instance)
{
	Devlogfs *l;

	for (l = devlogfslist.head; l; l = l->next)
		if (l->instance == instance)
			break;
	return l;
}

static Devlogfs *
devlogfsget(int instance)
{
	Devlogfs *l;
	rlock(&devlogfslist.rwlock);
	for (l = devlogfslist.head; l; l = l->next)
		if (l->instance == instance)
			break;
	if (l)
		incref(&l->ref);
	runlock(&devlogfslist.rwlock);
	return l;
}

static Devlogfs *
devlogfsfindbyname(char *name)
{
	Devlogfs *l;

	rlock(&devlogfslist.rwlock);
	for (l = devlogfslist.head; l; l = l->next)
		if (strcmp(l->name, name) == 0)
			break;
	runlock(&devlogfslist.rwlock);
	return l;
}

static Devlogfs *
devlogfssetdefname(char *name)
{
	Devlogfs *l;
	char *searchname;
	wlock(&devlogfslist.rwlock);
	if (waserror()) {
		wunlock(&devlogfslist.rwlock);
		nexterror();
	}
	if (name == nil)
		searchname = devlogfslist.defname;
	else
		searchname = name;
	for (l = devlogfslist.head; l; l = l->next)
		if (strcmp(l->name, searchname) == 0)
			break;
	if (l == nil) {
		logfsfreemem(devlogfslist.defname);
		devlogfslist.defname = nil;
	}
	else if (name) {
		if (devlogfslist.defname) {
			logfsfreemem(devlogfslist.defname);
			devlogfslist.defname = nil;
		}
		devlogfslist.defname = estrdup(name);
	}
	poperror();
	wunlock(&devlogfslist.rwlock);
	return l;
}

static Chan *
devlogfskopen(char *name, char *suffix, int mode)
{
	Chan *c;
	char *fn;
	int fd;

	fn = estrconcat(name, suffix, 0);
	fd = kopen(fn, mode);
	logfsfreemem(fn);
	if (fd < 0)
		error(up->env->errstr);
	c = fdtochan(up->env->fgrp, fd, mode, 0, 1);
	kclose(fd);
	return c;
}

static char *
xread(void *a, void *buf, long nbytes, ulong offset)
{
	Devlogfs *l = a;
	long rv;

	if (DEVLOGFSIODEBUG || l->trace)
		print("devlogfs: %s: read(0x%lux, %ld)\n", l->device, offset, nbytes);
	l->flash->offset = offset;
	rv = kchanio(l->flash, buf, nbytes, OREAD);
	if (rv < 0) {
		print("devlogfs: %s: flash read error: %s\n", l->device, up->env->errstr);
		return up->env->errstr;
	}
	if (rv != nbytes) {
		print("devlogfs: %s: short flash read: offset %lud, %ld not %ld\n", l->device, offset, rv, nbytes);
		return "short read";
	}
	return nil;
}

static char *
xwrite(void *a, void *buf, long nbytes, ulong offset)
{
	Devlogfs *l = a;
	long rv;

	if (DEVLOGFSIODEBUG || l->trace)
		print("devlogfs: %s: write(0x%lux, %ld)\n", l->device, offset, nbytes);
	l->flash->offset = offset;
	rv = kchanio(l->flash, buf, nbytes, OWRITE);
	if (rv < 0) {
		print("devlogfs: %s: flash write error: %s\n", l->device, up->env->errstr);
		return up->env->errstr;
	}
	if (rv != nbytes) {
		print("devlogfs: %s: short flash write: offset %lud, %ld not %ld\n", l->device, offset, rv, nbytes);
		return "short write";
	}
	return nil;
}

static char *
xerase(void *a, long address)
{
	Devlogfs *l = a;
	char cmd[40];

	if (DEVLOGFSIODEBUG || l->trace)
		print("devlogfs: %s: erase(0x%lux)\n", l->device, address);
	snprint(cmd, sizeof(cmd), "erase 0x%8.8lux", address);
	if (kchanio(l->flashctl, cmd, strlen(cmd), OWRITE) <= 0) {
		print("devlogfs: %s: flash erase error: %s\n", l->device, up->env->errstr);
		return up->env->errstr;
	}
	return nil;
}

static char *
xsync(void *a)
{
	Devlogfs *l = a;
	uchar statbuf[STATFIXLEN];

	if (DEVLOGFSIODEBUG || l->trace)
		print("devlogfs: %s: sync()\n", l->device);
	memset(statbuf, 0xff, sizeof(statbuf));
	memset(statbuf + STATFIXLEN - 8, 0x00, 8);
	PBIT16(statbuf, sizeof(statbuf) - BIT16SZ);
	if (kwstat(l->device, statbuf, sizeof(statbuf)) < 0)
		return up->env->errstr;
	return nil;
}

//#define LEAKHUNT
#ifdef LEAKHUNT
#define MAXLIVE 2000
typedef struct Live {
	void *p;
	int freed;
	ulong callerpc;
} Live;

static Live livemem[MAXLIVE];

static void
leakalloc(void *p, ulong callerpc)
{
	int x;
	int use = -1;
	for (x = 0; x < MAXLIVE; x++) {
		if (livemem[x].p == p) {
			if (!livemem[x].freed)
				print("leakalloc: unexpected realloc of 0x%.8lux from 0x%.8lux\n", p, callerpc);
//			else
//				print("leakalloc: reusing address 0x%.8lux from 0x%.8lux\n", p, callerpc);
			livemem[x].freed = 0;
			livemem[x].callerpc = callerpc;
			return;
		}
		else if (use < 0 && livemem[x].p == 0)
			use = x;
	}
	if (use < 0)
		panic("leakalloc: too many live entries");
	livemem[use].p = p;
	livemem[use].freed = 0;
	livemem[use].callerpc = callerpc;
}

static void
leakaudit(void)
{
	int x;
	for (x = 0; x < MAXLIVE; x++) {
		if (livemem[x].p && !livemem[x].freed)
			print("leakaudit: 0x%.8lux from 0x%.8lux\n", livemem[x].p, livemem[x].callerpc);
	}
}

static void
leakfree(void *p, ulong callerpc)
{
	int x;
	if (p == nil)
		return;
	for (x = 0; x < MAXLIVE; x++) {
		if (livemem[x].p == p) {
			if (livemem[x].freed)
				print("leakfree: double free of 0x%.8lux from 0x%.8lux, originally by 0x%.8lux\n",
					p, callerpc, livemem[x].callerpc);
			livemem[x].freed = 1;
			livemem[x].callerpc = callerpc;
			return;
		}
	}
	print("leakfree: free of unalloced address 0x%.8lux from 0x%.8lux\n", p, callerpc);
	leakaudit();
}

static void
leakrealloc(void *newp, void *oldp, ulong callerpc)
{
	leakfree(oldp, callerpc);
	leakalloc(newp, callerpc);
}
#endif


#ifdef LEAKHUNT
static void *_realloc(void *p, ulong size, ulong callerpc)
#else
void *
logfsrealloc(void *p, ulong size)
#endif
{
	void *q;
	ulong osize;
	if (waserror()) {
		print("wobbly thrown in memory allocator: %s\n", up->env->errstr);
		nexterror();
	}
	if (p == nil) {
		q = smalloc(size);
		poperror();
#ifdef LEAKHUNT
		leakrealloc(q, nil, callerpc);
#endif
		return q;
	}
	q = realloc(p, size);
	if (q) {
		poperror();
#ifdef LEAKHUNT
		leakrealloc(q, p, callerpc);
#endif
		return q;
	}
	q = smalloc(size);
	osize = msize(p);
	if (osize > size)
		osize = size;
	memmove(q, p, osize);
	free(p);
	poperror();
#ifdef LEAKHUNT
	leakrealloc(q, p, callerpc);
#endif
	return q;
}

#ifdef LEAKHUNT
void *
logfsrealloc(void *p, ulong size)
{
	return _realloc(p, size, getcallerpc(&p));
}

void *
nandfsrealloc(void *p, ulong size)
{
	return _realloc(p, size, getcallerpc(&p));
}
#else
void *
nandfsrealloc(void *p, ulong size)
{
	return logfsrealloc(p, size);
}
#endif

void
logfsfreemem(void *p)
{
#ifdef LEAKHUNT
	leakfree(p, getcallerpc(&p));
#endif
	free(p);
}

void
nandfsfreemem(void *p)
{
#ifdef LEAKHUNT
	leakfree(p, getcallerpc(&p));
#endif
	free(p);
}

static Devlogfs *
devlogfsconfig(char *name, char *device)
{
	Devlogfs *newl, *l;
	int i;
	int n;
	char buf[100], *fields[8];
	long rawblocksize, rawsize;

	newl = nil;

	qlock(&devlogfslist.configqlock);

	if (waserror()) {
		qunlock(&devlogfslist.configqlock);
		devlogfsfree(newl);
		nexterror();
	}

	rlock(&devlogfslist.rwlock);
	for (l = devlogfslist.head; l; l = l->next)
		if (strcmp(l->name, name) == 0) {
			runlock(&devlogfslist.rwlock);
			error(Einuse);
		}

	/* horrid n^2 solution to finding a unique instance number */

	for (i = 0;; i++) {
		for (l = devlogfslist.head; l; l = l->next)
			if (l->instance == i)
				break;
		if (l == nil)
			break;
	}
	runlock(&devlogfslist.rwlock);

	newl = emalloc(sizeof(Devlogfs));
	newl->instance = i;
	newl->name = estrdup(name);
	newl->device = estrdup(device);
	newl->filename[Qfs - Qfs] = estrconcat(devlogfsprefix, name, nil);
	newl->filename[Qfsboot - Qfs] = estrconcat(devlogfsprefix, name, devlogfsbootsuffix, nil);
	newl->flash = devlogfskopen(device, nil, ORDWR);
	newl->flashctl = devlogfskopen(device, "ctl", ORDWR);
	newl->flashctl->offset = 0;
	if ((n = kchanio(newl->flashctl, buf, sizeof(buf), OREAD)) <= 0) {
		print("devlogfsconfig: read ctl failed: %s\n", up->env->errstr);
		error(up->env->errstr);
	}

	if (n >= sizeof(buf))
		n = sizeof(buf) - 1;
	buf[n] = 0;
	n = getfields(buf, fields, nelem(fields), 1, " \t\n");
	newl->nand = 0;
	if (n >= 2) {
		/* detect NAND devices, and learn parameters from there */
		ulong manufacturer = strtoul(fields[0], nil, 16);
		ulong device = strtoul(fields[1], nil, 16);
		int d;

		for (d = 0; d < sizeof(nandtab) / sizeof(nandtab[0]); d++) {
			if ((nandtab[d].manufacturer == manufacturer
				&& nandtab[d].device == device)
				|| (nandtab[d].manufacturer == 0
					&& (manufacturer == Toshiba || manufacturer == Samsung)
					&& nandtab[d].device == device))
			{
				if (DEVLOGFSDEBUG)
					print("devlogfsconfig: nand device detected\n");
				newl->nand = 1;
				break;
			}
		}
	}
	if (n < 4)
		error("unknown erase size");
	rawblocksize = strtol(fields[5], nil, 0);
	rawsize = strtol(fields[4], nil, 0)-strtol(fields[3], nil, 0);
	if (newl->nand == 0)
		error("only NAND supported at the moment");
	errorany(nandfsinit(newl, rawsize, rawblocksize, xread, xwrite, xerase, xsync, &newl->ll));
	wlock(&devlogfslist.rwlock);
	newl->next = devlogfslist.head;
	devlogfslist.head = newl;
	logfsfreemem(devlogfslist.defname);
	devlogfslist.defname = nil;
	if (waserror()) {
	}
	else {
		devlogfslist.defname = estrdup(name);
		poperror();
	}
	wunlock(&devlogfslist.rwlock);
	poperror();
	qunlock(&devlogfslist.configqlock);
	return newl;
}

void
devlogfsunconfig(Devlogfs *devlogfs)
{
	Devlogfs **lp;

	qlock(&devlogfslist.configqlock);

	if (waserror()) {
		qunlock(&devlogfslist.configqlock);
		nexterror();
	}

	wlock(&devlogfslist.rwlock);

	if (waserror()) {
		wunlock(&devlogfslist.rwlock);
		nexterror();
	}

	for (lp = &devlogfslist.head; *lp && (*lp) != devlogfs; lp = &(*lp)->next)
		;
	if (*lp == nil) {
		if (DEVLOGFSBAD)
			print("devlogfsunconfig: not in list\n");
	}
	else
		*lp = devlogfs->next;

	poperror();
	wunlock(&devlogfslist.rwlock);

	/* now invisible to the naked eye */
	devlogfsfree(devlogfs);
	poperror();
	qunlock(&devlogfslist.configqlock);
}

static void
devlogfsllopen(Devlogfs *l)
{
	qlock(&l->qlock);
	if (waserror()) {
		qunlock(&l->qlock);
		nexterror();
	}
	if (l->lb == nil)
		errorany(logfsbootopen(l->ll, 0, 0, l->logfstrace, 1, &l->lb));
	l->state = BootOpen;
	poperror();
	qunlock(&l->qlock);
}

static void
devlogfsllformat(Devlogfs *l, long bootsize)
{
	qlock(&l->qlock);
	if (waserror()) {
		qunlock(&l->qlock);
		nexterror();
	}
	if (l->lb == nil)
		errorany(logfsformat(l->ll, 0, 0, bootsize, l->logfstrace));
	poperror();
	qunlock(&l->qlock);
}

static Chan *
devlogfsattach(char *spec)
{
	Chan *c;
#ifdef CALLTRACE
	print("devlogfsattach(spec = %s) - start\n", spec);
#endif
	/* create the identity store on first attach */
	if (is == nil)
		errorany(logfsisnew(&is));
	c =  devattach(0x29f, spec);
//	c =  devattach(L'ʟ', spec);
#ifdef CALLTRACE
	print("devlogfsattach(spec = %s) - return %.8lux\n", spec, (ulong)c);
#endif
	return c;
}

static Walkqid*
devlogfswalk(Chan *c, Chan *nc, char **name, int nname)
{
	int instance, qid, qt, clone;
	Walkqid *wq;

#ifdef CALLTRACE
	print("devlogfswalk(c = 0x%.8lux, nc = 0x%.8lux, name = 0x%.8lux, nname = %d) - start\n",
		(ulong)c, (ulong)nc, (ulong)name, nname);
#endif
	clone = 0;
	if(nc == nil){
		nc = devclone(c);
		nc->type = 0;
		SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
		if(DATAQID(qid, qt))
			nc->aux = devlogfsget(instance);
		clone = 1;
	}
	wq = devwalk(c, nc, name, nname, 0, 0, devlogfsgen);
	if (wq == nil || wq->nqid < nname) {
		if(clone)
			cclose(nc);
	}
	else if (clone) {
		wq->clone = nc;
		nc->type = c->type;
	}
#ifdef CALLTRACE
	print("devlogfswalk(c = 0x%.8lux, nc = 0x%.8lux, name = 0x%.8lux, nname = %d) - return\n",
		(ulong)c, (ulong)nc, (ulong)name, nname);
#endif
	return wq;
}

static int
devlogfsstat(Chan *c, uchar *dp, int n)
{
#ifdef CALLTRACE
	print("devlogfsstat(c = 0x%.8lux, dp = 0x%.8lux n= %d)\n",
		(ulong)c, (ulong)dp, n);
#endif
	return devstat(c, dp, n, 0, 0, devlogfsgen);
}

static Chan*
devlogfsopen(Chan *c, int omode)
{
	int instance, qid, qt;

	omode = openmode(omode);
	SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
#ifdef CALLTRACE
	print("devlogfsopen(c = 0x%.8lux, omode = %o, instance = %d, qid = %d, qt = %d)\n",
		(ulong)c, omode, instance, qid, qt);
#endif


	rlock(&devlogfslist.rwlock);
	if (waserror()) {
		runlock(&devlogfslist.rwlock);
#ifdef CALLTRACE
		print("devlogfsopen(c = 0x%.8lux, omode = %o) - error %s\n", (ulong)c, omode, up->env->errstr);
#endif
		nexterror();
	}

	if (DATAQID(qid, qt)) {
		Devlogfs *d;
		d = devlogfsfind(instance);
		if (d == nil)
			error(Enodev);
		if (strcmp(up->env->user, eve) != 0)
			error(Eperm);
		if (qid == Qfs && d->state != BootOpen)
			error(Eperm);
		if (d->server == nil) {
			errorany(logfsservernew(d->lb, d->ll, is, d->openflags, d->logfstrace, &d->server));
			d->state = NeedVersion;
		}
		c = devopen(c, omode, 0, 0, devlogfsgennolock);
		incref(&d->ref);
		c->aux = d;
	}
	else if (qid == Qctl || qid == Qusers) {
		if (strcmp(up->env->user, eve) != 0)
			error(Eperm);
		c = devopen(c, omode, 0, 0, devlogfsgennolock);
	}
	else
		c = devopen(c, omode, 0, 0, devlogfsgennolock);
	poperror();
	runlock(&devlogfslist.rwlock);
#ifdef CALLTRACE
	print("devlogfsopen(c = 0x%.8lux, omode = %o) - return\n", (ulong)c, omode);
#endif
	return c;
}

static void
devlogfsclose(Chan *c)
{
	int instance, qid, qt;
#ifdef CALLTRACE
	print("devlogfsclose(c = 0x%.8lux)\n", (ulong)c);
#endif
	SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
	USED(instance);
	if(DATAQID(qid, qt) && (c->flag & COPEN) != 0) {
		Devlogfs *d;
		d = c->aux;
		qlock(&d->qlock);
		if (qid == Qfs && d->state == Attached) {
			logfsserverflush(d->server);
			logfsserverfree(&d->server);
			d->state = BootOpen;
		}
		qunlock(&d->qlock);
		decref(&d->ref);
	}
#ifdef CALLTRACE
	print("devlogfsclose(c = 0x%.8lux) - return\n", (ulong)c);
#endif
}

typedef char *(SMARTIOFN)(void *magic, void *buf, long n, ulong offset, int write);

void
smartio(SMARTIOFN *io, void *magic, void *buf, long n, ulong offset, long blocksize, int write)
{
	void *tmp = nil;
	ulong blocks, toread;

	if (waserror()) {
		logfsfreemem(tmp);
		nexterror();
	}
	if (offset % blocksize) {
		ulong aoffset;
		int tmpoffset;
		int tocopy;

		if (tmp == nil)
			tmp = emalloc(blocksize);
		aoffset = offset / blocksize;
		aoffset *= blocksize;
		errorany((*io)(magic, tmp, blocksize, aoffset, 0));
		tmpoffset = offset - aoffset;
		tocopy = blocksize - tmpoffset;
		if (tocopy > n)
			tocopy = n;
		if (write) {
			memmove((uchar *)tmp + tmpoffset, buf, tocopy);
			errorany((*io)(magic, tmp, blocksize, aoffset, 1));
		}
		else
			memmove(buf, (uchar *)tmp + tmpoffset, tocopy);
		buf = (uchar *)buf + tocopy;
		n -= tocopy;
		offset = aoffset + blocksize;
	}
	blocks = n / blocksize;
	toread = blocks * blocksize;
	errorany((*io)(magic, buf, toread, offset, write));
	buf = (uchar *)buf + toread;
	n -= toread;
	offset += toread;
	if (n) {
		if (tmp == nil)
			tmp = emalloc(blocksize);
		errorany((*io)(magic, tmp, blocksize, offset, 0));
		if (write) {
			memmove(tmp, buf, n);
			errorany((*io)(magic, tmp, blocksize, offset, 1));
		}
		memmove(buf, tmp, n);
	}
	poperror();
	logfsfreemem(tmp);
}

static int
readok(void *a)
{
	Devlogfs *d = a;
	return d->reading;
}

static int
writeok(void *a)
{
	Devlogfs *d = a;
	return !d->reading;
}

long
devlogfsserverread(Devlogfs *d, void *buf, long n)
{
	if (d->state == Hungup)
		error(Ehungup);
	Sleep(&d->readrendez, readok, d);
	if (n > d->readcount)
		n = d->readcount;
	memmove(buf, d->readp, n);
	d->readp += n;
	d->readcount -= n;
	if (d->readcount == 0) {
		d->reading = 0;
		Wakeup(&d->writerendez);
	}
	return n;
}

static void
reply(Devlogfs *d)
{
	d->readp = d->readbuf;
	d->readcount = convS2M(&d->out, d->readp, d->readbufsize);
//print("reply is %d bytes\n", d->readcount);
	if (d->readcount == 0)
		panic("logfs: reply: did not fit\n");
	d->reading = 1;
	Wakeup(&d->readrendez);
}

static void
rerror(Devlogfs *d, char *ename)
{
	d->out.type = Rerror;
	d->out.ename = ename;
	reply(d);
}

static struct {
	QLock qlock;
	int (*read)(void *magic, Devlogfs *d, int line, char *buf, int buflen);
	void *magic;
	Devlogfs *d;
	int line;
} dump;

static void *
extentdumpinit(Devlogfs *d, int argc, char **argv)
{
	int *p;
	ulong path;
	ulong flashaddr, length;
	long block;
	int page, offset;
	if (argc != 1)
		error(Ebadarg);
	path = strtoul(argv[0], 0, 0);
	errorany(logfsserverreadpathextent(d->server, path, 0, &flashaddr, &length, &block, &page, &offset));
	p = emalloc(sizeof(ulong));
	*p = path;
	return p;
}

static int
extentdumpread(void *magic, Devlogfs *d, int line, char *buf, int buflen)
{
	ulong *p = magic;
	ulong flashaddr, length;
	long block;
	int page, offset;
	USED(d);
	errorany(logfsserverreadpathextent(d->server, *p, line, &flashaddr, &length, &block, &page, &offset));
	if (length == 0)
		return 0;
	return snprint(buf, buflen, "%.8ux %ud %ld %d %d\n", flashaddr, length, block, page, offset);
}

void
devlogfsdumpinit(Devlogfs *d,
	void *(*init)(Devlogfs *d, int argc, char **argv),
	int (*read)(void *magic, Devlogfs *d, int line, char *buf, int buflen), int argc, char **argv)
{
	qlock(&dump.qlock);
	if (waserror()) {
		qunlock(&dump.qlock);
		nexterror();
	}
	if (d) {
		if (d->state < NeedVersion)
			error("not mounted");
		qlock(&d->qlock);
		if (waserror()) {
			qunlock(&d->qlock);
			nexterror();
		}
	}
	if (dump.magic) {
		logfsfreemem(dump.magic);
		dump.magic = nil;
	}
	dump.d = d;
	dump.magic = (*init)(d, argc, argv);
	dump.read = read;
	dump.line = 0;
	if (d) {
		poperror();
		qunlock(&d->qlock);
	}
	poperror();
	qunlock(&dump.qlock);
}

long
devlogfsdumpread(char *buf, int buflen)
{
	char *tmp = nil;
	long n;
	qlock(&dump.qlock);
	if (waserror()) {
		logfsfreemem(tmp);
		qunlock(&dump.qlock);
		nexterror();
	}
	if (dump.magic == nil)
		error(Eio);
	tmp = emalloc(READSTR);
	if (dump.d) {
		if (dump.d->state < NeedVersion)
			error("not mounted");
		qlock(&dump.d->qlock);
		if (waserror()) {
			qunlock(&dump.d->qlock);
			nexterror();
		}
	}
	n = (*dump.read)(dump.magic, dump.d, dump.line, tmp, READSTR);
	if (n) {
		dump.line++;
		n = readstr(0, buf, buflen, tmp);
	}
	if (dump.d) {
		poperror();
		qunlock(&dump.d->qlock);
	}
	logfsfreemem(tmp);
	poperror();
	qunlock(&dump.qlock);
	return n;
}

void
devlogfsserverlogsweep(Devlogfs *d, int justone)
{
	int didsomething;
	if (d->state < NeedVersion)
		error("not mounted");
	qlock(&d->qlock);
	if (waserror()) {
		qunlock(&d->qlock);
		nexterror();
	}
	errorany(logfsserverlogsweep(d->server, justone, &didsomething));
	poperror();
	qunlock(&d->qlock);
}

void
devlogfsserverwrite(Devlogfs *d, void *buf, long n)
{
	int locked = 0;
	if (d->state == Hungup)
		error(Ehungup);
	Sleep(&d->writerendez, writeok, d);
	if (convM2S(buf, n, &d->in) != n) {
		/*
		 * someone is writing drivel; have nothing to do with them anymore
		 * most common cause; trying to mount authenticated
		 */
		d->state = Hungup;
		error(Ehungup);
	}
	d->out.tag = d->in.tag;
	d->out.fid = d->in.fid;
	d->out.type = d->in.type + 1;
	if (waserror()) {
		if (locked)
			qunlock(&d->qlock);
		rerror(d, up->env->errstr);
		return;
	}
	if (d->in.type != Tversion && d->in.type != Tattach) {
		if (d->state != Attached)
			error("must be attached");
		qlock(&d->qlock);
		locked = 1;
	}
	switch (d->in.type) {
	case Tauth:
		error("no authentication needed");
	case Tversion: {
		char *rversion;
		if (d->state != NeedVersion)
			error("unexpected Tversion");
		 if (d->in.tag != NOTAG)
			error("protocol botch");
		/*
		 * check the version string
		 */
		if (strcmp(d->in.version, devlogfs9pversion) != 0)
			rversion = "unknown";
		else
			rversion = devlogfs9pversion;
		/*
		 * allocate the reply buffer
		 */
		d->readbufsize = d->in.msize;
		if (d->readbufsize > MAXMSIZE)
			d->readbufsize = MAXMSIZE;
		d->readbuf = emalloc(d->readbufsize);
		/*
		 * compose the Rversion
		 */
		d->out.msize = d->readbufsize;
		d->out.version = rversion;
		d->state = NeedAttach;
		break;
	}
	case Tattach:
		if (d->state != NeedAttach)
			error("unexpected attach");
		if (d->in.afid != NOFID)
			error("unexpected afid");
		errorany(logfsserverattach(d->server, d->in.fid, d->in.uname, &d->out.qid));
		d->state = Attached;
		break;
	case Tclunk:
		errorany(logfsserverclunk(d->server, d->in.fid));
		break;
	case Tcreate:
		errorany(logfsservercreate(d->server, d->in.fid, d->in.name, d->in.perm, d->in.mode, &d->out.qid));
		d->out.iounit = d->readbufsize - 11;
		break;
	case Tflush:
		break;
	case Topen:
		errorany(logfsserveropen(d->server, d->in.fid, d->in.mode, &d->out.qid));
		d->out.iounit = d->readbufsize - 11;
		break;
	case Tread:
		d->out.data = (char *)d->readbuf + 11;
		/* TODO - avoid memmove */
		errorany(logfsserverread(d->server, d->in.fid, d->in.offset, d->in.count, (uchar *)d->out.data,
			d->readbufsize - 11, &d->out.count));
		break;
	case Tremove:
		errorany(logfsserverremove(d->server, d->in.fid));
		break;
	case Tstat:
		d->out.stat = d->readbuf + 9;
		/* TODO - avoid memmove */
		errorany(logfsserverstat(d->server, d->in.fid, d->out.stat, d->readbufsize - 9, &d->out.nstat));
//		print("nstat %d\n", d->out.nstat);
		break;
	case Twalk:
		errorany(logfsserverwalk(d->server, d->in.fid, d->in.newfid,
			d->in.nwname, d->in.wname, &d->out.nwqid, d->out.wqid));
		break;
	case Twrite:
		errorany(logfsserverwrite(d->server, d->in.fid, d->in.offset, d->in.count, (uchar *)d->in.data,
			&d->out.count));
		break;
	case Twstat:
		errorany(logfsserverwstat(d->server, d->in.fid, d->in.stat, d->in.nstat));
		break;
	default:
		print("devlogfsserverwrite: msg %d unimplemented\n", d->in.type);
		error("unimplemented");
	}
	poperror();
	if (locked)
		qunlock(&d->qlock);
	reply(d);
}

static long
devlogfsread(Chan *c, void *buf, long n, vlong off)
{
	int instance, qid, qt;

	SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
	USED(instance);
#ifdef CALLTRACE
	print("devlogfsread(c = 0x%.8lux, buf = 0x%.8lux, n = %ld, instance = %d, qid = %d, qt = %d) - start\n",
		(ulong)c, (ulong)buf, n, instance, qid, qt);
#endif
	if(qt & QTDIR) {
#ifdef CALLTRACE
		print("devlogfsread(c = 0x%.8lux, buf = 0x%.8lux, n = %ld, instance = %d, qid = %d, qt = %d) - calling devdirread\n",
			(ulong)c, (ulong)buf, n, instance, qid, qt);
#endif
		return devdirread(c, buf, n, 0, 0, devlogfsgen);
	}

	if(DATAQID(qid, qt)) {
		if (qid == Qfsboot) {
			Devlogfs *l = c->aux;
			qlock(&l->bootqlock);
			if (waserror()) {
				qunlock(&l->bootqlock);
				nexterror();
			}
			smartio((SMARTIOFN *)logfsbootio, l->lb, buf, n, off, logfsbootgetiosize(l->lb), 0);
			poperror();
			qunlock(&l->bootqlock);
			return n;
		}
		else if (qid == Qfs) {
			Devlogfs *d = c->aux;
			return devlogfsserverread(d, buf, n);
		}
		error(Eio);
	}

	if (qid == Qusers) {
		long nr;
		errorany(logfsisusersread(is, buf, n, (ulong)off, &nr));
		return nr;
	}
	else if (qid == Qdump)
		return devlogfsdumpread(buf, n);

	if (qid != Qctl)
		error(Egreg);

	return 0;
}

static long
devlogfswrite(Chan *c, void *buf, long n, vlong off)
{
	char cmd[64], *realfields[6];
	int i;
	int instance, qid, qt;

	if(n <= 0)
		return 0;
	SPLITPATH(c->qid.path, c->qid.type, instance, qid, qt);
#ifdef CALLTRACE
	print("devlogfswrite(c = 0x%.8lux, buf = 0x%.8lux, n = %ld, instance = %d, qid = %d, qt = %d) - start\n",
		(ulong)c, (ulong)buf, n, instance, qid, qt);
#endif
	USED(instance);
	if(DATAQID(qid, qt)){
		if (qid == Qfsboot) {
			Devlogfs *l = c->aux;
			qlock(&l->bootqlock);
			if (waserror()) {
				qunlock(&l->bootqlock);
				nexterror();
			}
			smartio((SMARTIOFN *)logfsbootio, l->lb, buf, n, off, logfsbootgetiosize(l->lb), 1);
			poperror();
			qunlock(&l->bootqlock);
			return n;
		}
		else if (qid == Qfs) {
			Devlogfs *d = c->aux;
			devlogfsserverwrite(d, buf, n);
			return n;
		}
		error(Eio);
	}
	else if (qid == Qctl) {
		Devlogfs *l = nil;
		char **fields;

		if(n > sizeof(cmd)-1)
			n = sizeof(cmd)-1;
		memmove(cmd, buf, n);
		cmd[n] = 0;
		i = getfields(cmd, realfields, 6, 1, " \t\n");
//print("i = %d\n", i);
		if (i <= 0)
			error(Ebadarg);
		fields = realfields;
		if (i == 3 && strcmp(fields[0], "uname") == 0) {
			switch (fields[2][0]) {
			default:
				errorany(logfsisgroupcreate(is, fields[1], fields[2]));
				break;
			case ':':
				errorany(logfsisgroupcreate(is, fields[1], fields[2] + 1));
				break;
			case '%':
				errorany(logfsisgrouprename(is, fields[1], fields[2] + 1));
				break;
			case '=':
				errorany(logfsisgroupsetleader(is, fields[1], fields[2] + 1));
				break;
			case '+':
				errorany(logfsisgroupaddmember(is, fields[1], fields[2] + 1));
				break;
			case '-':
				errorany(logfsisgroupremovemember(is, fields[1], fields[2] + 1));
				break;
			}
			i = 0;
		}
		if (i == 4 && strcmp(fields[0], "fsys") == 0 && strcmp(fields[2], "config") == 0) {
			l = devlogfsconfig(fields[1], fields[3]);
			i = 0;
		}
		else if (i >= 2 && strcmp(fields[0], "fsys") == 0) {
			l = devlogfssetdefname(fields[1]);
			if (l == nil)
				error(Ebadarg);
			i -= 2;
			fields += 2;
		}
		if (i != 0) {
			if (l == nil)
				l = devlogfssetdefname(nil);
			if (i >= 1 && strcmp(fields[0], "open") == 0) {
				int a;
				if (l == nil)
					error(Ebadarg);
				for (a = 1; a < i; a++)
					if (fields[a][0] == '-')
						switch (fields[a][1]) {
						case 'P':
							l->openflags |= LogfsOpenFlagNoPerm;
							break;
						case 'W':
							l->openflags |= LogfsOpenFlagWstatAllow;
							break;
						default:
							error(Ebadarg);
						}
				devlogfsllopen(l);
				i = 0;
			}
			else if (i == 2 && strcmp(fields[0], "format") == 0) {
				if (l == nil)
					error(Ebadarg);
				devlogfsllformat(l, strtol(fields[1], nil, 0));
				i = 0;
			}
			else if (i >= 1 && strcmp(fields[0], "sweep") == 0) {
				if (l == nil)
					error(Ebadarg);
				devlogfsserverlogsweep(l, 0);
				i = 0;
			}
			else if (i >= 1 && strcmp(fields[0], "sweepone") == 0) {
				if (l == nil)
					error(Ebadarg);
				devlogfsserverlogsweep(l, 1);
				i = 0;
			}
			else if (i <= 2&& strcmp(fields[0], "trace") == 0) {
				if (l == nil)
					error(Ebadarg);
				l->logfstrace = i > 1 ? strtol(fields[1], nil, 0) : 0;
				if (l->server)
					logfsservertrace(l->server, l->logfstrace);
				if (l->lb)
					logfsboottrace(l->lb, l->logfstrace);
				i = 0;
			}
			else if (i == 1 && strcmp(fields[0], "unconfig") == 0) {
				if (l == nil)
					error(Ebadarg);
				if (l->ref.ref > 0)
					error(Einuse);
				devlogfsunconfig(l);
				i = 0;
			}
			else if (i == 2 && strcmp(fields[0], "extent") == 0) {
				if (l == nil)
					error(Ebadarg);
				devlogfsdumpinit(l, extentdumpinit, extentdumpread, i - 1, fields + 1);
				i = 0;
			}
			else if (i >= 2 && strcmp(fields[0], "test") == 0) {
				if (l == nil)
					error(Ebadarg);
				errorany(logfsservertestcmd(l->server, i - 1, fields + 1));
				i = 0;
			}
#ifdef LEAKHUNT
			else if (i == 1 && strcmp(fields[0], "leakaudit") == 0) {
				leakaudit();
				i = 0;
			}
#endif
		}
		if (i != 0)
			error(Ebadarg);
		return n;
	}
	error(Egreg);
	return 0;		/* not reached */
}

static void
devlogfsfree(Devlogfs *devlogfs)
{
	if (devlogfs != nil) {
		int i;
		logfsfreemem(devlogfs->device);
		logfsfreemem(devlogfs->name);
		for (i = 0; i < Qend - Qfs; i++)
			logfsfreemem(devlogfs->filename[i]);
		cclose(devlogfs->flash);
		cclose(devlogfs->flashctl);
		qlock(&devlogfs->qlock);
		logfsserverfree(&devlogfs->server);
		logfsbootfree(devlogfs->lb);
		if (devlogfs->ll)
			(*devlogfs->ll->free)(devlogfs->ll);
		logfsfreemem(devlogfs->readbuf);
		qunlock(&devlogfs->qlock);
		logfsfreemem(devlogfs);
	}
}

#ifdef EMU
ulong
logfsnow(void)
{
	extern vlong timeoffset;
	return (timeoffset + osusectime()) / 1000000;
}
#endif

Dev logfsdevtab = {
	0x29f,
//	L'ʟ',
	"logfs",

#ifndef EMU
	devreset,
#endif
	devinit,
#ifndef EMU
	devshutdown,
#endif
	devlogfsattach,
	devlogfswalk,
	devlogfsstat,
	devlogfsopen,
	devcreate,
	devlogfsclose,
	devlogfsread,
	devbread,
	devlogfswrite,
	devbwrite,
	devremove,
	devwstat,
};