#include <9pm/windows.h>
#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/ctype.h>
#include <9pm/fcall.h>
#include "dat.h"
#include "fns.h"
#include "syscall.h"

#pragma comment(lib, "mpr.lib")
#define DPRINT if(1)print

typedef struct Uchan	Uchan;
struct Uchan
{
	HANDLE	h;
	Cname*	syspath;
	int	type;
	int	eof;
	Dir	dir;
	Rune	rtmp[128];
	WIN32_FIND_DATA	finddata;
	uchar	*buf;
	int	nbuf;
	int	havedir;
	int	rootoff;
	char	rootent[10];
	QLock	qlk;
};

enum
{
	Trootdir,
	Tmntdir,
	Tserver,
	Tdir,
	Tvolume,	/* root of drive (like c:\) */
	Tfile,

	Tpipe,
	Tconsole,
	Tstdin,		/* do we need this? */
};

static int pathqid(int, char*);
static long unixtime(FILETIME ft);
static FILETIME wintime(ulong);
static long consolewrite(Uchan*, void*, long);
static long consoleread(Uchan*, void*, long);
static long serverread(Uchan*, void*, long, vlong);
static void serveropen(Uchan*, int);
static void serverclose(Uchan*);
static long directoryread(Uchan*, uchar*, long, vlong);
static void data2dir(WIN32_FIND_DATA*, Dir*, char*);
static long rootdirread(Uchan*, uchar*, long, vlong);
static void rootdiropen(Uchan*);
/*
 * Convert between real Windows paths and the paths we present.
 * We map x:\ to /x, \\server to /mnt/server, and \\. to /mnt/local
 * (BUG not yet)
 */

static struct {
	Rune *r;
	char *s;
} pathtab[] = {
	L"X:\\",	"/X",
	L"\\\\.\\",	"/mnt/local",
	L"\\\\",	"/mnt",
};

static Rune*
towin(char *s)
{
	char *ss;
	Rune *r, *rr;
	int drive, i, match, len;

	drive = '?';
	if(s[0]=='/' && islower(s[1]) && (s[2]=='/' || s[2]=='\0')){
		drive = s[1];
		match = 0;
	}else{
		match = -1;
		for(i=0; i<nelem(pathtab); i++){
			len = strlen(pathtab[i].s);
			if(strncmp(s, pathtab[i].s, len)==0 && (s[len]=='\0' || s[len]=='/')){
				match = i;
				break;
			}
		}
		if(match < 0)
			return nil;
	}

	r = smalloc(sizeof(Rune)*(utflen(s)+runestrlen(pathtab[match].r)+10));
	runestrcpy(r, pathtab[match].r);
	rr = r+runestrlen(r);
	ss = s+strlen(pathtab[match].s);
	if(*ss == '/')
		ss++;
	while(*ss){
		ss += chartorune(rr, ss);
		if(*rr == '/')
			*rr = L'\\';
		rr++;
	}
	*rr = '\0';
	if(match == 0)
		r[0] = drive;
	return r;
}

Rune*
_execpath(char *s)
{
	return towin(s);
}

static char*
fromwin(Rune *r)
{
	int drive, i, match;
	char *s, *ss;
	Rune *rr;

	match = -1;
	if(isalpha(r[0]) && r[1]==':' && r[2]=='\\'){
		match = 0;
		drive = tolower(r[0]);
	}else{
		match = -1;
		for(i=0; i<nelem(pathtab); i++){
			if(runestrncmp(r, pathtab[i].r, runestrlen(pathtab[i].r))==0){
				match = i;
				break;
			}
		}
		if(match < 0)
			return nil;
	}

	s = smalloc(strlen(pathtab[match].s)+runenlen(r, runestrlen(r))+10);
	strcpy(s, pathtab[match].s);
	rr = r+runestrlen(pathtab[match].r);
	ss = s+strlen(s);
	if(*rr != '\0')
		*ss++ = '/';
	while(*rr){
		ss += runetochar(ss, rr);
		if(*(ss-1) == '\\')
			*(ss-1) = '/';
		rr++;
	}
	*ss = '\0';
	if(match == 0)
		s[1] = drive;
	return s;
}

static int
pathtype(char *s)
{
	Rune *r;
	int attr;

	if(strcmp(s, "/mnt") == 0)
		return Tmntdir;
	if(strcmp(s, "/") == 0)
		return Trootdir;
	if(strncmp(s, "/mnt/", 5)==0 && strchr(s+5, '/')==nil)
		return Tserver;
	if(s[0]=='/' && s[2]=='\0')
		return Tvolume;

	r = towin(s);
	if(r == nil){
		werrstr("towin %s?", s);
		return -1;
	}
	attr = GetFileAttributes(r);
	free(r);
	if(attr == -1){
		oserror();
		return -1;
	}
	if(attr & FILE_ATTRIBUTE_DIRECTORY)
		return Tdir;
	return Tfile;
}

