#define UNICODE
#define Unknown win_Unknown
#include	<windows.h>
#include	<winbase.h>
#undef Unknown
#undef	Sleep
#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	"r16.h"
#include	<lm.h>

/* TODO: try using / in place of \ in path names */

#ifndef SID_MAX_SUB_AUTHORITIES
#define	SID_MAX_SUB_AUTHORITIES	15
#endif

enum
{
	MAX_SID		= sizeof(SID) + SID_MAX_SUB_AUTHORITIES*sizeof(DWORD),
	ACL_ROCK	= sizeof(ACL) + 20*(sizeof(ACCESS_ALLOWED_ACE)+MAX_SID),
	SD_ROCK		= SECURITY_DESCRIPTOR_MIN_LENGTH + MAX_SID + ACL_ROCK,
	MAXCOMP		= 128,
};

typedef struct	User	User;
typedef struct  Gmem	Gmem;
typedef	struct	Stat	Stat;
typedef	struct Fsinfo	Fsinfo;
typedef	WIN32_FIND_DATA	Fsdir;

#ifndef INVALID_SET_FILE_POINTER
#define	INVALID_SET_FILE_POINTER	((DWORD)-1)
#endif

struct Fsinfo
{
	int	uid;
	int	gid;
	int	mode;
	int	fd;
	vlong	offset;
	QLock	oq;
	char*	spec;
	Rune16*	srv;
	Cname*	name;	/* Windows' idea of the file name */
	ushort	usesec;
	ushort	checksec;
	Fsdir*	de;	/* non-nil for saved entry from last dirread at offset */
};
#define	FS(c)	((Fsinfo*)(c)->aux)

/*
 * info about a user or group
 * there are two ways to specify a user:
 *	by sid, a unique identifier
 *	by user and domain names
 * this structure is used to convert between the two,
 * as well as figure out which groups a users belongs to.
 * the user information never gets thrown away,
 * but the group information gets refreshed with each setid.
 */
struct User
{
	QLock	lk;		/* locks the gotgroup and group fields */
	SID	*sid;
	Rune16	*name;
	Rune16	*dom;
	int	type;		/* the type of sid, ie SidTypeUser, SidTypeAlias, ... */
	int	gotgroup;	/* tried to add group */
	Gmem	*group;		/* global and local groups to which this user or group belongs. */
	User	*next;
};

struct Gmem
{
	User	*user;
	Gmem	*next;
};

/*
 * intermediate stat information
 */
struct Stat
{
	User	*owner;
	User	*group;
	ulong	mode;
};

/*
 * some "well-known" sids
 */
static	SID	*creatorowner;
static	SID	*creatorgroup;
static	SID	*everyone;
static	SID	*ntignore;
static	SID	*ntroot;	/* user who is supposed to run emu as a server */

/*
 * all users we ever see end up in this table
 * users are never deleted, but we should update
 * group information for users sometime
 */
static struct
{
	QLock	lk;
	User	*u;
}users;

/*
 * conversion from inferno permission modes to nt access masks
 * is this good enough?  this is what nt sets, except for NOMODE
 */
#define	NOMODE	(READ_CONTROL|FILE_READ_EA|FILE_READ_ATTRIBUTES)
#define	RMODE	(READ_CONTROL|SYNCHRONIZE\
		|FILE_READ_DATA|FILE_READ_EA|FILE_READ_ATTRIBUTES)
#define	XMODE	(READ_CONTROL|SYNCHRONIZE\
		|FILE_EXECUTE|FILE_READ_ATTRIBUTES)
#define	WMODE	(DELETE|READ_CONTROL|SYNCHRONIZE|WRITE_DAC|WRITE_OWNER\
		|FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA\
		|FILE_DELETE_CHILD|FILE_WRITE_ATTRIBUTES)

static	int
modetomask[] =
{
	NOMODE,
	XMODE,
	WMODE,
	WMODE|XMODE,
	RMODE,
	RMODE|XMODE,
	RMODE|WMODE,
	RMODE|WMODE|XMODE,
};

extern	DWORD	PlatformId;
	char    rootdir[MAXROOT] = "\\inferno";
	Rune16	rootname[] = L"inferno-server";
static	Qid	rootqid;
static	User	*fsnone;
static	User	*fsuser;
static	Rune16	*ntsrv;
static	int	usesec;
static	int	checksec;
static	int	isserver;
static	int	file_share_delete;
static	uchar	isntfrog[256];

static	void		fsremove(Chan*);

	wchar_t	*widen(char *s);
	char		*narrowen(wchar_t *ws);
	int		widebytes(wchar_t *ws);

static char Etoolong[] = "file name too long";

extern	int		nth2fd(HANDLE);
extern	HANDLE		ntfd2h(int);
static	int		cnisroot(Cname*);
static	int		fsisroot(Chan*);
static	int		okelem(char*, int);
static	int		fsexist(char*, Qid*);
static	char*	fspath(Cname*, char*, char*, char*);
static	Cname*	fswalkpath(Cname*, char*, int);
static	char*	fslastelem(Cname*);
static	long		fsdirread(Chan*, uchar*, int, vlong);
static	ulong		fsqidpath(char*);
static	int		fsomode(int);
static	int		fsdirset(char*, int, WIN32_FIND_DATA*, char*, Chan*, int isdir);
static 	int		fsdirsize(WIN32_FIND_DATA*, char*, Chan*);
static	void		fssettime(char*, long, long);
static	long		unixtime(FILETIME);
static	FILETIME	wintime(ulong);
static	void		secinit(void);
static	int		secstat(Dir*, char*, Rune16*);
static	int		secsize(char*, Rune16*);
static	void		seccheck(char*, ulong, Rune16*);
static	int		sechasperm(char*, ulong, Rune16*);
static	SECURITY_DESCRIPTOR* secsd(char*, char[SD_ROCK]);
static	int		secsdhasperm(SECURITY_DESCRIPTOR*, ulong, Rune16*);
static	int		secsdstat(SECURITY_DESCRIPTOR*, Stat*, Rune16*);
static	SECURITY_DESCRIPTOR* secmksd(char[SD_ROCK], Stat*, ACL*, int);
static	SID		*dupsid(SID*);
static	int		ismembersid(Rune16*, User*, SID*);
static	int		ismember(User*, User*);
static	User		*sidtouser(Rune16*, SID*);
static	User		*domnametouser(Rune16*, Rune16*, Rune16*);
static	User		*nametouser(Rune16*, Rune16*);
static	User		*unametouser(Rune16*, char*);
static	void		addgroups(User*, int);
static	User		*mkuser(SID*, int, Rune16*, Rune16*);
static	Rune16		*domsrv(Rune16 *, Rune16[MAX_PATH]);
static	Rune16		*filesrv(char*);
static	int		fsacls(char*);
static	User		*secuser(void);


int
winfilematch(char *path, WIN32_FIND_DATA *data)
{
	char *p;
	wchar_t *wpath;
	int r;

	p = path+strlen(path);
	while(p > path && p[-1] != '\\')
		--p;
	wpath = widen(p);
	r = (data->cFileName[0] == '.' && runes16len(data->cFileName) == 1)
			|| runes16cmp(data->cFileName, wpath) == 0;
	free(wpath);
	return r;
}

int
winfileclash(char *path)
{
	HANDLE h;
	WIN32_FIND_DATA data;
	wchar_t *wpath;

	wpath = widen(path);
	h = FindFirstFile(wpath, &data);
	free(wpath);
	if (h != INVALID_HANDLE_VALUE) {
		FindClose(h);
		return !winfilematch(path, &data);
	}
	return 0;
}


/*
 * this gets called to set up the environment when we switch users
 */
void
setid(char *name, int owner)
{
	User *u;

	if(owner && !iseve())
		return;

	kstrdup(&up->env->user, name);

	if(!usesec)
		return;

	u = unametouser(ntsrv, up->env->user);
	if(u == nil)
		u = fsnone;
	else {
		qlock(&u->lk);
		addgroups(u, 1);
		qunlock(&u->lk);
	}
	if(u == nil)
		panic("setid: user nil\n");

	up->env->ui = u;
}

static void
fsfree(Chan *c)
{
	cnameclose(FS(c)->name);
	if(FS(c)->de != nil)
		free(FS(c)->de);
	free(FS(c));
}

