703 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			703 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include	<windows.h>
 | |
| #include	<sys/types.h>
 | |
| #include	<sys/stat.h>
 | |
| #include	<fcntl.h>
 | |
| 
 | |
| #ifndef NAME_MAX
 | |
| #	define NAME_MAX 256
 | |
| #endif
 | |
| #include	"u.h"
 | |
| #include	"lib.h"
 | |
| #include	"dat.h"
 | |
| #include	"fns.h"
 | |
| #include	"error.h"
 | |
| 
 | |
| typedef struct DIR	DIR;
 | |
| typedef	struct Ufsinfo	Ufsinfo;
 | |
| 
 | |
| enum
 | |
| {
 | |
| 	NUID	= 256,
 | |
| 	NGID	= 256,
 | |
| 	MAXPATH	= 1024,
 | |
| 	MAXCOMP	= 128
 | |
| };
 | |
| 
 | |
| struct DIR
 | |
| {
 | |
| 	HANDLE	handle;
 | |
| 	char*	path;
 | |
| 	int	index;
 | |
| 	WIN32_FIND_DATA	wfd;
 | |
| };
 | |
| 
 | |
| struct Ufsinfo
 | |
| {
 | |
| 	int	mode;
 | |
| 	int	fd;
 | |
| 	int	uid;
 | |
| 	int	gid;
 | |
| 	DIR*	dir;
 | |
| 	ulong	offset;
 | |
| 	QLock	oq;
 | |
| 	char nextname[NAME_MAX];
 | |
| };
 | |
| 
 | |
| DIR*	opendir(char*);
 | |
| int	readdir(char*, DIR*);
 | |
| void	closedir(DIR*);
 | |
| void	rewinddir(DIR*);
 | |
| 
 | |
| char	*base = "c:/.";
 | |
| 
 | |
| static	Qid	fsqid(char*, struct stat *);
 | |
| static	void	fspath(Chan*, char*, char*);
 | |
| // static	void	fsperm(Chan*, int);
 | |
| static	ulong	fsdirread(Chan*, uchar*, int, ulong);
 | |
| static	int	fsomode(int);
 | |
| static  int	chown(char *path, int uid, int);
 | |
| 
 | |
| /* clumsy hack, but not worse than the Path stuff in the last one */
 | |
| static char*
 | |
| uc2name(Chan *c)
 | |
| {
 | |
| 	char *s;
 | |
| 
 | |
| 	if(c->name == nil)
 | |
| 		return "/";
 | |
| 	s = c2name(c);
 | |
| 	if(s[0]=='#' && s[1]=='U')
 | |
| 		return s+2;
 | |
| 	return s;
 | |
| }
 | |
| 
 | |
| static char*
 | |
| lastelem(Chan *c)
 | |
| {
 | |
| 	char *s, *t;
 | |
| 
 | |
| 	s = uc2name(c);
 | |
| 	if((t = strrchr(s, '/')) == nil)
 | |
| 		return s;
 | |
| 	if(t[1] == 0)
 | |
| 		return t;
 | |
| 	return t+1;
 | |
| }
 | |
| 	
 | |
| static Chan*
 | |
| fsattach(char *spec)
 | |