static Rune*
fspath(char *dir, char *elem)
{
	int len;
	char *e;
	Rune *s, *t;

	s = towin(dir);
	if(elem){
		t = smalloc(sizeof(Rune)*(runestrlen(s)+1+strlen(elem)+1));
		runestrcpy(t, s);
		len = runestrlen(t);
		if(t[len-1] != '\\'){
			t[len] = '\\';
			t[++len] = '\0';
		}
		free(s);
		s = t;
		t += len;
		e = elem;
		for(; *e; t++)
			e += chartorune(t, e);
		*t = '\0';
	}
	return s;
}

static Chan*
handle2chan(HANDLE h, int mode, Qid qid, char *buf)
{
	Chan *c;
	Uchan *uc;

	c = newchan();
	c->name = newcname(buf);
	c->mode = mode;
	c->flag |= COPEN;
	c->type = devno('U', 1);
	c->qid = qid;
	uc = smalloc(sizeof *uc);
	uc->syspath = newcname(buf);
	uc->type = pathtype(buf);
	uc->h = h;
	c->aux = uc;
	return c;
}

static Chan*
syspath2chan(Qid qid, char *buf)
{
	Chan *c;
	Uchan *uc;

	c = newchan();
	c->name = newcname(buf);
	c->type = devno('U', 1);
	c->qid = qid;
	uc = smalloc(sizeof *uc);
	uc->syspath = newcname(buf);
	uc->type = pathtype(buf);
	c->aux = uc;
	return c;
}

int
issysfd(Chan *c)
{
	Uchan *uc;

	if(devtab[c->type]->dc != 'U')
		return 0;
	uc = c->aux;
	return uc->h != nil && uc->h != INVALID_HANDLE_VALUE && uc->type >= Tfile;
}
	
static void
fsreset(void)
{
	char *s;

	for(s="\\:?*;"; *s; s++)
		isfrog[*s] = 1;
}

static Chan*
fsattach(char *spec)
{
	Chan *c;
	Qid q;
	Uchan *uc;
	static Lock lk;
	static int devno;

	if(spec && spec[0])
		error(Ebadspec);

	mkqid(&q, pathqid(0, "/"), 0, QTDIR);
	c = devattach('U', "");
	c->qid = q;
	uc = smalloc(sizeof *uc);
	uc->syspath = newcname("/");
	c->aux = uc;
	lock(&lk);
	c->dev = devno++;
	unlock(&lk);
	return c;
}

Walkqid*
fswalk(Chan *c, Chan *nc, char **name, int nname)
{
	int i, type;
	int volatile alloc;
	Cname *p, *oname;
	Uchan *uc;
	Walkqid *wq;

	if(nname > MAXWELEM)
		error("devfs: too many name elements");
	alloc = 0;
	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
	if(waserror()){
		if(alloc && wq->clone!=nil)
			cclose(wq->clone);
		free(wq);
		return nil;
	}
	if(nc == nil){
		nc = devclone(c);
		uc = smalloc(sizeof *uc);
		*uc = *(Uchan*)c->aux;
		nc->aux = uc;
		incref(&uc->syspath->ref);
		alloc = 1;
	}
	wq->clone = nc;
	uc = nc->aux;
	oname = uc->syspath;
	incref(&oname->ref);
	type = uc->type;
	for(i = 0; i < nname; i++){
		p = addelem(uc->syspath, name[i]);
		if(strcmp(name[i], "..") == 0)
			cleancname(p);
		uc->syspath = p;
		if((type = pathtype(p->s)) < 0){
			//print("cannot find %s\n", p->s);
			cnameclose(uc->syspath);
			uc->syspath = oname;
			if(alloc)
				cclose(nc);
			wq->clone = nil;
			if(i == 0){
				free(wq);
				poperror();
				return nil;
			}
			break;
		}
		mkqid(&wq->qid[i], pathqid(0, uc->syspath->s), 0, 0);
		if(type < Tfile)
			wq->qid[i].type |= QTDIR;
	}
	poperror();

	if(i == nname){
		cnameclose(oname);
		uc->type = type;
	}
		
	if(i > 0 && wq->clone != nil)
		wq->clone->qid = wq->qid[i-1];
	wq->nqid = i;
	return wq;
}