void
fsinit(void)
{
	int n, isvol;
	ulong attr;
	char *p, tmp[MAXROOT];
	wchar_t *wp, *wpath, *last;
	wchar_t wrootdir[MAXROOT];

	isntfrog['/'] = 1;
	isntfrog['\\'] = 1;
	isntfrog[':'] = 1;
	isntfrog['*'] = 1;
	isntfrog['?'] = 1;
	isntfrog['"'] = 1;
	isntfrog['<'] = 1;
	isntfrog['>'] = 1;

	/*
	 * vet the root
	 */
	strcpy(tmp, rootdir);
	for(p = tmp; *p; p++)
		if(*p == '/')
			*p = '\\';
	if(tmp[0] != 0 && tmp[1] == ':') {
		if(tmp[2] == 0) {
			tmp[2] = '\\';
			tmp[3] = 0;
		}
		else if(tmp[2] != '\\') {
			/* don't allow c:foo - only c:\foo */
			panic("illegal root pathX");
		}
	}
	wrootdir[0] = '\0';
	wpath = widen(tmp);
	for(wp = wpath; *wp; wp++) {
		if(*wp < 32 || (*wp < 256 && isntfrog[*wp] && *wp != '\\' && *wp != ':'))
			panic("illegal root path");
	}
	n = GetFullPathName(wpath, MAXROOT, wrootdir, &last);
	free(wpath);	
	runes16toutf(rootdir, wrootdir, MAXROOT);
	if(n >= MAXROOT || n == 0)
		panic("illegal root path");

	/* get rid of trailing \ */
	while(rootdir[n-1] == '\\') {
		if(n <= 2) {
			panic("illegal root path");
		}
		rootdir[--n] = '\0';
	}

	isvol = 0;
	if(rootdir[1] == ':' && rootdir[2] == '\0')
		isvol = 1;
	else if(rootdir[0] == '\\' && rootdir[1] == '\\') {
		p = strchr(&rootdir[2], '\\');
		if(p == nil)
			panic("inferno root can't be a server");
		isvol = strchr(p+1, '\\') == nil;
	}

	if(strchr(rootdir, '\\') == nil)
		strcat(rootdir, "\\.");
	attr = GetFileAttributes(wrootdir);
	if(attr == 0xFFFFFFFF)
		panic("root path '%s' does not exist", narrowen(wrootdir));
	rootqid.path = fsqidpath(rootdir);
	if(attr & FILE_ATTRIBUTE_DIRECTORY)
		rootqid.type |= QTDIR;
	rootdir[n] = '\0';

	rootqid.vers = time(0);

	/*
	 * set up for nt file security checking
	 */
	ntsrv = filesrv(rootdir);
	usesec = PlatformId == VER_PLATFORM_WIN32_NT; 	/* true for NT and 2000 */
	if(usesec){
		file_share_delete = FILE_SHARE_DELETE;	/* sensible handling of shared files by delete and rename */
		secinit();
		if(!fsacls(rootdir))
			usesec = 0;
	}
	checksec = usesec && isserver;
}

Chan*
fsattach(char *spec)
{
	Chan *c;
	static int devno;
	static Lock l;
	char *drive = (char *)spec;

	if (!emptystr(drive) && (drive[1] != ':' || drive[2] != '\0'))
		error(Ebadspec);

	c = devattach('U', spec);
	lock(&l);
	c->dev = devno++;
	unlock(&l);
	c->qid = rootqid;
	c->aux = smalloc(sizeof(Fsinfo));
	FS(c)->srv = ntsrv;
	if(!emptystr(spec)) {
		char *s = smalloc(strlen(spec)+1);
		strcpy(s, spec);
		FS(c)->spec = s;
		FS(c)->srv = filesrv(spec);
		if(usesec)
			FS(c)->usesec = fsacls(spec);
		FS(c)->checksec = FS(c)->usesec && isserver;
		c->qid.path = fsqidpath(spec);
		c->qid.type = QTDIR;
		c->qid.vers = 0;
	}else{
		FS(c)->usesec = usesec;
		FS(c)->checksec = checksec;
	}
	FS(c)->name = newcname("/");
	return c;
}

Walkqid*
fswalk(Chan *c, Chan *nc, char **name, int nname)
{
	int j, alloc;
	Walkqid *wq;
	char path[MAX_PATH], *p;
	Cname *ph;
	Cname *current, *next;

	if(nname > 0)
		isdir(c);

	alloc = 0;
	current = nil;
	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
	if(waserror()){
		if(alloc && wq->clone != nil)
			cclose(wq->clone);
		cnameclose(current);
		free(wq);
		return nil;
	}
	if(nc == nil){
		nc = devclone(c);
		nc->type = 0;
		alloc = 1;
	}
	wq->clone = nc;
	current = FS(c)->name;
	if(current != nil)
		incref(&current->r);
	for(j = 0; j < nname; j++){
		if(!(nc->qid.type&QTDIR)){
			if(j==0)
				error(Enotdir);
			break;
		}
		if(!okelem(name[j], 0)){
			if(j == 0)
				error(Efilename);
			break;
		}
		p = fspath(current, name[j], path, FS(c)->spec);
		if(FS(c)->checksec) {
			*p = '\0';
			if(!sechasperm(path, XMODE, FS(c)->srv)){
				if(j == 0)
					error(Eperm);
				break;
			}
			*p = '\\';
		}

		if(strcmp(name[j], "..") == 0) {
			if(fsisroot(c))
				nc->qid = rootqid;
			else{
				ph = fswalkpath(current, "..", 1);
				if(cnisroot(ph)){
					nc->qid = rootqid;
					current = ph;
					if(current != nil)
						incref(&current->r);
				}
				else {
					fspath(ph, 0, path, FS(c)->spec);
					if(!fsexist(path, &nc->qid)){
						cnameclose(ph);
						if(j == 0)
							error(Enonexist);
						break;
					}
				}
				next = fswalkpath(current, name[j], 1);
				cnameclose(current);
				current = next;
				cnameclose(ph);
			}
		}
		else{
			if(!fsexist(path, &nc->qid)){
				if(j == 0)
					error(Enonexist);
				break;
			}
			next = fswalkpath(current, name[j], 1);
			cnameclose(current);
			current = next;
		}
		wq->qid[wq->nqid++] = nc->qid;
	}
	poperror();
	if(wq->nqid < nname){
		cnameclose(current);
		if(alloc)
			cclose(wq->clone);
		wq->clone = nil;
	}else if(wq->clone){
		nc->aux = smalloc(sizeof(Fsinfo));
		nc->type = c->type;
		FS(nc)->spec = FS(c)->spec;
		FS(nc)->srv = FS(c)->srv;
		FS(nc)->name = current;
		FS(nc)->usesec = FS(c)->usesec;
		FS(nc)->checksec = FS(c)->checksec;
	}
	return wq;
}

Chan*
fsopen(Chan *c, int mode)
{
	HANDLE h;
	int m, isdir, aflag, cflag;
	char path[MAX_PATH];
	wchar_t *wpath;

	isdir = c->qid.type & QTDIR;
	if(isdir && mode != OREAD)
		error(Eperm);
	fspath(FS(c)->name, 0, path, FS(c)->spec);

	if(FS(c)->checksec) {
		switch(mode & (OTRUNC|3)) {
		case OREAD:
			seccheck(path, RMODE, FS(c)->srv);
			break;
		case OWRITE:
		case OWRITE|OTRUNC:
			seccheck(path, WMODE, FS(c)->srv);
			break;
		case ORDWR:
		case ORDWR|OTRUNC:
		case OREAD|OTRUNC:
			seccheck(path, RMODE|WMODE, FS(c)->srv);
			break;
		case OEXEC:
			seccheck(path, XMODE, FS(c)->srv);
			break;
		default:
			error(Ebadarg);
		}
	}

	c->mode = openmode(mode);
	if(isdir)
		FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
	else {
		m = fsomode(mode & 3);
		cflag = OPEN_EXISTING;
		if(mode & OTRUNC)
			cflag = TRUNCATE_EXISTING;
		aflag = FILE_FLAG_RANDOM_ACCESS;
		if(mode & ORCLOSE)
			aflag |= FILE_FLAG_DELETE_ON_CLOSE;
		if (winfileclash(path))
			error(Eexist);
		wpath = widen(path);
		h = CreateFile(wpath, m, FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, 0, cflag, aflag, 0);
		free(wpath);
		if(h == INVALID_HANDLE_VALUE)
			oserror();
		FS(c)->fd = nth2fd(h);
	}

	c->offset = 0;
	FS(c)->offset = 0;
	c->flag |= COPEN;
	return c;
}

