summaryrefslogtreecommitdiff
path: root/emu/port/devfs-posix.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /emu/port/devfs-posix.c
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'emu/port/devfs-posix.c')
-rw-r--r--emu/port/devfs-posix.c1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/emu/port/devfs-posix.c b/emu/port/devfs-posix.c
new file mode 100644
index 00000000..512c240c
--- /dev/null
+++ b/emu/port/devfs-posix.c
@@ -0,0 +1,1058 @@
+/*
+ * Unix file system interface
+ */
+#define _LARGEFILE64_SOURCE 1
+#define _FILE_OFFSET_BITS 64
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <utime.h>
+#include <dirent.h>
+#include <stdio.h>
+#define __EXTENSIONS__
+#undef getwd
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+typedef struct Fsinfo Fsinfo;
+struct Fsinfo
+{
+ int uid;
+ int gid;
+ int mode; /* Unix mode */
+ DIR* dir; /* open directory */
+ struct dirent* de; /* directory reading */
+ int fd; /* open files */
+ ulong offset; /* offset when reading directory */
+ QLock oq; /* mutex for offset */
+ char* spec;
+ Cname* name; /* Unix's name for file */
+ Qid rootqid; /* Plan 9's qid for Inferno's root */
+};
+
+#define FS(c) ((Fsinfo*)(c)->aux)
+
+enum
+{
+ IDSHIFT = 8,
+ NID = 1 << IDSHIFT,
+ IDMASK = NID - 1,
+ MAXPATH = 1024 /* TO DO: eliminate this */
+};
+
+typedef struct Pass Pass;
+struct Pass
+{
+ int id;
+ int gid;
+ char* name;
+ Pass* next;
+};
+
+char rootdir[MAXROOT] = ROOT;
+
+static Pass* uid[NID];
+static Pass* gid[NID];
+static Pass* member[NID];
+static RWlock idl;
+
+static Qid fsqid(struct stat *);
+static void fspath(Cname*, char*, char*);
+static int fsdirconv(Chan*, char*, struct stat*, uchar*, int, int);
+static Cname* fswalkpath(Cname*, char*, int);
+static char* fslastelem(Cname*);
+static char* id2name(Pass**, int);
+static int ingroup(int id, int gid);
+static void fsperm(Chan*, int);
+static long fsdirread(Chan*, uchar*, int, vlong);
+static int fsomode(int);
+static Pass* name2pass(Pass**, char*);
+static void getpwdf(void);
+static void getgrpf(void);
+static void fsremove(Chan*);
+
+/* Unix libc */
+
+extern struct passwd *getpwent(void);
+extern struct group *getgrent(void);
+
+/*
+ * this crud is to compensate for invalid symbolic links;
+ * all you can do is delete them, and good riddance
+ */
+static int
+xstat(char *f, struct stat *sb)
+{
+ if(stat(f, sb) >= 0)
+ return 0;
+ /* could possibly generate ->name as rob suggested */
+ return lstat(f, sb);
+}
+
+static void
+fsfree(Chan *c)
+{
+ cnameclose(FS(c)->name);
+ free(FS(c));
+}
+
+Chan*
+fsattach(char *spec)
+{
+ Chan *c;
+ struct stat stbuf;
+ static int devno;
+ static Lock l;
+
+ getpwdf();
+ getgrpf();
+
+ if(!emptystr(spec) && strcmp(spec, "*") != 0)
+ error(Ebadspec);
+ if(stat(rootdir, &stbuf) < 0)
+ oserror();
+
+ c = devattach('U', spec);
+ c->qid = fsqid(&stbuf);
+ c->aux = smalloc(sizeof(Fsinfo));
+ FS(c)->dir = nil;
+ FS(c)->de = nil;
+ FS(c)->fd = -1;
+ FS(c)->gid = stbuf.st_gid;
+ FS(c)->uid = stbuf.st_uid;
+ FS(c)->mode = stbuf.st_mode;
+ lock(&l);
+ c->dev = devno++;
+ unlock(&l);
+ if (!emptystr(spec)){
+ FS(c)->spec = "/";
+ FS(c)->name = newcname(FS(c)->spec);
+ }else
+ FS(c)->name = newcname(rootdir);
+ FS(c)->rootqid = c->qid;
+
+ return c;
+}
+
+Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ int j;
+ volatile int alloc;
+ Walkqid *wq;
+ struct stat stbuf;
+ char *n;
+ Cname *next;
+ Cname *volatile current;
+ Qid rootqid;
+
+ 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;
+ rootqid = FS(c)->rootqid;
+ 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;
+ }
+ n = name[j];
+ if(strcmp(n, ".") != 0 && !(isdotdot(n) && nc->qid.path == rootqid.path)){
+ next = current;
+ incref(&next->r);
+ next = addelem(current, n);
+ //print("** ufs walk '%s' -> %s [%s]\n", current->s, n, next->s);
+ if(xstat(next->s, &stbuf) < 0){
+ cnameclose(next);
+ if(j == 0)
+ error(Enonexist);
+ strcpy(up->env->errstr, Enonexist);
+ break;
+ }
+ nc->qid = fsqid(&stbuf);
+ 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;
+ if (nname) {
+ FS(nc)->gid = stbuf.st_gid;
+ FS(nc)->uid = stbuf.st_uid;
+ FS(nc)->mode = stbuf.st_mode;
+ } else {
+ FS(nc)->gid = FS(c)->gid;
+ FS(nc)->uid = FS(c)->uid;
+ FS(nc)->mode = FS(c)->mode;
+ }
+ FS(nc)->name = current;
+ FS(nc)->spec = FS(c)->spec;
+ FS(nc)->rootqid = rootqid;
+ FS(nc)->fd = -1;
+ FS(nc)->dir = nil;
+ FS(nc)->de = nil;
+ }
+ return wq;
+}
+
+static int
+fsstat(Chan *c, uchar *dp, int n)
+{
+ struct stat stbuf;
+ char *p;
+
+ if(xstat(FS(c)->name->s, &stbuf) < 0)
+ oserror();
+ p = fslastelem(FS(c)->name);
+ if(*p == 0)
+ p = "/";
+ rlock(&idl);
+ n = fsdirconv(c, p, &stbuf, dp, n, 0);
+ runlock(&idl);
+ return n;
+}
+
+static Chan*
+fsopen(Chan *c, int mode)
+{
+ int m, isdir;
+
+ m = mode & (OTRUNC|3);
+ switch(m) {
+ case 0:
+ fsperm(c, 4);
+ break;
+ case 1:
+ case 1|16:
+ fsperm(c, 2);
+ break;
+ case 2:
+ case 0|16:
+ case 2|16:
+ fsperm(c, 4);
+ fsperm(c, 2);
+ break;
+ case 3:
+ fsperm(c, 1);
+ break;
+ default:
+ error(Ebadarg);
+ }
+
+ isdir = c->qid.type & QTDIR;
+
+ if(isdir && mode != OREAD)
+ error(Eperm);
+
+ m = fsomode(m & 3);
+ c->mode = openmode(mode);
+
+ if(isdir) {
+ FS(c)->dir = opendir(FS(c)->name->s);
+ if(FS(c)->dir == 0)
+ oserror();
+ }
+ else {
+ if(mode & OTRUNC)
+ m |= O_TRUNC;
+ FS(c)->fd = open(FS(c)->name->s, m, 0666);
+ if(FS(c)->fd < 0)
+ oserror();
+ }
+
+ c->offset = 0;
+ FS(c)->offset = 0;
+ c->flag |= COPEN;
+ return c;
+}
+
+static void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+ int fd, m, o;
+ struct stat stbuf;
+ Cname *n;
+
+ fsperm(c, 2);
+
+ m = fsomode(mode&3);
+ openmode(mode); /* get the errors out of the way */
+
+ if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ error(Efilename);
+ n = fswalkpath(FS(c)->name, name, 1);
+ if(waserror()){
+ cnameclose(n);
+ nexterror();
+ }
+ if(perm & DMDIR) {
+ if(m)
+ error(Eperm);
+
+ perm &= ~0777 | (FS(c)->mode & 0777);
+ if(mkdir(n->s, perm) < 0)
+ oserror();
+
+ fd = open(n->s, 0);
+ if(fd < 0)
+ oserror();
+ fchmod(fd, perm);
+ fchown(fd, up->env->uid, FS(c)->gid);
+ if(fstat(fd, &stbuf) <0){
+ close(fd);
+ oserror();
+ }
+ close(fd);
+ FS(c)->dir = opendir(n->s);
+ if(FS(c)->dir == nil)
+ oserror();
+ } else {
+ o = (O_CREAT | O_EXCL) | (mode&3);
+ if(mode & OTRUNC)
+ o |= O_TRUNC;
+ perm &= ~0666 | (FS(c)->mode & 0666);
+ fd = open(n->s, o, perm);
+ if(fd < 0)
+ oserror();
+ fchmod(fd, perm);
+ fchown(fd, up->env->uid, FS(c)->gid);
+ if(fstat(fd, &stbuf) < 0){
+ close(fd);
+ oserror();
+ }
+ FS(c)->fd = fd;
+ }
+ cnameclose(FS(c)->name);
+ FS(c)->name = n;
+ poperror();
+
+ c->qid = fsqid(&stbuf);
+ FS(c)->gid = stbuf.st_gid;
+ FS(c)->uid = stbuf.st_uid;
+ FS(c)->mode = stbuf.st_mode;
+ c->mode = openmode(mode);
+ c->offset = 0;
+ FS(c)->offset = 0;
+ c->flag |= COPEN;
+}
+
+static void
+fsclose(Chan *c)
+{
+ if((c->flag & COPEN) != 0){
+ if(c->qid.type & QTDIR)
+ closedir(FS(c)->dir);
+ else
+ close(FS(c)->fd);
+ }
+ if(c->flag & CRCLOSE) {
+ if(!waserror()) {
+ fsremove(c);
+ poperror();
+ }
+ return;
+ }
+ fsfree(c);
+}
+
+static long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+ long r;
+
+ if(c->qid.type & QTDIR){
+ qlock(&FS(c)->oq);
+ if(waserror()) {
+ qunlock(&FS(c)->oq);
+ nexterror();
+ }
+ r = fsdirread(c, va, n, offset);
+ poperror();
+ qunlock(&FS(c)->oq);
+ }else{
+ r = pread(FS(c)->fd, va, n, offset);
+ if(r < 0 && (errno == ESPIPE || errno == EPIPE)){
+ r = read(FS(c)->fd, va, n);
+ if(r < 0)
+ oserror();
+ }
+ }
+ return r;
+}
+
+static long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+ long r;
+
+ r = pwrite(FS(c)->fd, va, n, offset);
+ if(r < 0 && (errno == ESPIPE || errno == EPIPE)){
+ r = write(FS(c)->fd, va, n);
+ if(r < 0)
+ oserror();
+ }
+ return r;
+}
+
+static void
+fswchk(Cname *c)
+{
+ struct stat stbuf;
+
+ if(stat(c->s, &stbuf) < 0)
+ oserror();
+
+ if(stbuf.st_uid == up->env->uid)
+ stbuf.st_mode >>= 6;
+ else
+ if(stbuf.st_gid == up->env->gid || ingroup(up->env->uid, stbuf.st_gid))
+ stbuf.st_mode >>= 3;
+
+ if(stbuf.st_mode & S_IWOTH)
+ return;
+
+ error(Eperm);
+}
+
+static void
+fsremove(Chan *c)
+{
+ int n;
+ volatile struct { Cname *dir; } dir;
+
+ dir.dir = fswalkpath(FS(c)->name, "..", 1);
+ if(waserror()){
+ if(dir.dir != nil)
+ cnameclose(dir.dir);
+ fsfree(c);
+ nexterror();
+ }
+ fswchk(dir.dir);
+ cnameclose(dir.dir);
+ dir.dir = nil;
+ if(c->qid.type & QTDIR)
+ n = rmdir(FS(c)->name->s);
+ else
+ n = remove(FS(c)->name->s);
+ if(n < 0)
+ oserror();
+ poperror();
+ fsfree(c);
+}
+
+static int
+fswstat(Chan *c, uchar *buf, int nb)
+{
+ Dir *d;
+ Pass *p;
+ volatile struct { Cname *ph; } ph;
+ struct stat stbuf;
+ struct utimbuf utbuf;
+ int tsync;
+
+ if(FS(c)->fd >= 0){
+ if(fstat(FS(c)->fd, &stbuf) < 0)
+ oserror();
+ }else{
+ if(stat(FS(c)->name->s, &stbuf) < 0)
+ oserror();
+ }
+ d = malloc(sizeof(*d)+nb);
+ if(d == nil)
+ error(Enomem);
+ if(waserror()){
+ free(d);
+ nexterror();
+ }
+ tsync = 1;
+ nb = convM2D(buf, nb, d, (char*)&d[1]);
+ if(nb == 0)
+ error(Eshortstat);
+ if(!emptystr(d->name) && strcmp(d->name, fslastelem(FS(c)->name)) != 0) {
+ tsync = 0;
+ validname(d->name, 0);
+ ph.ph = fswalkpath(FS(c)->name, "..", 1);
+ if(waserror()){
+ cnameclose(ph.ph);
+ nexterror();
+ }
+ fswchk(ph.ph);
+ ph.ph = fswalkpath(ph.ph, d->name, 0);
+ if(rename(FS(c)->name->s, ph.ph->s) < 0)
+ oserror();
+ cnameclose(FS(c)->name);
+ poperror();
+ FS(c)->name = ph.ph;
+ }
+
+ if(d->mode != ~0 && (d->mode&0777) != (stbuf.st_mode&0777)) {
+ tsync = 0;
+ if(up->env->uid != stbuf.st_uid)
+ error(Eowner);
+ if(FS(c)->fd >= 0){
+ if(fchmod(FS(c)->fd, d->mode&0777) < 0)
+ oserror();
+ }else{
+ if(chmod(FS(c)->name->s, d->mode&0777) < 0)
+ oserror();
+ }
+ FS(c)->mode &= ~0777;
+ FS(c)->mode |= d->mode&0777;
+ }
+
+ if(d->atime != ~0 && d->atime != stbuf.st_atime
+ || d->mtime != ~0 && d->mtime != stbuf.st_mtime) {
+ tsync = 0;
+ if(up->env->uid != stbuf.st_uid)
+ error(Eowner);
+ if(d->mtime != ~0)
+ utbuf.modtime = d->mtime;
+ else
+ utbuf.modtime = stbuf.st_mtime;
+ if(d->atime != ~0)
+ utbuf.actime = d->atime;
+ else
+ utbuf.actime = stbuf.st_atime;
+ if(utime(FS(c)->name->s, &utbuf) < 0) /* TO DO: futimes isn't portable */
+ oserror();
+ }
+
+ if(*d->gid){
+ tsync = 0;
+ rlock(&idl);
+ if(waserror()){
+ runlock(&idl);
+ nexterror();
+ }
+ p = name2pass(gid, d->gid);
+ if(p == 0)
+ error(Eunknown);
+ if(p->id != stbuf.st_gid) {
+ if(up->env->uid != stbuf.st_uid)
+ error(Eowner);
+ if(FS(c)->fd >= 0){
+ if(fchown(FS(c)->fd, stbuf.st_uid, p->id) < 0)
+ oserror();
+ }else{
+ if(chown(FS(c)->name->s, stbuf.st_uid, p->id) < 0)
+ oserror();
+ }
+ FS(c)->gid = p->id;
+ }
+ poperror();
+ runlock(&idl);
+ }
+
+ if(d->length != ~(uvlong)0){
+ tsync = 0;
+ if(FS(c)->fd >= 0){
+ fsperm(c, 2);
+ if(ftruncate(FS(c)->fd, d->length) < 0)
+ oserror();
+ }else{
+ fswchk(FS(c)->name);
+ if(truncate(FS(c)->name->s, d->length) < 0)
+ oserror();
+ }
+ }
+
+ poperror();
+ free(d);
+ if(tsync && FS(c)->fd >= 0 && fsync(FS(c)->fd) < 0)
+ oserror();
+ return nb;
+}
+
+#define QDEVBITS 4 /* 16 devices should be plenty */
+#define MAXDEV (1<<QDEVBITS)
+#define QDEVSHIFT (64-QDEVBITS)
+#define QINOMASK (((uvlong)1<<QDEVSHIFT)-1)
+#define QPATH(d,i) (((uvlong)(d)<<QDEVSHIFT)|((uvlong)(i)&QINOMASK))
+
+static Qid
+fsqid(struct stat *st)
+{
+ Qid q;
+ ulong dev;
+ int idev;
+ static int nqdev = 0;
+ static ulong qdev[MAXDEV];
+ static Lock l;
+
+ q.type = QTFILE;
+ if(S_ISDIR(st->st_mode))
+ q.type = QTDIR;
+
+ dev = st->st_dev;
+ lock(&l);
+ for(idev = 0; idev < nqdev; idev++)
+ if(qdev[idev] == dev)
+ break;
+ if(idev == nqdev) {
+ if(nqdev == MAXDEV) {
+ unlock(&l);
+ error("too many devices");
+ }
+ qdev[nqdev++] = dev;
+ }
+ unlock(&l);
+
+ if(0) /* we'll just let it be masked off */
+ if((uvlong)st->st_ino & ~QINOMASK)
+ error("inode number too large");
+
+ q.path = QPATH(idev, st->st_ino);
+ q.vers = st->st_mtime;
+
+ return q;
+}
+
+static void
+fspath(Cname *c, char *name, char *path)
+{
+ int n;
+
+ if(c->len+strlen(name) >= MAXPATH)
+ panic("fspath: name too long");
+ memmove(path, c->s, c->len);
+ n = c->len;
+ if(path[n-1] != '/')
+ path[n++] = '/';
+ strcpy(path+n, name);
+ if(isdotdot(name))
+ cleanname(path);
+/*print("->%s\n", path);*/
+}
+
+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;
+}
+
+/*
+ * Assuming pass is one of the static arrays protected by idl, caller must
+ * hold idl in writer mode.
+ */
+static void
+freepass(Pass **pass)
+{
+ int i;
+ Pass *p, *np;
+
+ for(i=0; i<NID; i++){
+ for(p = pass[i]; p; p = np){
+ np = p->next;
+ free(p);
+ }
+ pass[i] = 0;
+ }
+}
+
+static void
+getpwdf(void)
+{
+ unsigned i;
+ Pass *p;
+ static int mtime; /* serialized by idl */
+ struct stat stbuf;
+ struct passwd *pw;
+
+ if(stat("/etc/passwd", &stbuf) < 0)
+ panic("can't read /etc/passwd");
+
+ /*
+ * Unlocked peek is okay, since the check is a heuristic (as is
+ * the function).
+ */
+ if(stbuf.st_mtime <= mtime)
+ return;
+
+ wlock(&idl);
+ if(stbuf.st_mtime <= mtime) {
+ /*
+ * If we lost a race on updating the database, we can
+ * avoid some work.
+ */
+ wunlock(&idl);
+ return;
+ }
+ mtime = stbuf.st_mtime;
+ freepass(uid);
+ setpwent();
+ while(pw = getpwent()){
+ i = pw->pw_uid;
+ i = (i&IDMASK) ^ ((i>>IDSHIFT)&IDMASK);
+ p = realloc(0, sizeof(Pass));
+ if(p == 0)
+ panic("getpwdf");
+
+ p->next = uid[i];
+ uid[i] = p;
+ p->id = pw->pw_uid;
+ p->gid = pw->pw_gid;
+ p->name = strdup(pw->pw_name);
+ if(p->name == 0)
+ panic("no memory");
+ }
+
+ wunlock(&idl);
+ endpwent();
+}
+
+static void
+getgrpf(void)
+{
+ static int mtime; /* serialized by idl */
+ struct stat stbuf;
+ struct group *pw;
+ unsigned i;
+ int j;
+ Pass *p, *q;
+
+ if(stat("/etc/group", &stbuf) < 0)
+ panic("can't read /etc/group");
+
+ /*
+ * Unlocked peek is okay, since the check is a heuristic (as is
+ * the function).
+ */
+ if(stbuf.st_mtime <= mtime)
+ return;
+
+ wlock(&idl);
+ if(stbuf.st_mtime <= mtime) {
+ /*
+ * If we lost a race on updating the database, we can
+ * avoid some work.
+ */
+ wunlock(&idl);
+ return;
+ }
+ mtime = stbuf.st_mtime;
+ freepass(gid);
+ freepass(member);
+ /*
+ * Pass one -- group name to gid mapping.
+ */
+ setgrent();
+ while(pw = getgrent()){
+ i = pw->gr_gid;
+ i = (i&IDMASK) ^ ((i>>IDSHIFT)&IDMASK);
+ p = realloc(0, sizeof(Pass));
+ if(p == 0)
+ panic("getpwdf");
+ p->next = gid[i];
+ gid[i] = p;
+ p->id = pw->gr_gid;
+ p->gid = 0;
+ p->name = strdup(pw->gr_name);
+ if(p->name == 0)
+ panic("no memory");
+ }
+ /*
+ * Pass two -- group memberships.
+ */
+ setgrent();
+ while(pw = getgrent()){
+ for (j = 0;; j++) {
+ if (pw->gr_mem[j] == nil)
+ break;
+ q = name2pass(gid, pw->gr_mem[j]);
+ if (q == nil)
+ continue;
+ i = q->id + pw->gr_gid;
+ i = (i&IDMASK) ^ ((i>>IDSHIFT)&IDMASK);
+ p = realloc(0, sizeof(Pass));
+ if(p == 0)
+ panic("getpwdf");
+ p->next = member[i];
+ member[i] = p;
+ p->id = q->id;
+ p->gid = pw->gr_gid;
+ }
+ }
+
+ wunlock(&idl);
+ endgrent();
+}
+
+/* Caller must hold idl. Does not raise an error. */
+static Pass*
+name2pass(Pass **pw, char *name)
+{
+ int i;
+ static Pass *p;
+ static Pass **pwdb;
+
+ if(p && pwdb == pw && strcmp(name, p->name) == 0)
+ return p;
+
+ for(i=0; i<NID; i++)
+ for(p = pw[i]; p; p = p->next)
+ if(strcmp(name, p->name) == 0) {
+ pwdb = pw;
+ return p;
+ }
+
+ return 0;
+}
+
+/* Caller must hold idl. Does not raise an error. */
+static char*
+id2name(Pass **pw, int id)
+{
+ int i;
+ Pass *p;
+ char *s;
+
+ s = nil;
+ /* use last on list == first in file */
+ i = (id&IDMASK) ^ ((id>>IDSHIFT)&IDMASK);
+ for(p = pw[i]; p; p = p->next)
+ if(p->id == id)
+ s = p->name;
+ if(s)
+ return s;
+ return ""; /* TO DO: should be "%d" */
+}
+
+/* Caller must hold idl. Does not raise an error. */
+static int
+ingroup(int id, int gid)
+{
+ int i;
+ Pass *p;
+
+ i = id+gid;
+ i = (id&IDMASK) ^ ((id>>IDSHIFT)&IDMASK);
+ for(p = member[i]; p; p = p->next)
+ if(p->id == id && p->gid == gid)
+ return 1;
+ return 0;
+}
+
+static void
+fsperm(Chan *c, int mask)
+{
+ int m;
+
+ m = FS(c)->mode;
+/*
+ print("fsperm: %o %o uuid %d ugid %d cuid %d cgid %d\n",
+ m, mask, up->env->uid, up->env->gid, FS(c)->uid, FS(c)->gid);
+*/
+ if(FS(c)->uid == up->env->uid)
+ m >>= 6;
+ else
+ if(FS(c)->gid == up->env->gid || ingroup(up->env->uid, FS(c)->gid))
+ m >>= 3;
+
+ m &= mask;
+ if(m == 0)
+ error(Eperm);
+}
+
+static int
+isdots(char *name)
+{
+ return name[0] == '.' && (name[1] == '\0' || name[1] == '.' && name[2] == '\0');
+}
+
+static int
+fsdirconv(Chan *c, char *name, struct stat *s, uchar *va, int nb, int indir)
+{
+ Dir d;
+
+ memset(&d, 0, sizeof(d));
+ d.name = name;
+ d.uid = id2name(uid, s->st_uid);
+ d.gid = id2name(gid, s->st_gid);
+ d.muid = "";
+ d.qid = fsqid(s);
+ d.mode = (d.qid.type<<24)|(s->st_mode&0777);
+ d.atime = s->st_atime;
+ d.mtime = s->st_mtime;
+ d.length = s->st_size;
+ if(d.mode&DMDIR)
+ d.length = 0;
+ d.type = 'U';
+ d.dev = c->dev;
+ if(indir && sizeD2M(&d) > nb)
+ return -1; /* directory reader needs to know it didn't fit */
+ return convD2M(&d, va, nb);
+}
+
+static long
+fsdirread(Chan *c, uchar *va, int count, vlong offset)
+{
+ int i;
+ long n, r;
+ struct stat stbuf;
+ char path[MAXPATH], *ep;
+ struct dirent *de;
+ static char slop[8192];
+
+ i = 0;
+ fspath(FS(c)->name, "", path);
+ ep = path+strlen(path);
+ if(FS(c)->offset != offset) {
+ seekdir(FS(c)->dir, 0);
+ FS(c)->de = nil;
+ for(n=0; n<offset; ) {
+ de = readdir(FS(c)->dir);
+ if(de == 0) {
+ /* EOF, so stash offset and return 0 */
+ FS(c)->offset = n;
+ return 0;
+ }
+ if(de->d_ino==0 || de->d_name[0]==0 || isdots(de->d_name))
+ continue;
+ strecpy(ep, path+sizeof(path), de->d_name);
+ if(xstat(path, &stbuf) < 0) {
+ fprint(2, "dir: bad path %s\n", path);
+ continue;
+ }
+ rlock(&idl);
+ r = fsdirconv(c, de->d_name, &stbuf, slop, sizeof(slop), 1);
+ runlock(&idl);
+ if(r <= 0) {
+ FS(c)->offset = n;
+ return 0;
+ }
+ n += r;
+ }
+ FS(c)->offset = offset;
+ }
+
+ /*
+ * Take idl on behalf of id2name. Stalling attach, which is a
+ * rare operation, until the readdir completes is probably
+ * preferable to adding lock round-trips.
+ */
+ rlock(&idl);
+ while(i < count){
+ de = FS(c)->de;
+ FS(c)->de = nil;
+ if(de == nil)
+ de = readdir(FS(c)->dir);
+ if(de == nil)
+ break;
+
+ if(de->d_ino==0 || de->d_name[0]==0 || isdots(de->d_name))
+ continue;
+
+ strecpy(ep, path+sizeof(path), de->d_name);
+ if(xstat(path, &stbuf) < 0) {
+ fprint(2, "dir: bad path %s\n", path);
+ continue;
+ }
+ r = fsdirconv(c, de->d_name, &stbuf, va+i, count-i, 1);
+ if(r <= 0){
+ FS(c)->de = de;
+ break;
+ }
+ i += r;
+ FS(c)->offset += r;
+ }
+ runlock(&idl);
+ return i;
+}
+
+static int
+fsomode(int m)
+{
+ if(m < 0 || m > 3)
+ error(Ebadarg);
+ return m == 3? 0: m;
+}
+
+void
+setid(char *name, int owner)
+{
+ Pass *p;
+
+ if(owner && !iseve())
+ return;
+ kstrdup(&up->env->user, name);
+
+ rlock(&idl);
+ p = name2pass(uid, name);
+ if(p == nil){
+ runlock(&idl);
+ up->env->uid = -1;
+ up->env->gid = -1;
+ return;
+ }
+
+ up->env->uid = p->id;
+ up->env->gid = p->gid;
+ runlock(&idl);
+}
+
+Dev fsdevtab = {
+ 'U',
+ "fs",
+
+ devinit,
+ fsattach,
+ fswalk,
+ fsstat,
+ fsopen,
+ fscreate,
+ fsclose,
+ fsread,
+ devbread,
+ fswrite,
+ devbwrite,
+ fsremove,
+ fswstat
+};