ref: 3866717cbb020199d58171c1c0cdd7382a74ee82
dir: /emu/port/devfs-posix.c/
/*
* 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 <fcntl.h>
#include <sys/socket.h>
#include <sys/un.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 */
int eod; /* end of directory */
int issocket;
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 User User;
struct User
{
int id; /* might be user or group ID */
int gid; /* if it's a user not a group, the group ID (only for setid) */
char* name;
int nmem;
int* mem; /* member array, if nmem != 0 */
User* next;
};
char rootdir[MAXROOT] = ROOT;
static User* uidmap[NID];
static User* gidmap[NID];
static QLock idl;
static User* name2user(User**, char*, User* (*get)(char*));
static User* id2user(User**, int, User* (*get)(int));
static User* newuid(int);
static User* newgid(int);
static User* newuname(char*);
static User* newgname(char*);
static Qid fsqid(struct stat *);
static void fspath(Cname*, char*, char*);
static int fsdirconv(Chan*, char*, char*, struct stat*, uchar*, int, int);
static Cname* fswalkpath(Cname*, char*, int);
static char* fslastelem(Cname*);
static int ingroup(int id, int gid);
static void fsperm(Chan*, int);
static long fsdirread(Chan*, uchar*, int, vlong);
static int fsomode(int);
static void fsremove(Chan*);
static vlong osdisksize(int); /* defined by including file */
/*
* make invalid symbolic links visible; less confusing, and at least you can then delete them.
*/
static int
xstat(char *f, struct stat *sb)
{
if(stat(f, sb) >= 0)
return 0;
/* could possibly generate ->name as rob once 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 st;
static int devno;
static Lock l;
if(!emptystr(spec) && strcmp(spec, "*") != 0)
error(Ebadspec);
if(stat(rootdir, &st) < 0)
oserror();
if(!S_ISDIR(st.st_mode))
error(Enotdir);
c = devattach('U', spec);
c->qid = fsqid(&st);
c->aux = smalloc(sizeof(Fsinfo));
FS(c)->dir = nil;
FS(c)->de = nil;
FS(c)->fd = -1;
FS(c)->issocket = 0;
FS(c)->gid = st.st_gid;
FS(c)->uid = st.st_uid;
FS(c)->mode = st.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 st;
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, &st) < 0){
cnameclose(next);
if(j == 0)
error(Enonexist);
strcpy(up->env->errstr, Enonexist);
break;
}
nc->qid = fsqid(&st);
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 > 0) {
FS(nc)->gid = st.st_gid;
FS(nc)->uid = st.st_uid;
FS(nc)->mode = st.st_mode;
FS(nc)->issocket = S_ISSOCK(st.st_mode);
} else {
FS(nc)->gid = FS(c)->gid;
FS(nc)->uid = FS(c)->uid;
FS(nc)->mode = FS(c)->mode;
FS(nc)->issocket = FS(c)->issocket;
}
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 st;
char *p;
if(FS(c)->fd >= 0){
if(fstat(FS(c)->fd, &st) < 0)
oserror();
}else{
if(xstat(FS(c)->name->s, &st) < 0)
oserror();
}
p = fslastelem(FS(c)->name);
if(*p == 0)
p = "/";
qlock(&idl);
n = fsdirconv(c, FS(c)->name->s, p, &st, dp, n, 0);
qunlock(&idl);
return n;
}
static int
opensocket(char *path)
{
int fd;
struct sockaddr_un su;
memset(&su, 0, sizeof su);
su.sun_family = AF_UNIX;
if(strlen(path)+1 > sizeof su.sun_path)
error("unix socket name too long");
strcpy(su.sun_path, path);
if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return -1;
if(connect(fd, (struct sockaddr*)&su, sizeof su) >= 0)
return fd;
close(fd);
if((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
return -1;
if(connect(fd, (struct sockaddr*)&su, sizeof su) >= 0)
return fd;
close(fd);
return -1;
}
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 == nil)
oserror();
FS(c)->eod = 0;
}
else {
if(!FS(c)->issocket){
if(mode & OTRUNC)
m |= O_TRUNC;
FS(c)->fd = open(FS(c)->name->s, m, 0666);
}else
FS(c)->fd = opensocket(FS(c)->name->s);
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 st;
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, &st) <0){
close(fd);
oserror();
}
close(fd);
FS(c)->dir = opendir(n->s);
if(FS(c)->dir == nil)
oserror();
FS(c)->eod = 0;
} 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, &st) < 0){
close(fd);
oserror();
}
FS(c)->fd = fd;
}
cnameclose(FS(c)->name);
FS(c)->name = n;
poperror();
c->qid = fsqid(&st);
FS(c)->gid = st.st_gid;
FS(c)->uid = st.st_uid;
FS(c)->mode = st.st_mode;
c->mode = openmode(mode);
c->offset = 0;
FS(c)->offset = 0;
FS(c)->issocket = 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{
if(!FS(c)->issocket){
r = pread(FS(c)->fd, va, n, offset);
if(r >= 0)
return r;
if(errno != ESPIPE && errno != EPIPE)
oserror();
}
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;
if(!FS(c)->issocket){
r = pwrite(FS(c)->fd, va, n, offset);
if(r >= 0)
return r;
if(errno != ESPIPE && errno != EPIPE)
oserror();
}
r = write(FS(c)->fd, va, n);
if(r < 0)
oserror();
return r;
}
static void
fswchk(Cname *c)
{
struct stat st;
if(stat(c->s, &st) < 0)
oserror();
if(st.st_uid == up->env->uid)
st.st_mode >>= 6;
else if(st.st_gid == up->env->gid || ingroup(up->env->uid, st.st_gid))
st.st_mode >>= 3;
if(st.st_mode & S_IWOTH)
return;
error(Eperm);
}
static void
fsremove(Chan *c)
{
int n;
Cname *volatile dir;
if(waserror()){
fsfree(c);
nexterror();
}
dir = fswalkpath(FS(c)->name, "..", 1);
if(waserror()){
cnameclose(dir);
nexterror();
}
fswchk(dir);
cnameclose(dir);
poperror();
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;
User *p;
Cname *volatile ph;
struct stat st;
struct utimbuf utbuf;
int tsync;
if(FS(c)->fd >= 0){
if(fstat(FS(c)->fd, &st) < 0)
oserror();
}else{
if(stat(FS(c)->name->s, &st) < 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 = fswalkpath(FS(c)->name, "..", 1);
if(waserror()){
cnameclose(ph);
nexterror();
}
fswchk(ph);
ph = fswalkpath(ph, d->name, 0);
if(rename(FS(c)->name->s, ph->s) < 0)
oserror();
cnameclose(FS(c)->name);
poperror();
FS(c)->name = ph;
}
if(d->mode != ~0 && (d->mode&0777) != (st.st_mode&0777)) {
tsync = 0;
if(up->env->uid != st.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 != st.st_atime ||
d->mtime != ~0 && d->mtime != st.st_mtime) {
tsync = 0;
if(up->env->uid != st.st_uid)
error(Eowner);
if(d->mtime != ~0)
utbuf.modtime = d->mtime;
else
utbuf.modtime = st.st_mtime;
if(d->atime != ~0)
utbuf.actime = d->atime;
else
utbuf.actime = st.st_atime;
if(utime(FS(c)->name->s, &utbuf) < 0) /* TO DO: futimes isn't portable */
oserror();
}
if(*d->gid){
tsync = 0;
qlock(&idl);
if(waserror()){
qunlock(&idl);
nexterror();
}
p = name2user(gidmap, d->gid, newgname);
if(p == 0)
error(Eunknown);
if(p->id != st.st_gid) {
if(up->env->uid != st.st_uid)
error(Eowner);
if(FS(c)->fd >= 0){
if(fchown(FS(c)->fd, st.st_uid, p->id) < 0)
oserror();
}else{
if(chown(FS(c)->name->s, st.st_uid, p->id) < 0)
oserror();
}
FS(c)->gid = p->id;
}
poperror();
qunlock(&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;
}
static Qid
fsqid(struct stat *st)
{
Qid q;
u16int dev;
q.type = QTFILE;
if(S_ISDIR(st->st_mode))
q.type = QTDIR;
dev = (u16int)st->st_dev;
if(dev & 0x8000){
static int aware = 1;
if(aware==0){
aware = 1;
fprint(2, "fs: fsqid: top-bit dev: %#4.4ux\n", dev);
}
dev ^= 0x8080;
}
q.path = (uvlong)dev<<48;
q.path ^= 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;
}
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 *path, char *name, struct stat *s, uchar *va, int nb, int indir)
{
Dir d;
char uidbuf[NUMSIZE], gidbuf[NUMSIZE];
User *u;
int fd;
memset(&d, 0, sizeof(d));
d.name = name;
u = id2user(uidmap, s->st_uid, newuid);
if(u == nil){
snprint(uidbuf, sizeof(uidbuf), "#%lud", (long)s->st_uid);
d.uid = uidbuf;
}else
d.uid = u->name;
u = id2user(gidmap, s->st_gid, newgid);
if(u == nil){
snprint(gidbuf, sizeof(gidbuf), "#%lud", (long)s->st_gid);
d.gid = gidbuf;
}else
d.gid = u->name;
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;
else if(S_ISBLK(s->st_mode) && s->st_size == 0){
fd = open(path, O_RDONLY);
if(fd >= 0){
d.length = osdisksize(fd);
close(fd);
}
}
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 st;
char path[MAXPATH], *ep;
struct dirent *de;
static uchar 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;
FS(c)->eod = 0;
for(n=0; n<offset; ) {
de = readdir(FS(c)->dir);
if(de == 0) {
/* EOF, so stash offset and return 0 */
FS(c)->offset = n;
FS(c)->eod = 1;
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, &st) < 0) {
fprint(2, "dir: bad path %s\n", path);
continue;
}
qlock(&idl);
if(waserror()){
qunlock(&idl);
nexterror();
}
r = fsdirconv(c, path, de->d_name, &st, slop, sizeof(slop), 1);
poperror();
qunlock(&idl);
if(r <= 0) {
FS(c)->offset = n;
return 0;
}
n += r;
}
FS(c)->offset = offset;
}
if(FS(c)->eod)
return 0;
/*
* 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.
*/
qlock(&idl);
while(i < count){
de = FS(c)->de;
FS(c)->de = nil;
if(de == nil)
de = readdir(FS(c)->dir);
if(de == nil){
FS(c)->eod = 1;
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, &st) < 0) {
fprint(2, "dir: bad path %s\n", path);
continue;
}
r = fsdirconv(c, path, de->d_name, &st, va+i, count-i, 1);
if(r <= 0){
FS(c)->de = de;
break;
}
i += r;
FS(c)->offset += r;
}
qunlock(&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)
{
User *u;
if(owner && !iseve())
return;
kstrdup(&up->env->user, name);
qlock(&idl);
u = name2user(uidmap, name, newuname);
if(u == nil){
qunlock(&idl);
up->env->uid = -1;
up->env->gid = -1;
return;
}
up->env->uid = u->id;
up->env->gid = u->gid;
qunlock(&idl);
}
static User**
hashuser(User** tab, int id)
{
int i;
i = (id>>IDSHIFT) ^ id;
return &tab[i & IDMASK];
}
/*
* the caller of the following functions must hold QLock idl.
*/
/*
* we could keep separate maps of user and group names to Users to
* speed this up, but the reverse lookup currently isn't common (ie, change group by wstat and setid)
*/
static User*
name2user(User **tab, char *name, User* (*get)(char*))
{
int i;
User *u, **h;
static User *prevu;
static User **prevtab;
if(prevu != nil && prevtab == tab && strcmp(name, prevu->name) == 0)
return prevu; /* it's often the one we've just seen */
for(i=0; i<NID; i++)
for(u = tab[i]; u != nil; u = u->next)
if(strcmp(name, u->name) == 0) {
prevtab = tab;
prevu = u;
return u;
}
u = get(name);
if(u == nil)
return nil;
h = hashuser(tab, u->id);
u->next = *h;
*h = u;
prevtab = tab;
prevu = u;
return u;
}
static void
freeuser(User *u)
{
if(u != nil){
free(u->name);
free(u->mem);
free(u);
}
}
static User*
newuser(int id, int gid, char *name, int nmem)
{
User *u;
u = malloc(sizeof(*u));
if(u == nil)
return nil;
u->name = strdup(name);
if(u->name == nil){
free(u);
return nil;
}
u->nmem = nmem;
if(nmem){
u->mem = malloc(nmem*sizeof(*u->mem));
if(u->mem == nil){
free(u->name);
free(u);
return nil;
}
}else
u->mem = nil;
u->id = id;
u->gid = gid;
u->next = nil;
return u;
}
static User*
newuname(char *name)
{
struct passwd *p;
p = getpwnam(name);
if(p == nil)
return nil;
return newuser(p->pw_uid, p->pw_gid, name, 0);
}
static User*
newuid(int id)
{
struct passwd *p;
p = getpwuid(id);
if(p == nil)
return nil;
return newuser(p->pw_uid, p->pw_gid, p->pw_name, 0);
}
static User*
newgroup(struct group *g)
{
User *u, *gm;
int n, o;
if(g == nil)
return nil;
for(n=0; g->gr_mem[n] != nil; n++)
;
u = newuser(g->gr_gid, g->gr_gid, g->gr_name, n);
if(u == nil)
return nil;
o = 0;
for(n=0; g->gr_mem[n] != nil; n++){
gm = name2user(uidmap, g->gr_mem[n], newuname);
if(gm != nil)
u->mem[o++] = gm->id;
/* ignore names that don't map to IDs */
}
u->nmem = o;
return u;
}
static User*
newgid(int id)
{
return newgroup(getgrgid(id));
}
static User*
newgname(char *name)
{
return newgroup(getgrnam(name));
}
static User*
id2user(User **tab, int id, User* (*get)(int))
{
User *u, **h;
h = hashuser(tab, id);
for(u = *h; u != nil; u = u->next)
if(u->id == id)
return u;
u = get(id);
if(u == nil)
return nil;
u->next = *h;
*h = u;
return u;
}
static int
ingroup(int id, int gid)
{
int i;
User *g;
g = id2user(gidmap, gid, newgid);
if(g == nil || g->mem == nil)
return 0;
for(i = 0; i < g->nmem; i++)
if(g->mem[i] == id)
return 1;
return 0;
}
Dev fsdevtab = {
'U',
"fs",
devinit,
fsattach,
fswalk,
fsstat,
fsopen,
fscreate,
fsclose,
fsread,
devbread,
fswrite,
devbwrite,
fsremove,
fswstat
};