void
fscreate(Chan *c, char *name, int mode, ulong perm)
{
	Stat st;
	HANDLE h;
	int m, aflag;
	SECURITY_ATTRIBUTES sa;
	SECURITY_DESCRIPTOR *sd;
	BY_HANDLE_FILE_INFORMATION hi;
	char *p, path[MAX_PATH], sdrock[SD_ROCK];
	wchar_t *wpath;
	ACL *acl;

	if(!okelem(name, 1))
		error(Efilename);

	m = fsomode(mode & 3);
	p = fspath(FS(c)->name, name, path, FS(c)->spec);
	acl = (ACL*)smalloc(ACL_ROCK);
	sd = nil;
	if(FS(c)->usesec) {
		*p = '\0';
		sd = secsd(path, sdrock);
		*p = '\\';
		if(sd == nil){
			free(acl);
			oserror();
		}
		if(FS(c)->checksec && !secsdhasperm(sd, WMODE, FS(c)->srv)
		|| !secsdstat(sd, &st, FS(c)->srv)){
			if(sd != (void*)sdrock)
				free(sd);
			free(acl);
			error(Eperm);
		}
		if(sd != (void*)sdrock)
			free(sd);
		if(perm & DMDIR)
			st.mode = (perm & ~0777) | (st.mode & perm & 0777);
		else
			st.mode = (perm & ~0666) | (st.mode & perm & 0666);
		st.owner = up->env->ui;
		if(!isserver)
			st.owner = fsuser;
		sd = secmksd(sdrock, &st, acl, perm & DMDIR);
		if(sd == nil){
			free(acl);
			oserror();
		}
	}
	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor = sd;
	sa.bInheritHandle = 0;

	if(perm & DMDIR) {
		if(mode != OREAD) {
			free(acl);
			error(Eisdir);
		}
		wpath = widen(path);
		if(!CreateDirectory(wpath, &sa) || !fsexist(path, &c->qid)) {
			free(wpath);
			free(acl);
			oserror();
		}
		free(wpath);
		FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
	}
	else {
		aflag = 0;
		if(mode & ORCLOSE)
			aflag = FILE_FLAG_DELETE_ON_CLOSE;
		if (winfileclash(path))
			error(Eexist);
		wpath = widen(path);
		h = CreateFile(wpath, m, FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, &sa, CREATE_ALWAYS, aflag, 0);
		free(wpath);
		if(h == INVALID_HANDLE_VALUE) {
			free(acl);
			oserror();
		}
		FS(c)->fd = nth2fd(h);
		c->qid.path = fsqidpath(path);
		c->qid.type = 0;
		c->qid.vers = 0;
		if(GetFileInformationByHandle(h, &hi))
			c->qid.vers = unixtime(hi.ftLastWriteTime);
	}

	c->mode = openmode(mode);
	c->offset = 0;
	FS(c)->offset = 0;
	c->flag |= COPEN;
	FS(c)->name = fswalkpath(FS(c)->name, name, 0);
	free(acl);
}

void
fsclose(Chan *c)
{
	HANDLE h;

	if(c->flag & COPEN){
		h = ntfd2h(FS(c)->fd);
		if(h != INVALID_HANDLE_VALUE){
			if(c->qid.type & QTDIR)
				FindClose(h);
			else
				CloseHandle(h);
		}
	}
	if(c->flag & CRCLOSE){
		if(!waserror()){
			fsremove(c);
			poperror();
		}
		return;
	}
	fsfree(c);
}

/*
 * 64-bit seeks, using SetFilePointer because SetFilePointerEx
 * is not supported by NT
 */