| {
 | |
| 	Chan *c;
 | |
| 	struct stat stbuf;
 | |
| 	static int devno;
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	if(stat(base, &stbuf) < 0)
 | |
| 		error(strerror(errno));
 | |
| 
 | |
| 	c = devattach('U', spec);
 | |
| 
 | |
| 	uif = mallocz(sizeof(Ufsinfo), 1);
 | |
| 	uif->gid = stbuf.st_gid;
 | |
| 	uif->uid = stbuf.st_uid;
 | |
| 	uif->mode = stbuf.st_mode;
 | |
| 
 | |
| 	c->aux = uif;
 | |
| 	c->dev = devno++;
 | |
| 	c->qid.type = QTDIR;
 | |
| /*print("fsattach %s\n", c2name(c));*/
 | |
| 
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| static Chan*
 | |
| fsclone(Chan *c, Chan *nc)
 | |
| {
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	uif = mallocz(sizeof(Ufsinfo), 1);
 | |
| 	*uif = *(Ufsinfo*)c->aux;
 | |
| 	nc->aux = uif;
 | |
| 
 | |
| 	return nc;
 | |
| }
 | |
| 
 | |
| static int
 | |
| fswalk1(Chan *c, char *name)
 | |
| {
 | |
| 	struct stat stbuf;
 | |
| 	char path[MAXPATH];
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	fspath(c, name, path);
 | |
| 
 | |
| 	/*	print("** fs walk '%s' -> %s\n", path, name); */
 | |
| 
 | |
| 	if(stat(path, &stbuf) < 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 
 | |
| 	uif->gid = stbuf.st_gid;
 | |
| 	uif->uid = stbuf.st_uid;
 | |
| 	uif->mode = stbuf.st_mode;
 | |
| 
 | |
| 	c->qid = fsqid(path, &stbuf);
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| extern Cname* addelem(Cname*, char*);
 | |
| 
 | |
| static Walkqid*
 | |
| fswalk(Chan *c, Chan *nc, char **name, int nname)
 | |
| {
 | |
| 	int i;
 | |
| 	Cname *cname;
 | |
| 	Walkqid *wq;
 | |
| 
 | |
| 	if(nc != nil)
 | |
| 		panic("fswalk: nc != nil");
 | |
| 	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
 | |
| 	nc = devclone(c);
 | |
| 	cname = c->name;
 | |
| 	incref(&cname->ref);
 | |
| 
 | |
| 	fsclone(c, nc);
 | |
| 	wq->clone = nc;
 | |
| 	for(i=0; i<nname; i++){
 | |
| 		nc->name = cname;
 | |
| 		if(fswalk1(nc, name[i]) == 0)
 | |
| 			break;
 | |
| 		cname = addelem(cname, name[i]);
 | |
| 		wq->qid[i] = nc->qid;
 | |
| 	}
 | |
| 	nc->name = cname;
 | |
| 	if(i != nname){
 | |
| 		cclose(nc);
 | |
| 		wq->clone = nil;
 | |
| 	}
 | |
| 	wq->nqid = i;
 | |
| 	return wq;
 | |
| }
 | |
| 	
 | |
| static int
 | |
| fsstat(Chan *c, uchar *buf, int n)
 | |
| {
 | |
| 	Dir d;
 | |
| 	struct stat stbuf;
 | |
| 	char path[MAXPATH];
 | |
| 
 | |
| 	if(n < BIT16SZ)
 | |
| 		error(Eshortstat);
 | |
| 
 | |
| 	fspath(c, 0, path);
 | |
| 	if(stat(path, &stbuf) < 0)
 | |
| 		error(strerror(errno));
 | |
| 
 | |
| 	d.name = lastelem(c);
 | |
| 	d.uid = "unknown";
 | |
| 	d.gid = "unknown";
 | |
| 	d.muid = "unknown";
 | |
| 	d.qid = c->qid;
 | |
| 	d.mode = (c->qid.type<<24)|(stbuf.st_mode&0777);
 | |
| 	d.atime = stbuf.st_atime;
 | |
| 	d.mtime = stbuf.st_mtime;
 | |
| 	d.length = stbuf.st_size;
 | |
| 	d.type = 'U';
 | |
| 	d.dev = c->dev;
 | |
| 	return convD2M(&d, buf, n);
 | |
| }
 | |
| 
 | |
| static Chan*
 | |
| fsopen(Chan *c, int mode)
 | |
| {
 | |
| 	char path[MAXPATH];
 | |
| 	int m, isdir;
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| /*print("fsopen %s\n", c2name(c));*/
 | |
| 	m = mode & (OTRUNC|3);
 | |
| 	switch(m) {
 | |
| 	case 0:
 | |
| 		break;
 | |
| 	case 1:
 | |
| 	case 1|16:
 | |
| 		break;
 | |
| 	case 2:	
 | |
| 	case 0|16:
 | |
| 	case 2|16:
 | |
| 		break;
 | |
| 	case 3:
 | |
| 		break;
 | |
| 	default:
 | |
| 		error(Ebadarg);
 | |
| 	}
 | |
| 
 | |
| 	isdir = c->qid.type & QTDIR;
 | |
| 
 | |
| 	if(isdir && mode != OREAD)
 | |
| 		error(Eperm);
 | |
| 
 | |
| 	m = fsomode(m & 3);
 | |
| 	c->mode = openmode(mode);
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 
 | |
| 	fspath(c, 0, path);
 | |
| 	if(isdir) {
 | |
| 		uif->dir = opendir(path);
 | |
| 		if(uif->dir == 0)
 | |
| 			error(strerror(errno));
 | |
| 	}	
 | |
| 	else {
 | |
| 		if(mode & OTRUNC)
 | |
| 			m |= O_TRUNC;
 | |
| 		uif->fd = open(path, m|_O_BINARY, 0666);
 | |
| 
 | |
| 		if(uif->fd < 0)
 | |
| 			error(strerror(errno));
 | |
| 	}
 | |
| 	uif->offset = 0;
 | |
| 
 | |
| 	c->offset = 0;
 | |
| 	c->flag |= COPEN;
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| static void
 | |
| fscreate(Chan *c, char *name, int mode, ulong perm)
 | |
| {
 | |
| 	int fd, m;
 | |
| 	char path[MAXPATH];
 | |
| 	struct stat stbuf;
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	m = fsomode(mode&3);
 | |
| 
 | |
| 	fspath(c, name, path);
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 
 | |
| 	if(perm & DMDIR) {
 | |
| 		if(m)
 | |
| 			error(Eperm);
 | |
| 
 | |
| 		if(mkdir(path) < 0)
 | |
| 			error(strerror(errno));
 | |
| 
 | |
| 		fd = open(path, 0);
 | |
| 		if(fd >= 0) {
 | |
| 			chmod(path, perm & 0777);
 | |
| 			chown(path, uif->uid, uif->uid);
 | |
| 		}
 | |
| 		close(fd);
 | |
| 
 | |
| 		uif->dir = opendir(path);
 | |
| 		if(uif->dir == 0)
 | |
| 			error(strerror(errno));
 | |
| 	}
 | |
| 	else {
 | |
| 		fd = open(path, _O_WRONLY|_O_BINARY|_O_CREAT|_O_TRUNC, 0666);
 | |
| 		if(fd >= 0) {
 | |
| 			if(m != 1) {
 | |
| 				close(fd);
 | |
| 				fd = open(path, m|_O_BINARY);
 | |
| 			}
 | |
| 			chmod(path, perm & 0777);
 | |
| 			chown(path, uif->uid, uif->gid);
 | |
| 		}
 | |
| 		if(fd < 0)
 | |
| 			error(strerror(errno));
 | |
| 		uif->fd = fd;
 | |
| 	}
 | |
| 
 | |
| 	if(stat(path, &stbuf) < 0)
 | |
| 		error(strerror(errno));
 | |
| 	c->qid = fsqid(path, &stbuf);
 | |
| 	c->offset = 0;
 | |
| 	c->flag |= COPEN;
 | |
| 	c->mode = openmode(mode);
 | |
| }
 | |
| 
 | |
| static void
 | |
| fsclose(Chan *c)
 | |
| {
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 
 | |
| 	if(c->flag & COPEN) {
 | |
| 		if(c->qid.type & QTDIR)
 | |
| 			closedir(uif->dir);
 | |
| 		else
 | |
| 			close(uif->fd);
 | |
| 	}
 | |
| 
 | |
| 	free(uif);
 | |
| }
 | |
| 
 | |
| static long
 | |
| fsread(Chan *c, void *va, long n, vlong offset)
 | |
| {
 | |
| 	int fd, r;
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| /*print("fsread %s\n", c2name(c));*/
 | |
| 	if(c->qid.type & QTDIR)
 | |
| 		return fsdirread(c, va, n, offset);
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 	qlock(&uif->oq);
 | |
| 	if(waserror()) {
 | |
| 		qunlock(&uif->oq);
 | |
| 		nexterror();
 | |
| 	}
 | |
| 	fd = uif->fd;
 | |
| 	if(uif->offset != offset) {
 | |
| 		r = lseek(fd, offset, 0);
 | |
| 		if(r < 0)
 | |
| 			error(strerror(errno));
 | |
| 		uif->offset = offset;
 | |
| 	}
 | |
| 
 | |
| 	n = read(fd, va, n);
 | |
| 	if(n < 0)
 | |
| 		error(strerror(errno));
 | |
| 
 | |
| 	uif->offset += n;
 | |
| 	qunlock(&uif->oq);
 | |
| 	poperror();
 | |
| 
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static long
 | |
| fswrite(Chan *c, void *va, long n, vlong offset)
 | |
| {
 | |
| 	int fd, r;
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 
 | |
| 	qlock(&uif->oq);
 | |
| 	if(waserror()) {
 | |
| 		qunlock(&uif->oq);
 | |
| 		nexterror();
 | |
| 	}
 | |
| 	fd = uif->fd;
 | |
| 	if(uif->offset != offset) {
 | |
| 		r = lseek(fd, offset, 0);
 | |
| 		if(r < 0)
 | |
| 			error(strerror(errno));
 | |
| 		uif->offset = offset;
 | |
| 	}
 | |
| 
 | |
| 	n = write(fd, va, n);
 | |
| 	if(n < 0)
 | |
| 		error(strerror(errno));
 | |
| 
 | |
| 	uif->offset += n;
 | |
| 	qunlock(&uif->oq);
 | |
| 	poperror();
 | |
| 
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static void
 | |
| fsremove(Chan *c)
 | |
| {
 | |
| 	int n;
 | |
| 	char path[MAXPATH];
 | |
| 
 | |
| 	fspath(c, 0, path);
 | |
| 	if(c->qid.type & QTDIR)
 | |
| 		n = rmdir(path);
 | |
| 	else
 | |
| 		n = remove(path);
 | |
| 	if(n < 0)
 | |
| 		error(strerror(errno));
 | |
| }
 | |
| 
 | |
| static int
 | |
| fswstat(Chan *c, uchar *buf, int n)
 | |
| {
 | |
| 	Dir d;
 | |
| 	struct stat stbuf;
 | |
| 	char old[MAXPATH], new[MAXPATH];
 | |
| 	char strs[MAXPATH*3], *p;
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| 	if (convM2D(buf, n, &d, strs) != n)
 | |
| 		error(Ebadstat);
 | |
| 	
 | |
| 	fspath(c, 0, old);
 | |
| 	if(stat(old, &stbuf) < 0)
 | |
| 		error(strerror(errno));
 | |
| 
 | |
| 	uif = c->aux;
 | |
| 
 | |
| //	if(uif->uid != stbuf.st_uid)
 | |
| //		error(Eowner);
 | |
| 
 | |
| 	if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) {
 | |
| 		fspath(c, 0, old);
 | |
| 		strcpy(new, old);
 | |
| 		p = strrchr(new, '/');
 | |
| 		strcpy(p+1, d.name);
 | |
| 		if(rename(old, new) < 0)
 | |
| 			error(strerror(errno));
 | |
| 	}
 | |
| 
 | |
| 	fspath(c, 0, old);
 | |
| 	if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
 | |
| 		if(chmod(old, d.mode&0777) < 0)
 | |
| 			error(strerror(errno));
 | |
| 		uif->mode &= ~0777;
 | |
| 		uif->mode |= d.mode&0777;
 | |
| 	}
 | |
| /*
 | |
| 	p = name2pass(gid, d.gid);
 | |
| 	if(p == 0)
 | |
| 		error(Eunknown);
 | |
| 
 | |
| 	if(p->id != stbuf.st_gid) {
 | |
| 		if(chown(old, stbuf.st_uid, p->id) < 0)
 | |
| 			error(sys_errlist[errno]);
 | |
| 
 | |
| 		uif->gid = p->id;
 | |
| 	}
 | |
| */
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static Qid
 | |
| fsqid(char *p, struct stat *st)
 | |
| {
 | |
| 	Qid q;
 | |
| 	int dev;
 | |
| 	ulong h;
 | |
| 	static int nqdev;
 | |
| 	static uchar *qdev;
 | |
| 
 | |
| 	if(qdev == 0)
 | |
| 		qdev = mallocz(65536U, 1);
 | |
| 
 | |
| 	q.type = 0;
 | |
| 	if((st->st_mode&S_IFMT) ==  S_IFDIR)
 | |
| 		q.type = QTDIR;
 | |
| 
 | |
| 	dev = st->st_dev & 0xFFFFUL;
 | |
| 	if(qdev[dev] == 0)
 | |
| 		qdev[dev] = ++nqdev;
 | |
| 
 | |
| 	h = 0;
 | |
| 	while(*p != '\0')
 | |
| 		h += *p++ * 13;
 | |
| 	
 | |
| 	q.path = (vlong)qdev[dev]<<32;
 | |
| 	q.path |= h;
 | |
| 	q.vers = st->st_mtime;
 | |
| 
 | |
| 	return q;
 | |
| }
 | |
| 
 | |
| static void
 | |
| fspath(Chan *c, char *ext, char *path)
 | |
| {
 | |
| 	strcpy(path, base);
 | |
| 	strcat(path, "/");
 | |
| 	strcat(path, uc2name(c));
 | |
| 	if(ext) {
 | |
| 		strcat(path, "/");
 | |
| 		strcat(path, ext);
 | |
| 	}
 | |
| 	cleanname(path);
 | |
| }
 | |
| 
 | |
| static int
 | |
| isdots(char *name)
 | |
| {
 | |
| 	if(name[0] != '.')
 | |
| 		return 0;
 | |
| 	if(name[1] == '\0')
 | |
| 		return 1;
 | |
| 	if(name[1] != '.')
 | |
| 		return 0;
 | |
| 	if(name[2] == '\0')
 | |
| 		return 1;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| p9readdir(char *name, Ufsinfo *uif)
 | |
| {
 | |
| 	if(uif->nextname[0]){
 | |
| 		strcpy(name, uif->nextname);
 | |
| 		uif->nextname[0] = 0;
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	return readdir(name, uif->dir);
 | |
| }
 | |
| 
 | |
| static ulong
 | |
| fsdirread(Chan *c, uchar *va, int count, ulong offset)
 | |
| {
 | |
| 	int i;
 | |
| 	Dir d;
 | |
| 	long n;
 | |
| 	char de[NAME_MAX];
 | |
| 	struct stat stbuf;
 | |
| 	char path[MAXPATH], dirpath[MAXPATH];
 | |
| 	Ufsinfo *uif;
 | |
| 
 | |
| /*print("fsdirread %s\n", c2name(c));*/
 | |
| 	i = 0;
 | |
| 	uif = c->aux;
 | |
| 
 | |
| 	errno = 0;
 | |
| 	if(uif->offset != offset) {
 | |
| 		if(offset != 0)
 | |
| 			error("bad offset in fsdirread");
 | |
| 		uif->offset = offset;  /* sync offset */
 | |
| 		uif->nextname[0] = 0;
 | |
| 		rewinddir(uif->dir);
 | |
| 	}
 | |
| 
 | |
| 	fspath(c, 0, dirpath);
 | |
| 
 | |
| 	while(i+BIT16SZ < count) {
 | |
| 		if(!p9readdir(de, uif))
 | |
| 			break;
 | |
| 
 | |
| 		if(de[0]==0 || isdots(de))
 | |
| 			continue;
 | |
| 
 | |
| 		d.name = de;
 | |
| 		sprint(path, "%s/%s", dirpath, de);
 | |
| 		memset(&stbuf, 0, sizeof stbuf);
 | |
| 
 | |
| 		if(stat(path, &stbuf) < 0) {
 | |
| 			print("dir: bad path %s\n", path);
 | |
| 			/* but continue... probably a bad symlink */
 | |
| 		}
 | |
| 
 | |
| 		d.uid = "unknown";
 | |
| 		d.gid = "unknown";
 | |
| 		d.muid = "unknown";
 | |
| 		d.qid = fsqid(path, &stbuf);
 | |
| 		d.mode = (d.qid.type<<24)|(stbuf.st_mode&0777);
 | |
| 		d.atime = stbuf.st_atime;
 | |
| 		d.mtime = stbuf.st_mtime;
 | |
| 		d.length = stbuf.st_size;
 | |
| 		d.type = 'U';
 | |
| 		d.dev = c->dev;
 | |
| 		n = convD2M(&d, (char*)va+i, count-i);
 | |
| 		if(n == BIT16SZ){
 | |
| 			strcpy(uif->nextname, de);
 | |
| 			break;
 | |
| 		}
 | |
| 		i += n;
 | |
| 	}
 | |
| /*print("got %d\n", i);*/
 | |
| 	uif->offset += i;
 | |
| 	return i;
 | |
| }
 | |
| 
 | |
| static int
 | |
| fsomode(int m)
 | |
| {
 | |
| 	switch(m) {
 | |
| 	case 0:			/* OREAD */
 | |
| 	case 3:			/* OEXEC */
 | |
| 		return 0;
 | |
| 	case 1:			/* OWRITE */
 | |
| 		return 1;
 | |
| 	case 2:			/* ORDWR */
 | |
| 		return 2;
 | |
| 	}
 | |
| 	error(Ebadarg);
 | |
| 	return 0;
 | |
| }
 | |
| void
 | |
| closedir(DIR *d)
 | |
| {
 | |
| 	FindClose(d->handle);
 | |
| 	free(d->path);
 | |
| }
 | |
| 
 | |
| int
 | |
| readdir(char *name, DIR *d)
 | |
| {
 | |
| 	if(d->index != 0) {
 | |
| 		if(FindNextFile(d->handle, &d->wfd) == FALSE)
 | |
| 			return 0;
 | |
| 	}
 | |
| 	strcpy(name, d->wfd.cFileName);
 | |
| 	d->index++;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| void
 | |
| rewinddir(DIR *d)
 | |
| {
 | |
| 	FindClose(d->handle);
 | |
| 	d->handle = FindFirstFile(d->path, &d->wfd);
 | |
| 	d->index = 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| chown(char *path, int uid, int perm)
 | |
| {
 | |
| /*	panic("chown"); */
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| DIR*
 | |
| opendir(char *p)
 | |
| {
 | |
| 	DIR *d;
 | |
| 	char path[MAX_PATH];
 | |
| 
 | |
| 	
 | |
| 	snprint(path, sizeof(path), "%s/*.*", p);
 | |
| 
 | |
| 	d = mallocz(sizeof(DIR), 1);
 | |
| 	if(d == 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	d->index = 0;
 | |
| 
 | |
| 	d->handle = FindFirstFile(path, &d->wfd);
 | |
| 	if(d->handle == INVALID_HANDLE_VALUE) {
 | |
| 		free(d);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	d->path = strdup(path);
 | |
| 	return d;
 | |
| }
 | |
| 
 | |
| Dev fsdevtab = {
 | |
| 	'U',
 | |
| 	"fs",
 | |
| 
 | |
| 	devreset,
 | |
| 	devinit,
 | |
| 	devshutdown,
 | |
| 	fsattach,
 | |
| 	fswalk,
 | |
| 	fsstat,
 | |
| 	fsopen,
 | |
| 	fscreate,
 | |
| 	fsclose,
 | |
| 	fsread,
 | |
| 	devbread,
 | |
| 	fswrite,
 | |
| 	devbwrite,
 | |
| 	fsremove,
 | |
| 	fswstat,
 | |
| };
 |