static Chan*
fsopen(Chan *c, int mode)
{
	int acc, aflag, cflag, share;
	HANDLE h;
	Rune *wpath;
	Uchan *uc;

	uc = c->aux;
	switch(uc->type){
	case Tserver:
		serveropen(uc, mode);
		break;
	case Trootdir:
		rootdiropen(uc);
	case Tdir:
		uc->h = INVALID_HANDLE_VALUE;
		break;
	case Tfile:
		switch(mode&3){
		default:
			acc = 0;
			break;
		case OREAD:
		case OEXEC:
			acc = GENERIC_READ;
			break;
		case OWRITE:
			acc = GENERIC_WRITE;
			break;
		case ORDWR:
			acc = GENERIC_READ|GENERIC_WRITE;
			break;
		}
		cflag = OPEN_EXISTING;
		if(mode&OTRUNC)
			cflag = TRUNCATE_EXISTING;
		aflag = 0;
		if(mode&ORCLOSE)
			aflag |= FILE_FLAG_DELETE_ON_CLOSE;

		share = FILE_SHARE_READ|FILE_SHARE_WRITE;
		wpath = towin(uc->syspath->s);
		h = CreateFile(wpath, acc, share, 0, cflag, aflag, 0);
		free(wpath);
		if(h == INVALID_HANDLE_VALUE){
			oserror();
			//print("open %S: %r\n", wpath);
			nexterror();
		}
		uc->h = h;
		break;
	}
	c->mode = openmode(mode);
	c->offset = 0;
	c->flag |= COPEN;
	return c;
}

static void
fscreate(Chan *c, char *name, int mode, ulong perm)
{
	int acc, cflag, aflag, share;
	Rune *path;
	Uchan *uc;

	uc = c->aux;
	switch(uc->type){
	case Tserver:
	case Tvolume:
	case Tfile:
		error(Eperm);
	case Tdir:
		path = fspath(uc->syspath->s, name);
		if(perm&DMDIR){
			if(mode != OREAD)
				error(Ebadarg);
			if(!CreateDirectory(path, 0)){
				oserror();
				free(path);
				nexterror();
			}
			free(path);
			uc->h = INVALID_HANDLE_VALUE;
		}else{
			switch(mode&3){
			default:
				acc = 0;
				break;
			case OREAD:
			case OEXEC:
				acc = GENERIC_READ;
				break;
			case OWRITE:
				acc = GENERIC_WRITE;
				break;
			case ORDWR:
				acc = GENERIC_READ|GENERIC_WRITE;
				break;
			}
		/*BUG: OEXCL, OTRUNC? */
			cflag = CREATE_ALWAYS;
			aflag = 0;
			if(mode&ORCLOSE)
				aflag |= FILE_FLAG_DELETE_ON_CLOSE;
			share = FILE_SHARE_READ|FILE_SHARE_WRITE;
			uc->h = CreateFile(path, acc, share, 0, cflag, aflag, 0);
			free(path);
			if(uc->h == INVALID_HANDLE_VALUE){
				oserror();
				nexterror();
			}
			uc->type = Tfile;
			c->qid.type = 0;
		}
		uc->syspath = addelem(uc->syspath, name);
		c->qid.path = pathqid(0, uc->syspath->s);
		
		break;
	}
	c->mode = openmode(mode);
	c->offset = 0;
	c->flag |= COPEN;
}

static void fsremove(Chan*);

static void
fsclose(Chan *c)
{
	Uchan *uc;

	uc = c->aux;
	if(c->flag & COPEN){
		switch(uc->type){
		case Tconsole:
		case Tpipe:
		case Tfile:
		case Tstdin:
			if(uc->h != INVALID_HANDLE_VALUE)
				CloseHandle(uc->h);
			break;
		case Tdir:
			FindClose(uc->h);
			if(uc->havedir){
				free(uc->dir.name);
				free(uc->dir.uid);
				free(uc->dir.gid);
			}
			break;
		case Tserver:
			WNetCloseEnum(uc->h);
			break;
		}
	}
	cnameclose(uc->syspath);
	free(uc);
}

static long
fsread(Chan *c, void *a, long n, vlong vo)
{
	int r;
	Uchan *uc;
	OVERLAPPED o;
	DWORD m;

	uc = c->aux;
	if(uc->eof)
		return 0;

	DPRINT("fsread\n");
	memset(&o, 0, sizeof o);
	o.Offset = vo;
	o.OffsetHigh = vo>>32;
	switch(uc->type){
	case Tfile:
		r = ReadFile(uc->h, a, n, &m, &o);
		if(r == 0){
			if(uc->type==Tpipe || GetLastError() == ERROR_HANDLE_EOF)
				m = 0;
			else{
				oserror();
				nexterror();
			}
		}
		break;
	case Tmntdir:
		m = 0;
		break;
	case Trootdir:
		m = rootdirread(uc, a, n, vo);
		break;
	case Tconsole:
		m = consoleread(uc, a, n);
		break;
	case Tvolume:
	case Tdir:
		m = directoryread(uc, a, n, vo);
		break;
	case Tserver:
		m = serverread(uc, a, n, vo);
		break;
	}
	return m;
}