static void
fslseek(HANDLE h, vlong offset)
{
	LONG hi;

	if(offset <= 0x7fffffff){
		if(SetFilePointer(h, (LONG)offset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
			oserror();
	}else{
		hi = offset>>32;
		if(SetFilePointer(h, (LONG)offset, &hi, FILE_BEGIN) == INVALID_SET_FILE_POINTER &&
		   GetLastError() != NO_ERROR)
			oserror();
	}
}

long
fsread(Chan *c, void *va, long n, vlong offset)
{
	DWORD n2;
	HANDLE h;

	qlock(&FS(c)->oq);
	if(waserror()){
		qunlock(&FS(c)->oq);
		nexterror();
	}
	if(c->qid.type & QTDIR) {
		n2 = fsdirread(c, va, n, offset);
	}
	else {
		h = ntfd2h(FS(c)->fd);
		if(FS(c)->offset != offset){
			fslseek(h, offset);
			FS(c)->offset = offset;
		}
		if(!ReadFile(h, va, n, &n2, NULL))
			oserror();
		FS(c)->offset += n2;
	}
	qunlock(&FS(c)->oq);
	poperror();
	return n2;
}

long
fswrite(Chan *c, void *va, long n, vlong offset)
{
	DWORD n2;
	HANDLE h;

	qlock(&FS(c)->oq);
	if(waserror()){
		qunlock(&FS(c)->oq);
		nexterror();
	}
	h = ntfd2h(FS(c)->fd);
	if(FS(c)->offset != offset){
		fslseek(h, offset);
		FS(c)->offset = offset;
	}
	if(!WriteFile(h, va, n, &n2, NULL))
		oserror();
	FS(c)->offset += n2;
	qunlock(&FS(c)->oq);
	poperror();
	return n2;
}

int
fsstat(Chan *c, uchar *buf, int n)
{
	WIN32_FIND_DATA data;
	char path[MAX_PATH];
	wchar_t *wpath;

	/*
	 * have to fake up a data for volumes like
	 * c: and \\server\share since you can't FindFirstFile them
	 */
	if(fsisroot(c)){
		strcpy(path, rootdir);
		if(strchr(path, '\\') == nil)
			strcat(path, "\\.");
		wpath = widen(path);
		data.dwFileAttributes = GetFileAttributes(wpath);
		free(wpath);
		if(data.dwFileAttributes == 0xffffffff)
			oserror();
		data.ftCreationTime =
		data.ftLastAccessTime =
		data.ftLastWriteTime = wintime(time(0));
		data.nFileSizeHigh = 0;
		data.nFileSizeLow = 0;
		utftorunes16(data.cFileName, ".", MAX_PATH);
	} else {
		HANDLE h = INVALID_HANDLE_VALUE;

		fspath(FS(c)->name, 0, path, FS(c)->spec);
		if (c->flag & COPEN)
			h = ntfd2h(FS(c)->fd);

		if (h != INVALID_HANDLE_VALUE) {
			BY_HANDLE_FILE_INFORMATION fi;
			if (c->mode & OWRITE)
				FlushFileBuffers(h);
			if (!GetFileInformationByHandle(h, &fi))
				oserror();
			data.dwFileAttributes = fi.dwFileAttributes;
			data.ftCreationTime = fi.ftCreationTime;
			data.ftLastAccessTime = fi.ftLastAccessTime;
			data.ftLastWriteTime = fi.ftLastWriteTime;;
			data.nFileSizeHigh = fi.nFileSizeHigh;
			data.nFileSizeLow = fi.nFileSizeLow;
		} else {
			wpath = widen(path);
			h = FindFirstFile(wpath, &data);
			free(wpath);
			if(h == INVALID_HANDLE_VALUE)
				oserror();
			if (!winfilematch(path, &data)) {
				FindClose(h);
				error(Enonexist);
			}
			FindClose(h);
		}
		utftorunes16(data.cFileName, fslastelem(FS(c)->name), MAX_PATH);
	}

	return fsdirset(buf, n, &data, path, c, 0);
}

int
fswstat(Chan *c, uchar *buf, int n)
{
	int wsd;
	Dir dir;
	Stat st;
	Cname * volatile ph;
	HANDLE h;
	ulong attr;
	User *ou, *gu;
	WIN32_FIND_DATA data;
	SECURITY_DESCRIPTOR *sd;
	char *last, sdrock[SD_ROCK], path[MAX_PATH], newpath[MAX_PATH], strs[4*256];
	wchar_t wspath[MAX_PATH], wsnewpath[MAX_PATH];
	wchar_t *wpath;
	int nmatch;

	n = convM2D(buf, n, &dir, strs);
	if(n == 0)
		error(Eshortstat);

	last = fspath(FS(c)->name, 0, path, FS(c)->spec);
	utftorunes16(wspath, path, MAX_PATH);

	if(fsisroot(c)){
		if(dir.atime != ~0)
			data.ftLastAccessTime = wintime(dir.atime);
		if(dir.mtime != ~0)
			data.ftLastWriteTime = wintime(dir.mtime);
		utftorunes16(data.cFileName, ".", MAX_PATH);
	}else{
		h = FindFirstFile(wspath, &data);
		if(h == INVALID_HANDLE_VALUE)
			oserror();
		if (!winfilematch(path, &data)) {
			FindClose(h);
			error(Enonexist);
		}
		FindClose(h);
	}

	wsd = 0;
	ou = nil;
	gu = nil;
	if(FS(c)->usesec) {
		if(FS(c)->checksec && up->env->ui == fsnone)
			error(Eperm);

		/*
		 * find new owner and group
		 */
		if(!emptystr(dir.uid)){
			ou = unametouser(FS(c)->srv, dir.uid);
			if(ou == nil)
				oserror();
		}
		if(!emptystr(dir.gid)){
			gu = unametouser(FS(c)->srv, dir.gid);
			if(gu == nil){
				if(strcmp(dir.gid, "unknown") != 0
				&& strcmp(dir.gid, "deleted") != 0)
					oserror();
				gu = ou;
			}
		}

		/*
		 * find old stat info
		 */
		sd = secsd(path, sdrock);
		if(sd == nil || !secsdstat(sd, &st, FS(c)->srv)){
			if(sd != nil && sd != (void*)sdrock)
				free(sd);
			oserror();
		}
		if(sd != (void*)sdrock)
			free(sd);

		/*
		 * permission rules:
		 * if none, can't do anything
		 * chown => no way
		 * chgrp => current owner or group, and in new group
		 * mode/time => owner or in either group
		 * rename => write in parent
		 */
		if(ou == nil)
			ou = st.owner;
		if(FS(c)->checksec && st.owner != ou)
			error(Eperm);

		if(gu == nil)
			gu = st.group;
		if(st.group != gu){
			if(FS(c)->checksec
			&&(!ismember(up->env->ui, ou) && !ismember(up->env->ui, gu)
			|| !ismember(up->env->ui, st.group)))
				error(Eperm);
			wsd = 1;
		}

		if(dir.atime != ~0 && unixtime(data.ftLastAccessTime) != dir.atime
		|| dir.mtime != ~0 && unixtime(data.ftLastWriteTime) != dir.mtime
		|| dir.mode != ~0 && st.mode != dir.mode){
			if(FS(c)->checksec
			&& !ismember(up->env->ui, ou)
			&& !ismember(up->env->ui, gu)
			&& !ismember(up->env->ui, st.group))
				error(Eperm);
			if(dir.mode != ~0 && st.mode != dir.mode)
				wsd = 1;
		}
	}
	wpath = widen(dir.name);
	nmatch = runes16cmp(wpath, data.cFileName);
	free(wpath);
	if(!emptystr(dir.name) && nmatch != 0){
		if(!okelem(dir.name, 1))
			error(Efilename);
		ph = fswalkpath(FS(c)->name, "..", 1);
		if(waserror()){
			cnameclose(ph);
			nexterror();
		}
		ph = fswalkpath(ph, dir.name, 0);
		fspath(ph, 0, newpath, FS(c)->spec);
		utftorunes16(wsnewpath, newpath, MAX_PATH);
		if(GetFileAttributes(wpath) != 0xffffffff && !winfileclash(newpath))
			error("file already exists");
		if(fsisroot(c))
			error(Eperm);
		if(FS(c)->checksec){
			*last = '\0';
			seccheck(path, WMODE, FS(c)->srv);
			*last = '\\';
		}
		poperror();
		cnameclose(ph);
	}

	if(dir.atime != ~0 && unixtime(data.ftLastAccessTime) != dir.atime
	|| dir.mtime != ~0 && unixtime(data.ftLastWriteTime) != dir.mtime)
		fssettime(path, dir.atime, dir.mtime);

	attr = data.dwFileAttributes;
	if(dir.mode & 0222)
		attr &= ~FILE_ATTRIBUTE_READONLY;
	else
		attr |= FILE_ATTRIBUTE_READONLY;
	if(!fsisroot(c)
	&& attr != data.dwFileAttributes
	&& (attr & FILE_ATTRIBUTE_READONLY))
		SetFileAttributes(wspath, attr);
	if(FS(c)->usesec && wsd){
		ACL *acl = (ACL *) smalloc(ACL_ROCK);
		st.owner = ou;
		st.group = gu;
		if(dir.mode != ~0)
			st.mode = dir.mode;
		sd = secmksd(sdrock, &st, acl, data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
		if(sd == nil || !SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd)){
			free(acl);
			oserror();
		}
		free(acl);
	}

	if(!fsisroot(c)
	&& attr != data.dwFileAttributes
	&& !(attr & FILE_ATTRIBUTE_READONLY))
		SetFileAttributes(wspath, attr);

	/* do last so path is valid throughout */
	wpath = widen(dir.name);
	nmatch = runes16cmp(wpath, data.cFileName);
	free(wpath);
	if(!emptystr(dir.name) && nmatch != 0) {
		ph = fswalkpath(FS(c)->name, "..", 1);
		if(waserror()){
			cnameclose(ph);
			nexterror();
		}
		ph = fswalkpath(ph, dir.name, 0);
		fspath(ph, 0, newpath, FS(c)->spec);
		utftorunes16(wsnewpath, newpath, MAX_PATH);
		/*
		 * can't rename if it is open: if this process has it open, close it temporarily.
		 */
		if(!file_share_delete && c->flag & COPEN){
			h = ntfd2h(FS(c)->fd);
			if(h != INVALID_HANDLE_VALUE)
				CloseHandle(h);	/* woe betide it if ORCLOSE */
			FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
		}
		if(!MoveFile(wspath, wsnewpath)) {
			oserror();
		} else if(!file_share_delete && c->flag & COPEN) {
			int	aflag;
			SECURITY_ATTRIBUTES sa;
			
			/* The move succeeded, so open new file to maintain handle */
			sa.nLength = sizeof(sa);
			sa.lpSecurityDescriptor = sd;
			sa.bInheritHandle = 0;
			if(c->flag & CRCLOSE)
				aflag = FILE_FLAG_DELETE_ON_CLOSE;
			h = CreateFile(wsnewpath, fsomode(c->mode & 0x3), FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, &sa, OPEN_EXISTING, aflag, 0);
			if(h == INVALID_HANDLE_VALUE)
				oserror();
			FS(c)->fd = nth2fd(h);
		}
		cnameclose(FS(c)->name);
		poperror();
		FS(c)->name = ph;
	}
	return n;
}

static void
fsremove(Chan *c)
{
	int n;
	char *p, path[MAX_PATH];
	wchar_t wspath[MAX_PATH];

	if(waserror()){
		fsfree(c);
		nexterror();
	}
	if(fsisroot(c))
		error(Eperm);
	p = fspath(FS(c)->name, 0, path, FS(c)->spec);
	utftorunes16(wspath, path, MAX_PATH);
	if(FS(c)->checksec){
		*p = '\0';
		seccheck(path, WMODE, FS(c)->srv);
		*p = '\\';
	}
	if(c->qid.type & QTDIR)
		n = RemoveDirectory(wspath);
	else
		n = DeleteFile(wspath);
	if (!n) {
		ulong attr, mode;
		SECURITY_DESCRIPTOR *sd = nil;
		char sdrock[SD_ROCK];
		Stat st;
		int secok;
		attr = GetFileAttributes(wspath);
		if(attr != 0xFFFFFFFF) {
			if (FS(c)->usesec) {
				sd = secsd(path, sdrock);
				secok = (sd != nil) && secsdstat(sd, &st, FS(c)->srv);
				if (secok) {
					ACL *acl = (ACL *) smalloc(ACL_ROCK);
					mode = st.mode;
					st.mode |= 0660;
					sd = secmksd(sdrock, &st, acl, attr & FILE_ATTRIBUTE_DIRECTORY);
					if(sd != nil) {
						SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd);
					}
					free(acl);
					if(sd != nil && sd != (void*)sdrock)
						free(sd);
					sd = nil;
				}
			}
			SetFileAttributes(wspath, FILE_ATTRIBUTE_NORMAL);
			if(c->qid.type & QTDIR)
				n = RemoveDirectory(wspath);
			else
				n = DeleteFile(wspath);
			if (!n) {
				if (FS(c)->usesec && secok) {
					ACL *acl = (ACL *) smalloc(ACL_ROCK);
					st.mode =  mode;
					sd = secmksd(sdrock, &st, acl, attr & FILE_ATTRIBUTE_DIRECTORY);
					if(sd != nil) {
						SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd);
					}
					free(acl);
				}
				SetFileAttributes(wspath, attr);
				if(sd != nil && sd != (void*)sdrock)
					free(sd);
			}
		}
	}
	if(!n)
		oserror();
	poperror();
	fsfree(c);
}

