ref: 7e6af6b00ee3034daa6a1a1e9c6cb97c94b57196
dir: /ext4srv.c/
#include <ext4.h> #include <ext4_inode.h> #include <fcall.h> #include <thread.h> #include <9p.h> #include "group.h" #include "common.h" int mainstacksize = 65536; typedef struct Aux Aux; struct Aux { Part *p; u32int uid; char *path; union { ext4_file *file; ext4_dir *dir; }; int type; }; enum { Adir, Afile, Root = 0, }; static Opts opts; static char Eperm[] = "permission denied"; static char * linkresolve(Aux *a, char *s, char **value) { char *q, buf[4096+1]; ulong sz; if(ext4_readlink(s, buf, sizeof(buf), &sz) == 0){ if(sz == sizeof(buf)){ werrstr("readlink: %s: path too long", s); free(s); return nil; } if(opts.linkmode == Lhide){ werrstr("linkresolve: %s: links are hidden", s); free(s); return nil; } buf[sz] = 0; if(value != nil) *value = strdup(buf); cleanname(buf); if(buf[0] == '/'){ free(s); s = smprint("%M%s", a->p, buf); }else{ q = strrchr(s, '/'); *q = 0; q = s; s = smprint("%s/%s", q, buf); cleanname(s); free(q); } }else if(value != nil){ *value = nil; } return s; } static char * fullpath(Aux *a) { return linkresolve(a, smprint("%M/%s", a->p, a->path), nil); } static int haveperm(Aux *a, int p, struct ext4_inode *inodeout) { struct ext4_inode inode; u32int ino, id; int m, fm, r; Group *g; char *s; switch(p & 3){ case OREAD: p = AREAD; break; case OWRITE: p = AWRITE; break; case ORDWR: p = AREAD|AWRITE; break; case OEXEC: p = AEXEC; break; default: return 0; } if((s = fullpath(a)) == nil) return -1; if((r = ext4_raw_inode_fill(s, &ino, &inode)) != 0){ werrstr("%s: %s", s, errno2s(r)); free(s); return -1; } free(s); if(inodeout != nil) memmove(inodeout, &inode, sizeof(inode)); fm = ext4_inode_get_mode(a->p->sb, &inode); /* other */ m = fm & 7; if((p & m) == p) return 1; /* owner */ id = ext4_inode_get_uid(&inode); if(a->uid == Root || ((g = findgroupid(&a->p->groups, id)) != nil && ingroup(g, a->uid))){ m |= (fm >> 6) & 7; if((p & m) == p) return 1; } /* group */ id = ext4_inode_get_gid(&inode); if(a->uid == Root || ((g = findgroupid(&a->p->groups, id)) != nil && ingroup(g, a->uid))){ m |= (fm >> 3) & 7; if((p & m) == p) return 1; } return 0; } static void rattach(Req *r) { char err[ERRMAX]; Aux *a; if((a = calloc(1, sizeof(*a))) == nil) respond(r, "memory"); else if((a->p = openpart(r->ifcall.aname, &opts)) == nil){ free(a); rerrstr(err, sizeof(err)); respond(r, err); }else{ if(opts.asroot || findgroup(&a->p->groups, r->ifcall.uname, &a->uid) == nil) a->uid = Root; incref(a->p); a->type = Adir; a->path = strdup(""); r->ofcall.qid = a->p->qidmask; r->fid->qid = a->p->qidmask; r->fid->aux = a; respond(r, nil); } } static u32int toext4mode(u32int mode, u32int perm, int creat) { u32int e; e = 0; mode &= ~OCEXEC; if(mode & OTRUNC){ mode &= ~OTRUNC; e |= O_TRUNC; } if(mode == OWRITE) e |= O_WRONLY; else if(mode == ORDWR) e |= O_RDWR; if(creat) e |= O_CREAT; if(perm & DMEXCL) e |= O_EXCL; if(perm & DMAPPEND) e |= O_APPEND; return e; } static void ropen(Req *r) { char *path; int res; Aux *a; a = r->fid->aux; switch(a->type){ case Adir: if(r->ifcall.mode != OREAD || !haveperm(a, r->ifcall.mode, nil)){ respond(r, Eperm); return; } if(a->dir != nil){ respond(r, "double open"); return; } if((a->dir = malloc(sizeof(*a->dir))) == nil) goto Nomem; if((path = smprint("%M/%s", a->p, a->path)) == nil){ free(a->dir); a->dir = nil; goto Nomem; } res = ext4_dir_open(a->dir, path); free(path); if(res != 0){ free(a->dir); a->dir = nil; respond(r, errno2s(res)); return; } break; case Afile: if(!haveperm(a, r->ifcall.mode, nil)){ respond(r, Eperm); return; } if(a->file != nil){ respond(r, "double open"); return; } if((a->file = malloc(sizeof(*a->file))) == nil) goto Nomem; if((path = smprint("%M/%s", a->p, a->path)) == nil){ free(a->file); a->file = nil; goto Nomem; } res = ext4_fopen2(a->file, path, toext4mode(r->ifcall.mode, 0, 0)); free(path); if(res != 0){ free(a->file); a->file = nil; respond(r, errno2s(res)); return; } break; Nomem: respond(r, "memory"); return; } r->ofcall.iounit = 0; respond(r, nil); } static void rcreate(Req *r) { struct ext4_inode inode; u32int perm, dirperm, t; char *s, *err, *q; int mkdir, res; Aux *a; a = r->fid->aux; err = nil; s = nil; if(a->file != nil || a->dir != nil){ err = "double create"; goto error; } if(!haveperm(a, OWRITE, &inode)){ err = Eperm; goto error; } /* first make sure this is a directory */ t = ext4_inode_type(a->p->sb, &inode); if((t & EXT4_INODE_MODE_DIRECTORY) == 0){ err = "create in non-directory"; goto error; } if((s = fullpath(a)) == nil) goto error; ext4_mode_get(s, &dirperm); /* check if the entry already exists */ if((q = smprint("%s/%s", s, r->ifcall.name)) == nil){ Nomem: err = "memory"; goto error; } free(s); s = q; if(ext4_inode_exist(s, EXT4_DE_UNKNOWN) == 0){ err = "already exists"; goto error; } mkdir = r->ifcall.perm & DMDIR; perm = mkdir ? 0666 : 0777; perm = r->ifcall.perm & (~perm | (dirperm & perm)); if(mkdir){ a->type = Adir; if((res = ext4_dir_mk(s)) != 0) goto ext4error; if((a->dir = malloc(sizeof(*a->dir))) == nil) goto Nomem; if((res = ext4_dir_open(a->dir, s)) != 0){ free(a->dir); a->dir = nil; goto ext4errorrm; } }else{ a->type = Afile; if((a->file = malloc(sizeof(*a->file))) == nil) goto Nomem; if((res = ext4_fopen2(a->file, s, toext4mode(r->ifcall.mode, perm, 1))) != 0){ free(a->file); a->file = nil; goto ext4error; } } if((res = ext4_mode_set(s, perm)) != 0) goto ext4errorrm; ext4_owner_set(s, a->uid, ext4_inode_get_gid(&inode)); a->path = strdup(strchr(s+1, '/')+1); free(s); r->ofcall.iounit = 0; respond(r, nil); return; ext4errorrm: if(mkdir) ext4_dir_rm(s); else ext4_fremove(s); ext4error: err = errno2s(res); error: free(s); respond(r, err); } static int dirfill(Dir *dir, Aux *a, char *path) { struct ext4_inode inode; u32int t, ino, id; char tmp[16]; char *s, *q; Group *g; int r; memset(dir, 0, sizeof(*dir)); if(path == nil){ path = a->path; s = smprint("%M/%s", a->p, a->path); }else{ if(*a->path == 0 && *path == 0) path = "/"; s = smprint("%M%s%s/%s", a->p, *a->path ? "/" : "", a->path, path); } if((s = linkresolve(a, s, nil)) == nil) return -1; if((r = ext4_raw_inode_fill(s, &ino, &inode)) != 0){ werrstr("%s: %s", s, errno2s(r)); free(s); return -1; } dir->mode = ext4_inode_get_mode(a->p->sb, &inode) & 0x1ff; dir->qid.path = a->p->qidmask.path | ino; dir->qid.vers = ext4_inode_get_generation(&inode); t = ext4_inode_type(a->p->sb, &inode); if(t & EXT4_INODE_MODE_DIRECTORY){ dir->qid.type |= QTDIR; dir->mode |= DMDIR; }else dir->length = ext4_inode_get_size(a->p->sb, &inode); if(ext4_inode_get_flags(&inode) & EXT4_INODE_FLAG_APPEND){ dir->qid.type |= QTAPPEND; dir->mode |= DMAPPEND; } if((q = strrchr(path, '/')) != nil) path = q+1; dir->name = estrdup9p(path); dir->atime = ext4_inode_get_access_time(&inode); dir->mtime = ext4_inode_get_modif_time(&inode); sprint(tmp, "%ud", id = ext4_inode_get_uid(&inode)); dir->uid = estrdup9p((g = findgroupid(&a->p->groups, id)) != nil ? g->name : tmp); sprint(tmp, "%ud", id = ext4_inode_get_gid(&inode)); dir->gid = estrdup9p((g = findgroupid(&a->p->groups, id)) != nil ? g->name : tmp); free(s); return 0; } static int dirgen(int n, Dir *dir, void *aux) { const ext4_direntry *e; Aux *a; a = aux; if(n == 0) ext4_dir_entry_rewind(a->dir); for(;;){ do{ if((e = ext4_dir_entry_next(a->dir)) == nil) return -1; }while(strcmp((char*)e->name, ".") == 0 || strcmp((char*)e->name, "..") == 0); if(dirfill(dir, a, (char*)e->name) == 0) return 0; } } static void rread(Req *r) { ulong n; Aux *a; a = r->fid->aux; if(a->type == Adir && a->dir != nil){ dirread9p(r, dirgen, a); respond(r, nil); return; }else if(a->type == Afile && a->file != nil){ ext4_fseek(a->file, r->ifcall.offset, 0); if(ext4_fread(a->file, r->ofcall.data, r->ifcall.count, &n) != 0){ respond(r, "i/o error"); }else{ r->ofcall.count = n; respond(r, nil); } return; } respond(r, "eh?"); } static void rwrite(Req *r) { ulong n; int res; Aux *a; a = r->fid->aux; if(a->type == Adir){ respond(r, "can't write to dir"); return; }else if(a->type == Afile){ ext4_fseek(a->file, r->ifcall.offset, SEEK_SET); if((res = ext4_fwrite(a->file, r->ifcall.data, r->ifcall.count, &n)) != 0) respond(r, errno2s(res)); else{ r->ofcall.count = n; respond(r, nil); } return; } respond(r, "eh?"); } static void rremove(Req *r) { struct ext4_inode inode; const ext4_direntry *e; u32int ino, t, empty; char *s, *err; ext4_dir dir; Group *g; int res; Aux *a; a = r->fid->aux; err = nil; /* do not resolve links here as most likely it's JUST the link we want to remove */ if((s = smprint("%M/%s", a->p, a->path)) == nil){ err = "memory"; goto end; } if((res = ext4_raw_inode_fill(s, &ino, &inode)) != 0){ ext4error: err = errno2s(res); goto end; } if(a->uid == Root || ((g = findgroupid(&a->p->groups, ext4_inode_get_uid(&inode))) != nil && g->id == a->uid)){ t = ext4_inode_type(a->p->sb, &inode); if((t & EXT4_INODE_MODE_DIRECTORY) != 0 && ext4_dir_open(&dir, s) == 0){ do{ e = ext4_dir_entry_next(&dir); empty = e == nil; if(empty) break; }while(strcmp((char*)e->name, ".") == 0 || strcmp((char*)e->name, "..") == 0); ext4_dir_close(&dir); if(!empty) err = "directory not empty"; else if((res = ext4_dir_rm(s)) != 0) goto ext4error; }else if((res = ext4_fremove(s)) != 0) goto ext4error; }else{ err = Eperm; } end: free(s); respond(r, err); } static void rstat(Req *r) { char err[ERRMAX]; Aux *a; a = r->fid->aux; if(dirfill(&r->d, a, nil) != 0){ rerrstr(err, sizeof(err)); respond(r, err); }else{ respond(r, nil); } } static void rwstat(Req *r) { int res, isdir, wrperm, isowner; char *old, *new, *err, *s; struct ext4_inode inode; u32int uid, gid; ext4_file f; Aux *a, o; Group *g; a = r->fid->aux; old = nil; new = nil; err = nil; /* can't do anything to root, can't change the owner */ if(a->path[0] == 0 || (r->d.uid != nil && r->d.uid[0] != 0)){ err = Eperm; goto error; } if((old = smprint("%M/%s", a->p, a->path)) == nil){ err = "memory"; goto error; } new = old; wrperm = haveperm(a, OWRITE, &inode); uid = ext4_inode_get_uid(&inode); isowner = a->uid == Root || a->uid == uid; /* permission to truncate */ isdir = ext4_inode_type(a->p->sb, &inode) & EXT4_INODE_MODE_DIRECTORY; if(r->d.length >= 0 && (!wrperm || isdir || !ext4_inode_can_truncate(a->p->sb, &inode))){ err = Eperm; goto error; } /* permission to rename */ if(r->d.name != nil && r->d.name[0] != 0){ if((s = strrchr(old, '/')) != nil) *s = 0; if((new = smprint("%M/%s%s%s", a->p, s ? old : "", s ? "/" : "", r->d.name)) == nil){ err = "memory"; goto error; } /* check parent write permission */ o = *a; o.path = old; if(!haveperm(&o, OWRITE, nil)){ err = Eperm; goto error; } *s = '/'; } /* permission to change mode */ if(r->d.mode != ~0){ /* has to be owner and can't change dir bit */ if(!isowner || (!!isdir != !!(r->d.mode & DMDIR))){ err = Eperm; goto error; } } /* permission to change mtime */ if(r->d.mtime != ~0 && !isowner){ err = Eperm; goto error; } /* permission to change gid */ if(r->d.gid != nil && r->d.gid[0] != 0){ /* has to be the owner, group has to exist, must be in that group */ if(a->uid == Root || (!isowner || (g = findgroup(&a->p->groups, r->d.gid, &gid)) == nil || !ingroup(g, a->uid))){ err = Eperm; goto error; } } /* done checking permissions, now apply all the changes and hope it all works */ /* rename */ if(r->d.name != nil && r->d.name[0] != 0){ if((res = ext4_frename(old, new)) != 0) goto ext4error; free(old); old = new; new = nil; free(a->path); a->path = strdup(strchr(old+1, '/')+1); } /* truncate */ if(r->d.length >= 0){ if((res = ext4_fopen2(&f, new, toext4mode(OWRITE, 0, 0))) != 0 || (res = ext4_ftruncate(&f, r->d.length)) != 0){ goto ext4error; } ext4_fclose(&f); } /* mode */ if(r->d.mode != ~0 && (res = ext4_mode_set(new, r->d.mode & 0x1ff)) != 0) goto ext4error; /* mtime */ if(r->d.mtime != ~0 && (res = ext4_mtime_set(new, r->d.mtime)) != 0) goto ext4error; /* gid */ if(r->d.gid != nil && r->d.gid[0] != 0 && (res = ext4_owner_set(new, uid, gid)) != 0) goto ext4error; goto error; /* not really an error */ ext4error: err = errno2s(res); error: free(old); if(new != old) free(new); respond(r, err); } static char * rwalk1(Fid *fid, char *name, Qid *qid) { static char errbuf[ERRMAX]; struct ext4_inode inode; u32int ino, t; Aux *a, dir; char *s, *q; int r; a = fid->aux; /* try walking to the real file first */ if((s = fullpath(a)) == nil){ /* else try link itself. might want to just remove it anyway */ if((s = smprint("%M/%s", a->p, a->path)) == nil){ r = ENOMEM; goto error; } } if((r = ext4_raw_inode_fill(s, &ino, &inode)) != 0) goto error; t = ext4_inode_type(a->p->sb, &inode); if((t & EXT4_INODE_MODE_DIRECTORY) == 0){ free(s); return "not a directory"; } dir = *a; dir.path = s; if(!haveperm(&dir, OEXEC, nil)){ free(s); return Eperm; } q = s; s = smprint("%s/%s", q, name); cleanname(s); free(q); if((s = linkresolve(a, s, nil)) == nil){ rerrstr(errbuf, sizeof(errbuf)); return errbuf; } if((r = ext4_raw_inode_fill(s, &ino, &inode)) != 0) goto error; qid->type = 0; qid->path = a->p->qidmask.path | ino; qid->vers = ext4_inode_get_generation(&inode); t = ext4_inode_type(a->p->sb, &inode); if(t & EXT4_INODE_MODE_DIRECTORY){ qid->type |= QTDIR; a->type = Adir; }else a->type = Afile; if(ext4_inode_get_flags(&inode) & EXT4_INODE_FLAG_APPEND) qid->type |= QTAPPEND; free(a->path); a->path = strdup(strchr(s+1, '/')+1); free(s); fid->qid = *qid; return nil; error: free(s); return errno2s(r); } static char * rclone(Fid *oldfid, Fid *newfid) { Aux *a, *c; a = oldfid->aux; switch(a->type){ case Afile: case Adir: if((c = calloc(1, sizeof(*c))) == nil) return "memory"; memmove(c, a, sizeof(*c)); c->path = strdup(a->path); c->file = nil; c->dir = nil; break; default: return "unknown aux type"; } incref(c->p); newfid->aux = c; return nil; } static void rdestroyfid(Fid *fid) { Aux *a; a = fid->aux; if(a == nil) return; fid->aux = nil; if(a->type == Adir){ if(a->dir != nil){ ext4_dir_close(a->dir); free(a->dir); } }else if(a->type == Afile){ if(a->file != nil){ ext4_fclose(a->file); free(a->file); } }else{ /* that would be a BUG */ return; } if(decref(a->p) == 0) closepart(a->p); free(a->path); free(a); } static int note(void *, char *s) { if(strncmp(s, "sys:", 4) != 0){ closeallparts(); return 1; } return 0; } static void rstart(Srv *) { threadnotify(note, 1); } static void rend(Srv *) { closeallparts(); threadexitsall(nil); } static Srv fs = { .attach = rattach, .open = ropen, .create = rcreate, .read = rread, .write = rwrite, .remove = rremove, .stat = rstat, .wstat = rwstat, .walk1 = rwalk1, .clone = rclone, .destroyfid = rdestroyfid, .start = rstart, .end = rend, }; static void usage(void) { fprint(2, "usage: %s [-C] [-R] [-g GROUPFILE] [-l hide] [-s SRVNAME]\n", argv0); threadexitsall("usage"); } static int linkmode(char *m) { if(strcmp(m, "hide") == 0) return Lhide; usage(); return -1; } void threadmain(int argc, char **argv) { char *srv, *gr; vlong sz; int f; srv = "ext4"; ARGBEGIN{ case 'D': chatty9p++; break; case 'C': opts.cachewb = 1; break; case 'l': opts.linkmode = linkmode(EARGF(usage())); break; case 's': srv = EARGF(usage()); break; case 'g': gr = EARGF(usage()); if((f = open(gr, OREAD)) < 0) sysfatal("%r"); if((sz = seek(f, 0, 2)) < 0) sysfatal("%s: invalid group file", gr); if((opts.group = malloc(sz+1)) == nil) sysfatal("memory"); seek(f, 0, 0); if(readn(f, opts.group, sz) != sz) sysfatal("%s: read failed", gr); close(f); opts.group[sz] = 0; break; case 'R': opts.asroot = 1; break; }ARGEND if(argc != 0) usage(); threadpostmountsrv(&fs, srv, nil, 0); threadexits(nil); }