static long
fswrite(Chan *c, void *a, long n, vlong vo)
{
	Uchan *uc;
	OVERLAPPED o;
	DWORD m;

	memset(&o, 0, sizeof o);
	o.Offset = vo;
	o.OffsetHigh = vo>>32;
	uc = c->aux;

	switch(uc->type){
	case Tpipe:
		if(!WriteFile(uc->h, a, n, &m, nil)){
			oserror();
print("pipewrite: %r\n");
			nexterror();
		}
		break;
	case Tfile:
		if(!WriteFile(uc->h, a, n, &m, &o)){
			oserror();
print("writefile: %r\n");
			nexterror();
		}
		break;
	case Tconsole:
		m = consolewrite(uc, a, n);
		break;
	case Tdir:
		error(Ebadusefd);
		break;
	}
	return m;
}

static void
fsremove(Chan *c)
{
	int err;
	Rune *path;
	Uchan *uc;

	uc = c->aux;
	path = towin(uc->syspath->s);
	if(path == nil)
		error(Eperm);
	if(c->qid.type&QTDIR)
		err = !RemoveDirectory(path);
	else
		err = !DeleteFile(path);
	free(path);
	if(err)
		oserror();
	if(c->flag & COPEN){
		CloseHandle(uc->h);
		uc->h = INVALID_HANDLE_VALUE;
	}
	cnameclose(uc->syspath);
	free(uc);
	if(err)
		nexterror();
}

static int
fsstat(Chan *c, uchar *buf, int n)
{
	Dir d;
	HANDLE h;
	char *dpath, *s;
	Rune *path, *p;
	Uchan *uc;
	WIN32_FIND_DATA data;

	uc = c->aux;
	switch(uc->type){
	case Tpipe:
	case Tconsole:
	case Tstdin:
		error("cannot stat pipe, console, stdin");
	}

	dpath = strdup(uc->syspath->s);
	if(dpath == nil)
		error("out of memory");
	s = strrchr(dpath, '/');
	if(s == dpath)
		*(s+1) = '\0';
	else
		*s = '\0';
	path = towin(uc->syspath->s);
	if(waserror()){
		free(dpath);
		free(path);
		nexterror();
	}
	memset(&d, 0, sizeof d);
	switch(uc->type){
	default:
		error("windows fsstat bug");
	case Trootdir:
		d.name = strdup("/");
		goto Fakedir;
	case Tmntdir:
		d.name = strdup("mnt");
		goto Fakedir;
	case Tserver:
		p = runestrrchr(path, '\\');
		p++;
		memset(&d, 0, sizeof d);
		d.name = win_wstr2utf(p);
	Fakedir:
		d.uid = strdup("sys");
		d.gid = strdup("sys");
		d.mtime = d.atime = seconds();
		d.type = 'U';
		d.dev = 0;
		d.qid = c->qid;
		d.mode = DMDIR|0777;
		break;
	case Tvolume:
		memset(&data, 0, sizeof data);
		data.dwFileAttributes = GetFileAttributes(path);
		if(data.dwFileAttributes == (DWORD)-1){
			oserror();
			nexterror();
		}
		data.ftCreationTime =
		data.ftLastAccessTime =
		data.ftLastWriteTime = wintime(seconds());
		data.nFileSizeHigh = 0;
		data.nFileSizeLow = 0;
		p = runestrrchr(path, '\\');
		p++;
		runestrecpy(data.cFileName, data.cFileName+nelem(data.cFileName), p);
		data2dir(&data, &d, uc->syspath->s);
		break;
	case Tdir:
	case Tfile:
		memset(&data, 0, sizeof data);
		h = FindFirstFile(path, &data);
		if(h == INVALID_HANDLE_VALUE){
			oserror();
			nexterror();
		}
		FindClose(h);
		data2dir(&data, &d, dpath);
		break;
	}
	n = convD2M(&d, buf, n);
	free(dpath);
	free(d.uid);
	free(d.gid);
	free(d.name);
	free(path);
	poperror();
	return n;
}