/*
 * check elem for illegal characters /\:*?"<>
 * ... and relatives are also disallowed,
 * since they specify grandparents, which we
 * are not prepared to handle
 */
static int
okelem(char *elem, int nodots)
{
	int c, dots;

	dots = 0;
	while((c = *(uchar*)elem) != 0){
		if(isntfrog[c])
			return 0;
		if(c == '.' && dots >= 0)
			dots++;
		else
			dots = -1;
		elem++;
	}
	if(nodots)
		return dots <= 0;
	return dots <= 2;
}

static int
cnisroot(Cname *c)
{
	return strcmp(c->s, "/") == 0;
}

static int
fsisroot(Chan *c)
{
	return strcmp(FS(c)->name->s, "/") == 0;
}

static char*
fspath(Cname *c, char *ext, char *path, char *spec)
{
	char *p, *last, *rootd;
	int extlen = 0;

	rootd = spec != nil ? spec : rootdir;
	if(ext)
		extlen = strlen(ext) + 1;
	if(strlen(rootd) + extlen >= MAX_PATH)
		error(Etoolong);
	strcpy(path, rootd);
	if(cnisroot(c)){
		if(ext) {
			strcat(path, "\\");
			strcat(path, ext);
		}
	}else{
		if(*c->s != '/') {
			if(strlen(path) + 1 >= MAX_PATH)
				error(Etoolong);
			strcat(path, "\\");
		}
		if(strlen(path) + strlen(c->s) + extlen >= MAX_PATH)
			error(Etoolong);
		strcat(path, c->s);
		if(ext){
			strcat(path, "\\");
			strcat(path, ext);
		}
	}
	last = path;
	for(p = path; *p != '\0'; p++){
		if(*p == '/' || *p == '\\'){
			*p = '\\';
			last = p;
		}
	}
	return last;
}

extern void cleancname(Cname*);

static Cname *
fswalkpath(Cname *c, char *name, int dup)
{
	if(dup)
		c = newcname(c->s);
	c = addelem(c, name);
	if(isdotdot(name))
		cleancname(c);
	return c;
}

static char *
fslastelem(Cname *c)
{
	char *p;

	p = c->s + c->len;
	while(p > c->s && p[-1] != '/')
		p--;
	return p;
}

static int
fsdirbadentry(WIN32_FIND_DATA *data)
{
	wchar_t *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 Fsdir*
fsdirent(Chan *c, char *path, Fsdir *data)
{
	wchar_t *wpath;
	HANDLE h;

	h = ntfd2h(FS(c)->fd);
	if(data == nil)
		data = smalloc(sizeof(*data));
	if(FS(c)->offset == 0){
		if(h != INVALID_HANDLE_VALUE)
			FindClose(h);
		wpath = widen(path);
		h = FindFirstFile(wpath, data);
		free(wpath);
		FS(c)->fd = nth2fd(h);
		if(h == INVALID_HANDLE_VALUE){
			free(data);
			return nil;
		}
		if(!fsdirbadentry(data))
			return data;
	}
	do{
		if(!FindNextFile(h, data)){
			free(data);
			return nil;
		}
	}while(fsdirbadentry(data));
	return data;
}

static long
fsdirread(Chan *c, uchar *va, int count, vlong offset)
{
	int i, r;
	char path[MAX_PATH], *p;
	Fsdir *de;
	vlong o;

	if(count == 0 || offset < 0)
		return 0;
	p = fspath(FS(c)->name, "*.*", path, FS(c)->spec);
	p++;
	de = nil;
	if(FS(c)->offset != offset){
		de = FS(c)->de;
		if(FS(c)->de != nil){
			free(FS(c)->de);
			FS(c)->de = nil;
		}
		FS(c)->offset = 0;
		for(o = 0; o < offset;){
			de = fsdirent(c, path, de);
			if(de == nil){
				FS(c)->offset = o;
				return 0;
			}
			runes16toutf(p, de->cFileName, &path[MAX_PATH]-p);
			path[MAX_PATH-1] = '\0';
			o += fsdirsize(de, path, c);
		}
		FS(c)->offset = offset;
	}
	for(i = 0; i < count;){
		if(FS(c)->de != nil){	/* left over from previous read at offset */
			de = FS(c)->de;
			FS(c)->de = nil;
		}else{
			de = fsdirent(c, path, de);
			if(de == nil)
				break;
		}
		runes16toutf(p, de->cFileName, &path[MAX_PATH]-p);
		path[MAX_PATH-1] = '\0';
		r = fsdirset(va+i, count-i, de, path, c, 1);
		if(r <= 0){
			/* won't fit; save for next read at this offset */
			FS(c)->de = de;
			break;
		}
		i += r;
		FS(c)->offset += r;
	}
	return i;
}

static ulong
fsqidpath(char *p)
{
	ulong h;
	int c;

	h = 0;
	while(*p != '\0'){
		/* force case insensitive file names */
		c = *p++;
		if(c >= 'A' && c <= 'Z')
			c += 'a'-'A';
		h = h * 19 ^ c;
	}
	return h;
}

/* TO DO: substitute fixed, made-up (unlikely) names for these */
static char* devf[] = { "aux", "com1", "com2", "lpt1", "nul", "prn", nil };

static int
devfile(char *p)
{
	char *s, *t, *u, **ss;

	if((u = strrchr(p, '\\')) != nil)
		u++;
	else if((u = strrchr(p, '/')) != nil)
		u++;
	else
		u = p;
	for(ss = devf; *ss != nil; ss++){
		for(s = *ss, t = u; *s != '\0' && *t != '\0' && *t != '.'; s++, t++)
			if(*s != *t && *s != *t+'a'-'A')
				break;
		if(*s == '\0' && (*t == '\0' || *t == '.'))
			return 1;
	}
	return 0;
}

/*
 * there are other ways to figure out
 * the attributes and times for a file.
 * perhaps they are faster
 */
static int
fsexist(char *p, Qid *q)
{
	HANDLE h;
	WIN32_FIND_DATA data;
	wchar_t *wpath;

	if(devfile(p))
		return 0;
	wpath = widen(p);
	h = FindFirstFile(wpath, &data);
	free(wpath);
	if(h == INVALID_HANDLE_VALUE)
		return 0;
			if (!winfilematch(p, &data)) {
				FindClose(h);
				return 0;
			}
	FindClose(h);

	q->path = fsqidpath(p);
	q->type = 0;

	if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		q->type |= QTDIR;

	q->vers = unixtime(data.ftLastWriteTime);

	return 1;
}

static int
fsdirset(char *edir, int n, WIN32_FIND_DATA *data, char *path, Chan *c, int isdir)
{
	Dir dir;
	static char neveryone[] = "Everyone";

	dir.name = narrowen(data->cFileName);
	dir.muid = nil;
	dir.qid.path = fsqidpath(path);
	dir.qid.vers = 0;
	dir.qid.type = 0;
	dir.mode = 0;
	dir.atime = unixtime(data->ftLastAccessTime);
	dir.mtime = unixtime(data->ftLastWriteTime);
	dir.qid.vers = dir.mtime;
	dir.length = ((uvlong)data->nFileSizeHigh<<32) | ((uvlong)data->nFileSizeLow & ~((uvlong)0xFFFFFFFF<<32));
	dir.type = 'U';
	dir.dev = c->dev;

	if(!FS(c)->usesec){
		/* no NT security so make something up */
		dir.uid = neveryone;
		dir.gid = neveryone;
		dir.mode = 0777;
	}else if(!secstat(&dir, path, FS(c)->srv))
		oserror();

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

	if(isdir && sizeD2M(&dir) > n)
		n = -1;
	else 
		n = convD2M(&dir, edir, n);
	if(dir.uid != neveryone)
		free(dir.uid);
	if(dir.gid != neveryone)
		free(dir.gid);
	free(dir.name);
	return n;
}

static int
fsdirsize(WIN32_FIND_DATA *data, char *path, Chan *c)
{
	int i, n;

	n = widebytes(data->cFileName);
	if(!FS(c)->usesec)
		n += 8+8;
	else{
		i = secsize(path, FS(c)->srv);
		if(i < 0)
			oserror();
		n += i;
	}
	return STATFIXLEN+n;
}

static void
fssettime(char *path, long at, long mt)
{
	HANDLE h;
	FILETIME atime, mtime;
	wchar_t *wpath;

	wpath = widen(path);
	h = CreateFile(wpath, GENERIC_WRITE,
		0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	free(wpath);
	if(h == INVALID_HANDLE_VALUE)
		return;
	mtime = wintime(mt);
	atime = wintime(at);
	if(!SetFileTime(h, 0, &atime, &mtime)){
		CloseHandle(h);
		oserror();
	}
	CloseHandle(h);
}

static int
fsomode(int m)
{
	switch(m & 0x3) {
	case OREAD:
	case OEXEC:
		return GENERIC_READ;
	case OWRITE:
		return GENERIC_WRITE;
	case ORDWR:
		return GENERIC_READ|GENERIC_WRITE;
	}
	error(Ebadarg);
	return 0;
}

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

/*
 * the sec routines manage file permissions for nt.
 * nt files have an associated security descriptor,
 * which has in it an owner, a group,
 * and a discretionary acces control list, or acl,
 * which specifies the permissions for the file.
 *
 * the strategy for mapping between inferno owner,
 * group, other, and mode and nt file security is:
 *
 *	inferno owner == nt file owner
 *	inferno other == nt Everyone
 *	inferno group == first non-owner,
 *			non-Everyone user given in the acl,
 *			or the owner if there is no such user.
 * we examine the entire acl when check for permissions,
 * but only report a subset.
 *
 * when we write an acl, we also give all permissions to
 * the special user rootname, who is supposed to run emu in server mode.
 */
static void
secinit(void)
{
	HANDLE token;
	TOKEN_PRIVILEGES *priv;
	char privrock[sizeof(TOKEN_PRIVILEGES) + 1*sizeof(LUID_AND_ATTRIBUTES)];
	SID_IDENTIFIER_AUTHORITY id = SECURITY_CREATOR_SID_AUTHORITY;
	SID_IDENTIFIER_AUTHORITY wid = SECURITY_WORLD_SID_AUTHORITY;
	SID_IDENTIFIER_AUTHORITY ntid = SECURITY_NT_AUTHORITY;

	if(!AllocateAndInitializeSid(&id, 1,
		SECURITY_CREATOR_OWNER_RID,
		1, 2, 3, 4, 5, 6, 7, &creatorowner)
	|| !AllocateAndInitializeSid(&id, 1,
		SECURITY_CREATOR_GROUP_RID,
		1, 2, 3, 4, 5, 6, 7, &creatorgroup)
	|| !AllocateAndInitializeSid(&wid, 1,
		SECURITY_WORLD_RID,
		1, 2, 3, 4, 5, 6, 7, &everyone)
	|| !AllocateAndInitializeSid(&ntid, 1,
		0,
		1, 2, 3, 4, 5, 6, 7, &ntignore))
		panic("can't initialize well-known sids");

	fsnone = sidtouser(ntsrv, everyone);
	if(fsnone == nil)
		panic("can't make none user");

	/*
	 * see if we are running as the emu server user
	 * if so, set up SE_RESTORE_NAME privilege,
	 * which allows setting the owner field in a security descriptor.
	 * other interesting privileges are SE_TAKE_OWNERSHIP_NAME,
	 * which enables changing the ownership of a file to yourself
	 * regardless of the permissions on the file, SE_BACKUP_NAME,
	 * which enables reading any files regardless of permission,
	 * and SE_CHANGE_NOTIFY_NAME, which enables walking through
	 * directories without X permission.
	 * SE_RESTORE_NAME and SE_BACKUP_NAME together allow writing
	 * and reading any file data, regardless of permission,
	 * if the file is opened with FILE_BACKUP_SEMANTICS.
	 */
	isserver = 0;
	fsuser = secuser();
	if(fsuser == nil)
		fsuser = fsnone;
	else if(runes16cmp(fsuser->name, rootname) == 0
	     && OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)){
		priv = (TOKEN_PRIVILEGES*)privrock;
		priv->PrivilegeCount = 1;
		priv->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
		if(LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &priv->Privileges[0].Luid)
		&& AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL))
			isserver = 1;
		CloseHandle(token);
	}
}

