/* * Unix file system interface */ #define _LARGEFILE64_SOURCE 1 #define _FILE_OFFSET_BITS 64 #include "dat.h" #include "fns.h" #include "error.h" #include #include #include #include #include #include #define __EXTENSIONS__ #undef getwd #include #include #include 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(¤t->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<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; inext; 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; inext) 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; ndir); 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 };