static int
fswstat(Chan *c, uchar *edir, int nedir)
{
	char *strs;
	int attr, n;
	Dir dir;
	FILETIME *pat, at, *pmt, mt;
	HANDLE h;
	Rune *path, *newpath, *p;
	Uchan *uc;
	WIN32_FIND_DATA *data;

	uc = c->aux;
	path = towin(uc->syspath->s);
	strs = smalloc(nedir);
	data = smalloc(sizeof(*data));
	if(waserror()){
		free(path);
		free(strs);
		free(data);
		nexterror();
	}
	if((nedir=convM2D(edir, nedir, &dir, strs)) == 0)
		error("convM2D failed");
	switch(uc->type){
	case Trootdir:
	case Tmntdir:
	case Tserver:
		error(Eperm);
	case Tvolume:
		if(!win_usesecurity)
			error("cannot wstat volume: no security");		
		if(win_secwperm(path, &dir, 1) < 0)
			nexterror();
		break;
	case Tdir:
	case Tfile:
		h = FindFirstFile(path, data);
		if(h == INVALID_HANDLE_VALUE) {
			oserror();
			nexterror();
		}
		FindClose(h);

		pat = pmt = NULL;
		if(~dir.atime != 0){
			at = wintime(dir.atime);
			pat = &at;
		}
		if(~dir.mtime != 0){
			mt = wintime(dir.mtime);
			pmt = &mt;
		}
		if(pat || pmt){
			h = CreateFile(path, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
			if(h == INVALID_HANDLE_VALUE){
				oserror();
				nexterror();
			}
			if(!SetFileTime(h, 0, pat, pmt)){
				oserror();
				CloseHandle(h);
				nexterror();
			}
			CloseHandle(h);
		}

		attr = data->dwFileAttributes;
		if(~dir.mode != 0){
			if(dir.mode & 0222)
				attr &= ~FILE_ATTRIBUTE_READONLY;
			else
				attr |= FILE_ATTRIBUTE_READONLY;
			if(attr&FILE_ATTRIBUTE_READONLY)
				SetFileAttributes(path, attr);
		}

		//print("win_usesecurity %d\n", win_usesecurity);
		if(win_usesecurity && win_secwperm(path, &dir, attr&FILE_ATTRIBUTE_DIRECTORY) < 0)
			nexterror();

		if(~dir.mode != 0 && !(attr & FILE_ATTRIBUTE_READONLY))
			SetFileAttributes(path, attr);

		/* do last so path is valid throughout */
		if(dir.name != nil && dir.name[0]){
			p = runestrrchr(path, '\\');
			n = p-path+1;
			newpath = smalloc(sizeof(Rune)*(n+utflen(dir.name)+1));
			memmove(newpath, path, n*sizeof(Rune));
			win_utf2wstrn(newpath+n, utflen(dir.name)+1, dir.name);
			if(runestrcmp(path, newpath) != 0 && !MoveFile(path, newpath)){
				free(newpath);
				oserror();
				nexterror();
			}
			free(newpath);
		}
		break;
	case Tpipe:
		error("cannot wstat pipe");
	case Tstdin:
		error("cannot wstat stdin");
	case Tconsole:
		error("cannot wstat console");
	}
	free(path);
	free(strs);
	free(data);
	poperror();
	return nedir;
}

static void
fschdir(Chan *c)
{
	Rune *path;
	Uchan *uc;

	uc = c->aux;
	switch(uc->type){
	default:
		error("unKnown path type (BUG)");
	case Tserver:
		error("can't chdir to server yet");
	case Tvolume:
	case Tfile:
		path = towin(uc->syspath->s);
		if(path == nil)
			error("can't chdir there");
		if(!SetCurrentDirectory(path)){
			free(path);
			oserror();
			nexterror();
		}
		free(path);
		break;
	}
}

Dev devfs = 
{
	(Rune)'U',
	"fs",

	fsreset,
	fsattach,
	fswalk,
	fsstat,
	fsopen,
	fscreate,
	fsclose,
	fsread,
	devbread,
	fswrite,
	devbwrite,
	fsremove,
	fswstat,
};


static int
pathqid(int oh, char *path)
{
	uint h;
	char *p;

	h = oh;
	h = (h*1000003 + '\\');

	for(p=path; *p; p++)
		h = (h*1000003 + *p);

	return h;
}

static long
unixtime(FILETIME ft)
{
	vlong t;

	t = (vlong)ft.dwLowDateTime + ((vlong)ft.dwHighDateTime<<32);
	t -= (vlong)10000000*134774*24*60*60;

	return (long)(t/10000000);
}

static FILETIME
wintime(ulong t)
{
	FILETIME ft;
	vlong vt;

	vt = (vlong)t*10000000+(vlong)10000000*134774*24*60*60;

	ft.dwLowDateTime = vt;
	ft.dwHighDateTime = vt>>32;

	return ft;
}

static long
consolewrite(Uchan *uc, void *buf, long n)
{
	char *p;
	Rune buf2[1000], *q;
	int i, n2, on;
	static Lock lk;
	static int tmpconsole;

	qlock(&uc->qlk);
	if(uc->h == INVALID_HANDLE_VALUE) {
		lock(&lk);
		if(!tmpconsole)
			tmpconsole = AllocConsole();
		unlock(&lk);
		uc->h = GetStdHandle(STD_OUTPUT_HANDLE);
	}

	p = buf;
	on = n;

	/* handle partial runes */
	if(uc->nbuf) {
		i = uc->nbuf;
		assert(i < UTFmax);
		while(i < UTFmax && n>0) {
			uc->buf[i] = *p;
			i++;
			p++;
			n--;
			if(fullrune(uc->buf, i)) {
				uc->nbuf = 0;
				chartorune(buf2, uc->buf);
				if(!WriteConsole(uc->h, buf2, 1, &n, 0)) {
					qunlock(&uc->qlk);
					oserror();
					nexterror();
				}
				break;
			}
		}
	}

	while(n >= UTFmax || fullrune(p, n)) {
		n2 = nelem(buf2);
		q = buf2;

		while(n2) {
			if(n < UTFmax && !fullrune(p, n))
				break;
			i = chartorune(q, p);
			p += i;
			n -= i;
			n2--;
			q++;
		}
		
		if(!WriteConsole(uc->h, buf2, q-buf2, &n2, 0)) {
			qunlock(&uc->qlk);
			oserror();
			nexterror();
		}
	}

	if(n != 0) {
		if(uc->buf == 0)
			uc->buf = mallocz(UTFmax, 1);
		assert(n+uc->nbuf < UTFmax);
		memcpy(uc->buf+uc->nbuf, p, n);
		uc->nbuf += n;
	}
	qunlock(&uc->qlk);
	return on;
}

/*
 * It appears that win NT 4.0 has a bug in ReadConsole
 * If ReadConsole is called with a buffer that is smaller than
 * the input that is buffered, it appears that readconsoles buffer
 * can get corrupted by WriteConsole
 * i.e
 *      char buf[3];
 *	int n2;
 *
 *	for(;;) {
 *		if(!ReadConsole(h, buf, sizeof(buf), &n2, 0))
 *			exit(0);
 *		if(!WriteConsole(h2, buf, n2, &n2, 0))
 *			exit(0);
 *
 *		Sleep(100);
 *	}
 *
 */
static long
consoleread(Uchan *uc, void *a, long n)
{
	int n2, i;
	Rune r, rbuf[200];
	char *p;
	uchar *buf;

	buf = a;
	qlock(&uc->qlk);
	if(n == 0 || uc->h == INVALID_HANDLE_VALUE){
		qunlock(&uc->qlk);
		return 0;
	}

	while(uc->nbuf==0 && uc->eof==0) {
		if(!ReadConsole(uc->h, rbuf, nelem(rbuf), &n2, 0)){
			qunlock(&uc->qlk);
			oserror();
			nexterror();
			return -1;
		}
		if(n2 == 0)
			continue;

		if(uc->buf == 0)
			uc->buf = mallocz(sizeof(rbuf)*UTFmax, 1);
		
		for(i=0,p=uc->buf; i<n2; i++) {
			r = rbuf[i];
			if(r == '\r')
				continue;
			if(r == 0x4) {
				uc->eof = 1;
				break;
			}
			p += runetochar(p, &r);
		}
		uc->nbuf = p-uc->buf;
	}

	if(n > uc->nbuf)
		n = uc->nbuf;
	memmove(buf, uc->buf, n);
	memmove(uc->buf, uc->buf+n, uc->nbuf-n);
	uc->nbuf -= n;
	qunlock(&uc->qlk);
	return n;
}

static void
serveropen(Uchan *uc, int mode)
{
	NETRESOURCE res;
	HANDLE h;

	res.dwScope = RESOURCE_GLOBALNET;
	res.dwType = RESOURCETYPE_DISK;
	res.dwDisplayType = RESOURCEDISPLAYTYPE_GENERIC;
	res.dwUsage = RESOURCEUSAGE_CONTAINER;
	res.lpLocalName = 0;
	res.lpRemoteName = towin(uc->syspath->s);
	res.lpComment = 0;
	res.lpProvider = 0;
	if (WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_DISK, 0,
			&res, &h) != NO_ERROR) {
		free(res.lpRemoteName);
		uc->h = INVALID_HANDLE_VALUE;
		oserror();
		nexterror();
	}
	free(res.lpRemoteName);
	uc->h = h;
}