/*
 * get the User for the executing process
 */
static User*
secuser(void)
{
	DWORD need;
	HANDLE token;
	TOKEN_USER *tu;
	char turock[sizeof(TOKEN_USER) + MAX_SID];

	if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
		return nil;

	tu = (TOKEN_USER*)turock;
	if(!GetTokenInformation(token, TokenUser, tu, sizeof(turock), &need)){
		CloseHandle(token);
		return nil;
	}
	CloseHandle(token);
	return sidtouser(nil, tu->User.Sid);
}

static int
secstat(Dir *dir, char *file, Rune16 *srv)
{
	int ok, n;
	Stat st;
	char sdrock[SD_ROCK];
	SECURITY_DESCRIPTOR *sd;

	sd = secsd(file, sdrock);
	if(sd == nil){
		int e = GetLastError();
		if(e == ERROR_ACCESS_DENIED || e == ERROR_SHARING_VIOLATION){
			dir->uid = strdup("unknown");
			dir->gid = strdup("unknown");
			if(dir->uid == nil || dir->gid == nil){
				free(dir->uid);
				error(Enomem);	/* will change to use kstrdup */
			}
			dir->mode = 0;
			return 1;
		}
		return 0;
	}
	ok = secsdstat(sd, &st, srv);
	if(sd != (void*)sdrock)
		free(sd);
	if(ok){
		dir->mode = st.mode;
		n = rune16nlen(st.owner->name, runes16len(st.owner->name));
		dir->uid = smalloc(n+1);
		runes16toutf(dir->uid, st.owner->name, n+1);
		n = rune16nlen(st.group->name, runes16len(st.group->name));
		dir->gid = smalloc(n+1);
		runes16toutf(dir->gid, st.group->name, n+1);
	}
	return ok;
}

static int
secsize(char *file, Rune16 *srv)
{
	int ok;
	Stat st;
	char sdrock[SD_ROCK];
	SECURITY_DESCRIPTOR *sd;

	sd = secsd(file, sdrock);
	if(sd == nil){
		int e = GetLastError();
		if(e == ERROR_ACCESS_DENIED || e == ERROR_SHARING_VIOLATION)
			return 7+7;
		return -1;
	}
	ok = secsdstat(sd, &st, srv);
	if(sd != (void*)sdrock)
		free(sd);
	if(ok)
		return rune16nlen(st.owner->name, runes16len(st.owner->name))+rune16nlen(st.group->name, runes16len(st.group->name));
	return -1;
}

/*
 * verify that u had access to file
 */
static void
seccheck(char *file, ulong access, Rune16 *srv)
{
	if(!sechasperm(file, access, srv))
		error(Eperm);
}

static int
sechasperm(char *file, ulong access, Rune16 *srv)
{
	int ok;
	char sdrock[SD_ROCK];
	SECURITY_DESCRIPTOR *sd;

	/*
	 * only really needs dacl info
	 */
	sd = secsd(file, sdrock);
	if(sd == nil)
		return 0;
	ok = secsdhasperm(sd, access, srv);
	if(sd != (void*)sdrock)
		free(sd);
	return ok;
}

