text: #include	"u.h"
  #include	<sys/types.h>
  #include	<sys/stat.h>
  #include	<dirent.h>
  #include	<fcntl.h>
  #include	<errno.h>
  #include	<stdio.h> /* for remove, rename */
  #include	<limits.h>
  #include	<pwd.h>
  #include	<grp.h>
  #include	<sys/time.h>
  
  #ifndef NAME_MAX
  #	define NAME_MAX 256
  #endif
  #include	"lib.h"
  #include 	"mem.h"
  #include	"dat.h"
  #include	"fns.h"
  #include	"error.h"
  
  
  typedef	struct Ufsinfo	Ufsinfo;
  
  enum
  {
  	NUID	= 256,
  	NGID	= 256,
  	MAXPATH	= 1024,
  	MAXCOMP	= 128
  };
  
  struct Ufsinfo
  {
  	int	mode;
  	int	fd;
  	int	uid;
  	int	gid;
  	DIR*	dir;
  	vlong	offset;
  	QLock	oq;
  	char nextname[NAME_MAX];
  };
  
  char	*base = "/";
  
  static	Qid	fsqid(char*, struct stat *);
  static	void	fspath(Chan*, char*, char*);
  static	ulong	fsdirread(Chan*, uchar*, int, ulong);
  static	int	fsomode(int);
  
  /* clumsy hack, but not worse than the Path stuff in the last one */
  static char*
  uc2name(Chan *c)
  {
  	char *s;
  
  	if(c->path == nil)
  		return "/";
  	s = chanpath(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->mode = stbuf.st_mode;
  	uif->uid = stbuf.st_uid;
  	uif->gid = stbuf.st_gid;
  
  	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->mode = stbuf.st_mode;
  	uif->uid = stbuf.st_uid;
  	uif->gid = stbuf.st_gid;
  
  	c->qid = fsqid(path, &stbuf);
  
  	return 1;
  }
  
  extern Path* addelem(Path*, char*, Chan*);
  
  static Walkqid*
  fswalk(Chan *c, Chan *nc, char **name, int nname)
  {
  	int i;
  	Path *path;
  	Walkqid *wq;
  
  	if(nc != nil)
  		panic("fswalk: nc != nil");
  	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
  	nc = devclone(c);
  	path = c->path;
  	incref(&path->ref);
  
  	fsclone(c, nc);
  	wq->clone = nc;
  	for(i=0; i<nname; i++){
  		nc->path = path;
  		if(fswalk1(nc, name[i]) == 0)
  			break;
  		path = addelem(path, name[i], c);
  		wq->qid[i] = nc->qid;
  	}
  	nc->path = path;
  	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;
  	struct passwd *pwd;
  	struct group *grp;
  	char path[MAXPATH];
  
  	if(n < BIT16SZ)
  		error(Eshortstat);
  
  	fspath(c, 0, path);
  	if(stat(path, &stbuf) < 0)
  		error(strerror(errno));
  
  	pwd = getpwuid(stbuf.st_uid);
  	grp = getgrgid(stbuf.st_gid);
  
  	d.name = lastelem(c);
  	d.uid = pwd->pw_name;
  	if(grp)
  		d.gid = grp->gr_name;
  	else {
  		char buf[8];
  		sprint(buf, "%d", stbuf.st_gid);
  		d.gid = buf;
  	}
  	d.muid = d.uid;
  	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, 0666);
  
  		if(uif->fd < 0)
  			error(strerror(errno));
  	}
  	uif->offset = 0;
  
  	c->offset = 0;
  	c->flag |= COPEN;
  	return c;
  }
  
  /*
     Not exactly sure why fscreate() should override the umask.
     Cutting the chmod() and chown() out make drawterm behave better
     when touching and creating local files (from /mnt/term/).
   */
  
  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, perm & 0777) < 0)
  			error(strerror(errno));
  
  		fd = open(path, 0);
  #if 0
  		if(fd >= 0) {
  			chmod(path, perm & 0777);
  			chown(path, uif->uid, uif->uid);
  		}
  #endif
  		close(fd);
  
  		uif->dir = opendir(path);
  		if(uif->dir == 0)
  			error(strerror(errno));
  	}
  	else {
  		fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
  		if(fd >= 0) {
  			if(m != 1) {
  				close(fd);
  				fd = open(path, m);
  			}
  #if 0
  			chmod(path, perm & 0777);
  			chown(path, uif->uid, uif->gid);
  #endif
  		}
  		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;
  	vlong 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 == -1)
  			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;
  	vlong 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 == -1)
  			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));
  }
  
  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;
  
  	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;
  	}
  
  	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(lstat(new, &stbuf) >= 0)
  			error(Eexist);
  		if(rename(old, new) < 0)
  			error(strerror(errno));
  	}
  
  	if(d.mtime != -1){
  		struct timeval tv[2];
  
  		if (d.atime == -1) {
  			if (gettimeofday(&tv[0], NULL) == -1)
  				error(strerror(errno));		
  		} else
  			tv[0].tv_sec = d.atime;
  		tv[1].tv_sec = d.mtime;
  		utimes(old, tv);
  	}
  
  /*
  	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(strerror(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)
  {
  	/* assume len of MAXPATH */
  	if(ext == nil)
  		snprint(path, MAXPATH, "%s/%s", base, uc2name(c));
  	else
  		snprint(path, MAXPATH, "%s/%s/%s", base, uc2name(c), 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)
  {
  	struct dirent *de;
  	
  	if(uif->nextname[0]){
  		strcpy(name, uif->nextname);
  		uif->nextname[0] = 0;
  		return 1;
  	}
  
  	de = readdir(uif->dir);
  	if(de == NULL)
  		return 0;
  		
  	strcpy(name, de->d_name);
  	return 1;
  }
  
  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;
  	struct passwd *pwd;
  	struct group *grp;
  
  /* print("fsdirread %s\n", uc2name(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;
  		snprint(path, sizeof path, "%s/%s", dirpath, de);
  		memset(&stbuf, 0, sizeof stbuf);
  
  		if(stat(path, &stbuf) < 0) {
  			/* fprint(2, "dir: bad path %s\n", path); */
  			/* but continue... probably a bad symlink */
  		}
  
  		pwd = getpwuid(stbuf.st_uid);
  		grp = getgrgid(stbuf.st_gid);
  
  		if(pwd)
  			d.uid = pwd->pw_name;
  		else
  			d.uid = "unknown";
  		if(grp)
  			d.gid = grp->gr_name;
  		else {
  			d.gid = "unknown";
  		}
  		d.muid = d.uid;
  		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, (uchar*)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;
  }
  
  Dev fsdevtab = {
  	'U',
  	"fs",
  
  	devreset,
  	devinit,
  	devshutdown,
  	fsattach,
  	fswalk,
  	fsstat,
  	fsopen,
  	fscreate,
  	fsclose,
  	fsread,
  	devbread,
  	fswrite,
  	devbwrite,
  	fsremove,
  	fswstat,
  };
  