static int
serverentry(Uchan *uc, int t)
{
	int count, n;
	uchar buf[1000];
	Dir *dir;
	Rune *p;
	NETRESOURCE *res;

	n = sizeof(buf);
	count = 1;
	if(WNetEnumResource(uc->h, &count, buf, &n) != NO_ERROR)
		return 0;
	res = (NETRESOURCE*)buf;
	dir = &uc->dir;
	memset(dir, 0, sizeof(Dir));
	p = runestrrchr(res->lpRemoteName, (Rune)'\\');
	dir->name = win_wstr2utf(p+1);
	dir->mode = DMDIR | 0777;
	dir->qid.path = DMDIR;	/* BUG */
	dir->qid.vers = t;
	dir->atime = t;
	dir->mtime = t;
	uc->havedir = 1;
	return 1;
}

static long
serverread(Uchan *uc, void *a, long n, vlong off)
{
	int i, m, t;
	uchar *buf;

	qlock(&uc->qlk);
	if(waserror()){		/* serveropen can error */
		qunlock(&uc->qlk);
		nexterror();
	}
	if(off == 0){
		if(uc->havedir){
			uc->havedir = 0;
			free(uc->dir.name);
		}
		if(uc->h != INVALID_HANDLE_VALUE){
			WNetCloseEnum(uc->h);
			uc->h = INVALID_HANDLE_VALUE;
		}
		serveropen(uc, OREAD);
	}

	buf = a;
	t = seconds();
	for(i=0; i<n; i+=m){
		if(!uc->havedir && !serverentry(uc, t))
			break;
		if((m=convD2M(&uc->dir, buf+i, n-i)) <= BIT16SZ)
			break;
		uc->havedir = 0;
		free(uc->dir.name);
	}
	qunlock(&uc->qlk);
	poperror();
	return i;
}