static SECURITY_DESCRIPTOR*
secsd(char *file, char sdrock[SD_ROCK])
{
	DWORD need;
	SECURITY_DESCRIPTOR *sd;
	char *path, pathrock[6];
	wchar_t *wpath;

	path = file;
	if(path[0] != '\0' && path[1] == ':' && path[2] == '\0'){
		path = pathrock;
		strcpy(path, "?:\\.");
		path[0] = file[0];
	}
	sd = (SECURITY_DESCRIPTOR*)sdrock;
	need = 0;
	wpath = widen(path);
	if(GetFileSecurity(wpath, OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, sd, SD_ROCK, &need)) {
		free(wpath);
		return sd;
	}
	 if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
		free(wpath);
		return nil;
	}
	sd = malloc(need);
	if(sd == nil) {
		free(wpath);
		error(Enomem);
	}
	if(GetFileSecurity(wpath, OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, sd, need, &need)) {
		free(wpath);
		return sd;
	}
	free(wpath);
	free(sd);
	return nil;
}

static int
secsdstat(SECURITY_DESCRIPTOR *sd, Stat *st, Rune16 *srv)
{
	ACL *acl;
	BOOL hasacl, b;
	ACE_HEADER *aceh;
	User *owner, *group;
	SID *sid, *osid, *gsid;
	ACCESS_ALLOWED_ACE *ace;
	int i, allow, deny, *p, m;
	ACL_SIZE_INFORMATION size;

	st->mode = 0;

	osid = nil;
	gsid = nil;
	if(!GetSecurityDescriptorOwner(sd, &osid, &b)
	|| !GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b))
		return 0;

	if(acl == 0)
		size.AceCount = 0;
	else if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation))
		return 0;

	/*
	 * first pass through acl finds group
	 */
	for(i = 0; i < size.AceCount; i++){
		if(!GetAce(acl, i, &aceh))
			continue;
		if(aceh->AceFlags & INHERIT_ONLY_ACE)
			continue;

		if(aceh->AceType != ACCESS_ALLOWED_ACE_TYPE
		&& aceh->AceType != ACCESS_DENIED_ACE_TYPE)
			continue;

		ace = (ACCESS_ALLOWED_ACE*)aceh;
		sid = (SID*)&ace->SidStart;
		if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
			continue;

		if(EqualSid(sid, everyone))
			;
		else if(EqualSid(sid, osid))
			;
		else if(EqualPrefixSid(sid, ntignore))
			continue;		/* boring nt accounts */
		else{
			gsid = sid;
			break;
		}
	}
	if(gsid == nil)
		gsid = osid;

	owner = sidtouser(srv, osid);
	if(owner == nil)
		return 0;
	group = sidtouser(srv, gsid);
	if(group == nil)
		return 0;

	/* no acl means full access */
	allow = 0;
	if(acl == 0)
		allow = 0777;
	deny = 0;
	for(i = 0; i < size.AceCount; i++){
		if(!GetAce(acl, i, &aceh))
			continue;
		if(aceh->AceFlags & INHERIT_ONLY_ACE)
			continue;

		if(aceh->AceType == ACCESS_ALLOWED_ACE_TYPE)
			p = &allow;
		else if(aceh->AceType == ACCESS_DENIED_ACE_TYPE)
			p = &deny;
		else
			continue;

		ace = (ACCESS_ALLOWED_ACE*)aceh;
		sid = (SID*)&ace->SidStart;
		if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
			continue;

		m = 0;
		if(ace->Mask & FILE_EXECUTE)
			m |= 1;
		if(ace->Mask & FILE_WRITE_DATA)
			m |= 2;
		if(ace->Mask & FILE_READ_DATA)
			m |= 4;

		if(ismembersid(srv, owner, sid))
			*p |= (m << 6) & ~(allow|deny) & 0700;
		if(ismembersid(srv, group, sid))
			*p |= (m << 3) & ~(allow|deny) & 0070;
		if(EqualSid(everyone, sid))
			*p |= m & ~(allow|deny) & 0007;
	}

	st->mode = allow & ~deny;
	st->owner = owner;
	st->group = group;
	return 1;
}

static int
secsdhasperm(SECURITY_DESCRIPTOR *sd, ulong access, Rune16 *srv)
{
	User *u;
	ACL *acl;
	BOOL hasacl, b;
	ACE_HEADER *aceh;
	SID *sid, *osid, *gsid;
	int i, allow, deny, *p, m;
	ACCESS_ALLOWED_ACE *ace;
	ACL_SIZE_INFORMATION size;

	u = up->env->ui;
	allow = 0;
	deny = 0;
	osid = nil;
	gsid = nil;
	if(!GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b))
		return 0;

	/* no acl means full access */
	if(acl == 0)
		return 1;
	if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation))
		return 0;
	for(i = 0; i < size.AceCount; i++){
		if(!GetAce(acl, i, &aceh))
			continue;
		if(aceh->AceFlags & INHERIT_ONLY_ACE)
			continue;

		if(aceh->AceType == ACCESS_ALLOWED_ACE_TYPE)
			p = &allow;
		else if(aceh->AceType == ACCESS_DENIED_ACE_TYPE)
			p = &deny;
		else
			continue;

		ace = (ACCESS_ALLOWED_ACE*)aceh;
		sid = (SID*)&ace->SidStart;
		if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
			continue;

		m = ace->Mask;

		if(ismembersid(srv, u, sid))
			*p |= m & ~(allow|deny);
	}

	allow &= ~deny;

	return (allow & access) == access;
}

static SECURITY_DESCRIPTOR*
secmksd(char *sdrock, Stat *st, ACL *dacl, int isdir)
{
	int m;

	ulong mode;
	ACE_HEADER *aceh;
	SECURITY_DESCRIPTOR *sd;

	sd = (SECURITY_DESCRIPTOR*)sdrock;
	if(!InitializeAcl(dacl, ACL_ROCK, ACL_REVISION))
		return nil;

	mode = st->mode;
	if(st->owner == st->group){
		mode |= (mode >> 3) & 0070;
		mode |= (mode << 3) & 0700;
	}


	m = modetomask[(mode>>6) & 7];
	if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, st->owner->sid))
		return nil;

	if(isdir && !AddAccessAllowedAce(dacl, ACL_REVISION, m, creatorowner))
		return nil;

	m = modetomask[(mode>>3) & 7];
	if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, st->group->sid))
		return nil;

	m = modetomask[(mode>>0) & 7];
	if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, everyone))
		return nil;

	if(isdir){
		/* hack to add inherit flags */
		if(!GetAce(dacl, 1, &aceh))
			return nil;
		aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
		if(!GetAce(dacl, 2, &aceh))
			return nil;
		aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
		if(!GetAce(dacl, 3, &aceh))
			return nil;
		aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
	}

	/*
	 * allow server user to access any file
	 */
	if(isserver){
		if(!AddAccessAllowedAce(dacl, ACL_REVISION, RMODE|WMODE|XMODE, fsuser->sid))
			return nil;
		if(isdir){
			if(!GetAce(dacl, 4, &aceh))
				return nil;
			aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
		}
	}

	if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION))
		return nil;
	if(!SetSecurityDescriptorDacl(sd, 1, dacl, 0))
		return nil;
//	if(isserver && !SetSecurityDescriptorOwner(sd, st->owner->sid, 0))
//		return nil;
	return sd;
}

/*
 * the user manipulation routines
 * just make it easier to deal with user identities
 */
static User*
sidtouser(Rune16 *srv, SID *s)
{
	SID_NAME_USE type;
	Rune16 aname[100], dname[100];
	DWORD naname, ndname;
	User *u;

	qlock(&users.lk);
	for(u = users.u; u != 0; u = u->next)
		if(EqualSid(s, u->sid))
			break;
	qunlock(&users.lk);

	if(u != 0)
		return u;

	naname = sizeof(aname);
	ndname = sizeof(dname);

	if(!LookupAccountSidW(srv, s, aname, &naname, dname, &ndname, &type))
		return mkuser(s, SidTypeUnknown, L"unknown", L"unknown");
	return mkuser(s, type, aname, dname);
}

static User*
domnametouser(Rune16 *srv, Rune16 *name, Rune16 *dom)
{
	User *u;

	qlock(&users.lk);
	for(u = users.u; u != 0; u = u->next)
		if(runes16cmp(name, u->name) == 0 && runes16cmp(dom, u->dom) == 0)
			break;
	qunlock(&users.lk);
	if(u == 0)
		u = nametouser(srv, name);
	return u;
}

static User*
nametouser(Rune16 *srv, Rune16 *name)
{
	char sidrock[MAX_SID];
	SID *sid;
	SID_NAME_USE type;
	Rune16 dom[MAX_PATH];
	DWORD nsid, ndom;

	sid = (SID*)sidrock;
	nsid = sizeof(sidrock);
	ndom = sizeof(dom);
	if(!LookupAccountNameW(srv, name, sid, &nsid, dom, &ndom, &type))
		return nil;

	return mkuser(sid, type, name, dom);
}

/*
 * this mapping could be cached
 */
static User*
unametouser(Rune16 *srv, char *name)
{
	Rune16 rname[MAX_PATH];

	utftorunes16(rname, name, MAX_PATH);
	return nametouser(srv, rname);
}

/*
 * make a user structure and add it to the global cache.
 */
static User*
mkuser(SID *sid, int type, Rune16 *name, Rune16 *dom)
{
	User *u;

	qlock(&users.lk);
	for(u = users.u; u != 0; u = u->next){
		if(EqualSid(sid, u->sid)){
			qunlock(&users.lk);
			return u;
		}
	}

	switch(type) {
	default:
		break;
	case SidTypeDeletedAccount:
		name = L"deleted";
		break;
	case SidTypeInvalid:
		name = L"invalid";
		break;
	case SidTypeUnknown:
		name = L"unknown";
		break;
	}

	u = malloc(sizeof(User));
	if(u == nil){
		qunlock(&users.lk);
		return 0;
	}
	u->next = nil;
	u->group = nil;
	u->sid = dupsid(sid);
	u->type = type;
	u->name = nil;
	if(name != nil)
		u->name = runes16dup(name);
	u->dom = nil;
	if(dom != nil)
		u->dom = runes16dup(dom);

	u->next = users.u;
	users.u = u;

	qunlock(&users.lk);
	return u;
}

/*
 * check if u is a member of gsid,
 * which might be a group.
 */
static int
ismembersid(Rune16 *srv, User *u, SID *gsid)
{
	User *g;

	if(EqualSid(u->sid, gsid))
		return 1;

	g = sidtouser(srv, gsid);
	if(g == 0)
		return 0;
	return ismember(u, g);
}

static int
ismember(User *u, User *g)
{
	Gmem *grps;

	if(EqualSid(u->sid, g->sid))
		return 1;

	if(EqualSid(g->sid, everyone))
		return 1;

	qlock(&u->lk);
	addgroups(u, 0);
	for(grps = u->group; grps != 0; grps = grps->next){
		if(EqualSid(grps->user->sid, g->sid)){
			qunlock(&u->lk);
			return 1;
		}
	}
	qunlock(&u->lk);
	return 0;
}

/*
 * find out what groups a user belongs to.
 * if force, throw out the old info and do it again.
 *
 * note that a global group is also know as a group,
 * and a local group is also know as an alias.
 * global groups can only contain users.
 * local groups can contain global groups or users.
 * this code finds all global groups to which a user belongs,
 * and all the local groups to which the user or a global group
 * containing the user belongs.
 */
static void
addgroups(User *u, int force)
{
	LOCALGROUP_USERS_INFO_0 *loc;
	GROUP_USERS_INFO_0 *grp;
	DWORD i, n, rem;
	User *gu;
	Gmem *g, *next;
	Rune16 *srv, srvrock[MAX_PATH];

	if(force){
		u->gotgroup = 0;
		for(g = u->group; g != nil; g = next){
			next = g->next;
			free(g);
		}
		u->group = nil;
	}
	if(u->gotgroup)
		return;
	u->gotgroup = 1;

	n = 0;
	srv = domsrv(u->dom, srvrock);
	i = NetUserGetGroups(srv, u->name, 0,
		(BYTE**)&grp, MAX_PREFERRED_LENGTH, &n, &rem);
	if(i == NERR_Success || i == ERROR_MORE_DATA){
		for(i = 0; i < n; i++){
			gu = domnametouser(srv, grp[i].grui0_name, u->dom);
			if(gu == 0)
				continue;
			g = malloc(sizeof(Gmem));
			if(g == nil)
				error(Enomem);
			g->user = gu;
			g->next = u->group;
			u->group = g;
		}
		NetApiBufferFree(grp);
	}

	n = 0;
	i = NetUserGetLocalGroups(srv, u->name, 0, LG_INCLUDE_INDIRECT,
		(BYTE**)&loc, MAX_PREFERRED_LENGTH, &n, &rem);
	if(i == NERR_Success || i == ERROR_MORE_DATA){
		for(i = 0; i < n; i++){
			gu = domnametouser(srv, loc[i].lgrui0_name, u->dom);
			if(gu == NULL)
				continue;
			g = malloc(sizeof(Gmem));
			if(g == nil)
				error(Enomem);
			g->user = gu;
			g->next = u->group;
			u->group = g;
		}
		NetApiBufferFree(loc);
	}
}

static SID*
dupsid(SID *sid)
{
	SID *nsid;
	int n;

	n = GetLengthSid(sid);
	nsid = malloc(n);
	if(nsid == nil || !CopySid(n, nsid, sid))
		panic("can't copy sid");
	return nsid;
}

/*
 * return the name of the server machine for file
 */
static Rune16*
filesrv(char *file)
{
	int n;
	Rune16 *srv;
	char *p, uni[MAX_PATH], mfile[MAX_PATH];
	wchar_t vol[3];

	strcpy(mfile, file);
	/* assume file is a fully qualified name - X: or \\server */
	if(file[1] == ':') {
		vol[0] = file[0];
		vol[1] = file[1];
		vol[2] = 0;
		if(GetDriveType(vol) != DRIVE_REMOTE)
			return 0;
		n = sizeof(uni);
		if(WNetGetUniversalName(vol, UNIVERSAL_NAME_INFO_LEVEL, uni, &n) != NO_ERROR)
			return nil;
		runes16toutf(mfile, ((UNIVERSAL_NAME_INFO*)uni)->lpUniversalName, MAX_PATH);
		file = mfile;
	}
	file += 2;
	p = strchr(file, '\\');
	if(p == 0)
		n = strlen(file);
	else
		n = p - file;
	if(n >= MAX_PATH)
		n = MAX_PATH-1;

	memmove(uni, file, n);
	uni[n] = '\0';

	srv = malloc((n + 1) * sizeof(Rune16));
	if(srv == nil)
		panic("filesrv: no memory");
	utftorunes16(srv, uni, n+1);
	return srv;
}

/*
 * does the file system support acls?
 */
static int
fsacls(char *file)
{
	char *p;
	DWORD flags;
	char path[MAX_PATH];
	wchar_t wpath[MAX_PATH];

	/* assume file is a fully qualified name - X: or \\server */
	if(file[1] == ':') {
		path[0] = file[0];
		path[1] = file[1];
		path[2] = '\\';
		path[3] = 0;
	} else {
		strcpy(path, file);
		p = strchr(path+2, '\\');
		if(p == 0)
			return 0;
		p = strchr(p+1, '\\');
		if(p == 0)
			strcat(path, "\\");
		else
			p[1] = 0;
	}
	utftorunes16(wpath, path, MAX_PATH);
	if(!GetVolumeInformation(wpath, NULL, 0, NULL, NULL, &flags, NULL, 0))
		return 0;

	return flags & FS_PERSISTENT_ACLS;
}

/*
 * given a domain, find out the server to ask about its users.
 * we just ask the local machine to do the translation,
 * so it might fail sometimes.  in those cases, we don't
 * trust the domain anyway, and vice versa, so it's not
 * clear what benifit we would gain by getting the answer "right".
 */
static Rune16*
domsrv(Rune16 *dom, Rune16 srv[MAX_PATH])
{
	Rune16 *psrv;
	int n, r;

	if(dom[0] == 0)
		return nil;

	r = NetGetAnyDCName(NULL, dom, (LPBYTE*)&psrv);
	if(r == NERR_Success) {
		n = runes16len(psrv);
		if(n >= MAX_PATH)
			n = MAX_PATH-1;
		memmove(srv, psrv, n*sizeof(Rune16));
		srv[n] = 0;
		NetApiBufferFree(psrv);
		return srv;
	}

	return nil;
}

Dev fsdevtab = {
	'U',
	"fs",

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