static void
serverclose(Uchan *uc)
{
	if(uc->havedir){
		uc->havedir = 0;
		free(uc->dir.name);
	}
	if(uc->h != INVALID_HANDLE_VALUE){
		WNetCloseEnum(uc->h);
		uc->h = INVALID_HANDLE_VALUE;
	}
}

static void
data2dir(WIN32_FIND_DATA *dat, Dir *dir, char *dirpath)
{
	int ret;
	Rune *p;
	char *ext;

	dir->name = win_wstr2utf(dat->cFileName);
	dir->qid.type = 0;
	dir->qid.path = pathqid(pathqid(0, dirpath), dir->name);
	dir->atime = unixtime(dat->ftLastAccessTime);
	dir->mtime = unixtime(dat->ftLastWriteTime);
	dir->qid.vers = dir->mtime;
	dir->length = dat->nFileSizeLow | (dat->nFileSizeHigh<<32);
	dir->type = 'U';
	dir->dev = 0;

	if(!win_usesecurity){
		//print("win_usesecurity = 0\n");
		goto Nosec;
	}
	p = fspath(dirpath, dir->name);
	ret = win_secperm(dir, p);
	//print("win_secperm %S = %d\n", p, ret);
	free(p);
	if(ret < 0){
	Nosec:
		/* no NT security so make something up */
		dir->uid = strdup(eve);
		dir->gid = strdup(eve);
		dir->mode = 0666;
		ext = strrchr(dir->name, '.');
		if(ext != 0 && (cistrcmp(ext, ".exe") == 0 || cistrcmp(ext, ".rsh") == 0))
			dir->mode |= 0111;
		if(dat->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			dir->mode |= 0111;
	}

	if(dat->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
		dir->mode &= ~0222;
	if(dat->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
		dir->qid.type |= QTDIR;
		dir->mode |= DMDIR;
	}
}

static void
udata2dir(Uchan *uc)
{
	Rune *s;

	s = uc->finddata.cFileName;
	if(s[0] == 0)
		return;
	if(s[0]=='.' && (s[1]==0 || (s[1]=='.' && s[2]==0)))
		return;
	data2dir(&uc->finddata, &uc->dir, uc->syspath->s);
	uc->havedir = 1;
}

int
dirbadentry(WIN32_FIND_DATA *data)
{
	Rune *s;

	s = data->cFileName;
	if(s[0] == 0)
		return 1;
	if(s[0] == '.' && (s[1] == 0 || s[1] == '.' && s[2] == 0))
		return 1;

	return 0;
}

static int
dirnext(Uchan *uc)
{
	while(!uc->havedir){
		if(!FindNextFile(uc->h, &uc->finddata))
			return 0;
		udata2dir(uc);
	}
	return 1;
}

static long
directoryread(Uchan *uc, uchar *buf, long n, vlong off)
{
	int i, m;
	Rune *path;

	qlock(&uc->qlk);
	if(off == 0){
		DPRINT("\toffset zero\n");
		if(uc->h != INVALID_HANDLE_VALUE && uc->h != nil){
			DPRINT("FindClose %p\n", uc->h);
			FindClose(uc->h);
		}
		if(uc->havedir){
			DPRINT("Havedir\n");
			uc->havedir = 0;
			free(uc->dir.name);
			free(uc->dir.uid);
			free(uc->dir.gid);
		}
		path = fspath(uc->syspath->s, "*.*");
		uc->h = FindFirstFile(path, &uc->finddata);
		free(path);
		if(uc->h == INVALID_HANDLE_VALUE){
			qunlock(&uc->qlk);
			DPRINT("\tfindfirst failed; ret 0\n");
			return 0;
		}
		udata2dir(uc);
	}

	for(i=0; i<n; i+=m){
		if(!uc->havedir && !dirnext(uc)){
			break;
		}
		if((m=convD2M(&uc->dir, buf+i, n-i)) <= BIT16SZ){
			DPRINT("\tconvD2M returns %d; ret %d\n", m, i);
			break;
		}
		uc->havedir = 0;
		free(uc->dir.name);
		free(uc->dir.uid);
		free(uc->dir.gid);
	}
	qunlock(&uc->qlk);
	return i;
}

static void
rootdiropen(Uchan *uc)
{
	uc->dir.name = uc->rootent;
	uc->dir.uid = eve;
	uc->dir.gid = eve;
	uc->dir.mode = DMDIR|0777;
	uc->dir.qid.type = QTDIR;
	uc->dir.qid.vers = 0;
	uc->dir.length = 0;
	uc->rootoff = 0;
}

static long
rootdirread(Uchan *uc, uchar *buf, long n, vlong off)
{
	int i, m;

	qlock(&uc->qlk);
	if(off==0){
		uc->rootoff = 0;
		uc->havedir = 0;
	}
	for(i=0; i<n; i+=m){
		if(!uc->havedir){
			if(uc->rootoff>26)
				break;
			else if(uc->rootoff==26)
				strcpy(uc->rootent, "mnt");
			else{
				uc->rootent[0] = 'a'+uc->rootoff;
				uc->rootent[1] = '\0';
			}
			uc->dir.qid.path = pathqid(pathqid(0, "/"), uc->dir.name);
			uc->havedir = 1;
			uc->rootoff++;
		}
		if((m=convD2M(&uc->dir, buf+i, n-i)) <= BIT16SZ){
			DPRINT("\tconvD2M returns %d; ret %d\n", m, i);
			break;
		}
		uc->havedir = 0;
	}
	qunlock(&uc->qlk);
	return i;
}

long
syspassfd(ulong *arg)
{
	int j, fd;
	char buf[ERRMAX];
	Chan *c;
	HANDLE h, nh;
	Qid qid;
	Syscallmem *scm;
	Uchan *uc;

	if(waserror()){
		print("up %p syspassfd: %s\n", up, up->errstr);
		nexterror();
	}

	fd = arg[0];
	if(fd < 0 || fd > 2)
		error(Ebadarg);

	h = (HANDLE)arg[1];
	if(h==nil || h==INVALID_HANDLE_VALUE)
		error(Ebadarg);

	scm = up->sysaux;
	if(!DuplicateHandle(scm->hs.hclientproc, h, GetCurrentProcess(), &nh, 0, 0, DUPLICATE_SAME_ACCESS)){
		osrerrstr(buf, sizeof buf);
		snprint(up->errstr, ERRMAX, "DuplicateHandle: %s", buf);
		nexterror();
	}

	uc = smalloc(sizeof(Uchan));
	uc->h = nh;
	if(waserror()){
		free(uc);
		CloseHandle(nh);
		nexterror();
	}
	/*
	 * This won't happen, since
	 * we can't pass console handles
	 * between processes.  Idiots.
	 */
	if(GetConsoleMode(nh, &j))
		uc->type = Tconsole;
	else{
		j = GetFileType(nh);
		switch(j){
		default:
			error("unknown handle type");
		case FILE_TYPE_UNKNOWN:
		case FILE_TYPE_DISK:
		case FILE_TYPE_CHAR:
			uc->type = Tfile;
			break;
		case FILE_TYPE_PIPE:
			uc->type = Tpipe;
			break;
		}
	}
	c = newchan();
	if(waserror()){
		cclose(c);
		nexterror();
	}
	sprint(up->genbuf, "/fd/%d", fd);
	c->name = newcname(up->genbuf);
	c->type = devno('U', 1);
	mkqid(&qid, fd, 0, 0);
	c->qid = qid;
	c->aux = uc;
	c->flag |= COPEN;
	c->mode = fd==0 ? OREAD: OWRITE;
	print("up %p newfd chan %p fd %d\n", up, c, fd);
	if(newfdx(c, fd) < 0){
		print("failed %s\n", up->errstr);
		nexterror();
	}
	poperror();
	poperror();
	poperror();
	return 0;
}

long
syspassdir(ulong *arg)
{
	USED(arg);
	error(Ebadarg);
	return -1;
}
