ref: 5140e43ad849d962d2e2825b739b6efa7c66b362
dir: /ext4srv.c/
#include <ext4.h> #include <ext4_inode.h> #include <fcall.h> #include <thread.h> #include <9p.h> #include <bio.h> #include "group.h" #include "common.h" #define MIN(a,b) ((a)<(b)?(a):(b)) int mainstacksize = 65536; typedef struct Aux Aux; struct Aux { Part *p; u32int uid; char *path; int doff; union { ext4_file *file; ext4_dir *dir; }; int type; }; enum { Adir, Afile, Root = 0, }; static Opts opts = { .group = nil, .cachewb = 0, .asroot = 0, .linkmode = Lhide, .fstype = -1, .blksz = 0, .label = nil, .inodesz = 256, .ninode = 0, }; static u8int zero[65536]; static char Eperm[] = "permission denied"; static char *srvname; static char * linkresolve(Aux *a, char *s, char **value) { char *q, buf[4096+1]; usize sz; int res; res = 0; if(opts.linkmode == Lresolve && (res = ext4_readlink(s, buf, sizeof(buf), &sz)) == 0){ if(sz == sizeof(buf)){ werrstr("readlink: %s: path too long", 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); free(q); cleanname(strchr(s+1, '/')); } }else{ if(res != 0) werrstr("readlink: %s: %s", s, errno2s(res)); 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(p & OTRUNC) p |= AWRITE; 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) e |= O_TRUNC; mode &= 3; 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){ 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)); r->fid->qid.path = a->p->qidmask.path | a->file->inode; r->fid->qid.vers = 0; r->fid->qid.type = 0; r->ofcall.qid = r->fid->qid; free(a->path); 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("inode: %s: %s", s, errno2s(r)); free(s); return -1; } t = ext4_inode_type(a->p->sb, &inode); if(opts.linkmode == Lhide && t == EXT4_INODE_MODE_SOFTLINK){ 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); dir->qid.type = 0; 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 || n != a->doff){ ext4_dir_entry_rewind(a->dir); a->doff = 0; } for(;;){ do{ if((e = ext4_dir_entry_next(a->dir)) == nil) return -1; }while(e->name == nil || strcmp((char*)e->name, ".") == 0 || strcmp((char*)e->name, "..") == 0); if(opts.linkmode == Lhide && e->inode_type == EXT4_DE_SYMLINK) continue; if(a->doff++ != n) continue; if(dirfill(dir, a, (char*)e->name) == 0) return 0; a->doff--; } } static void rread(Req *r) { usize n; int res; Aux *a; a = r->fid->aux; if(a->type == Adir && a->dir != nil){ dirread9p(r, dirgen, a); }else if(a->type == Afile && a->file != nil){ if(ext4_fseek(a->file, r->ifcall.offset, SEEK_SET) != 0) n = 0; else if((res = ext4_fread(a->file, r->ofcall.data, r->ifcall.count, &n)) != 0){ respond(r, errno2s(res)); return; } r->ofcall.count = n; } respond(r, nil); } static void rwrite(Req *r) { usize n; int res; Aux *a; a = r->fid->aux; if(a->type == Adir){ respond(r, "can't write to dir"); }else if(a->type == Afile){ while(ext4_fsize(a->file) < r->ifcall.offset){ ext4_fseek(a->file, 0, SEEK_END); if((res = ext4_fwrite(a->file, zero, MIN(r->ifcall.offset-ext4_fsize(a->file), sizeof(zero)), &n)) != 0) goto ext4error; } if((res = ext4_fseek(a->file, r->ifcall.offset, SEEK_SET)) != 0) goto ext4error; if((res = ext4_fwrite(a->file, r->ifcall.data, r->ifcall.count, &n)) != 0) goto ext4error; r->ofcall.count = n; respond(r, nil); } return; ext4error: respond(r, errno2s(res)); } 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 && ext4_dir_open(&dir, s) == 0){ for(empty = 1; empty;){ if((e = ext4_dir_entry_next(&dir)) == nil) break; empty = e->name == nil || 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) goto ext4error; res = ext4_ftruncate(&f, r->d.length); ext4_fclose(&f); if(res != 0) goto ext4error; } /* 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){ free(s); return "not a directory"; } dir = *a; dir.path = strchr(s+1, '/')+1; if(!haveperm(&dir, OEXEC, nil)){ free(s); return Eperm; } q = s; s = smprint("%s/%s", q, name); cleanname(strchr(s+1, '/')); 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; t = ext4_inode_type(a->p->sb, &inode); if(opts.linkmode == Lhide && t == EXT4_INODE_MODE_SOFTLINK){ r = ENOENT; goto error; } qid->type = 0; qid->path = a->p->qidmask.path | ino; qid->vers = ext4_inode_get_generation(&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; 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; 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 && a->dir != nil){ ext4_dir_close(a->dir); free(a->dir); }else if(a->type == Afile && a->file != nil){ ext4_fclose(a->file); free(a->file); } if(decref(a->p) < 1) closepart(a->p); free(a->path); free(a); } static int note(void *, char *s) { if(strncmp(s, "sys:", 4) != 0){ closeallparts(); close(0); return 1; } return 0; } static void cmdsrv(void *) { char s[32], *c, *a[4]; int f, p[2], n; Biobuf b; if(pipe(p) < 0) sysfatal("%r"); snprint(s, sizeof(s), "#s/%s.cmd", srvname); if((f = create(s, ORCLOSE|OWRITE, 0660)) < 0){ remove(s); if((f = create(s, ORCLOSE|OWRITE, 0660)) < 0) sysfatal("%r"); } if(fprint(f, "%d", p[0]) < 1) sysfatal("srv write"); dup(p[1], 0); close(p[1]); close(p[0]); Binit(&b, 0, OREAD); for(; (c = Brdstr(&b, '\n', 1)) != nil; free(c)){ if((n = tokenize(c, a, nelem(a))) < 1) continue; USED(n); if(strcmp(a[0], "stats") == 0 || strcmp(a[0], "df") == 0){ statallparts(); }else if(strcmp(a[0], "halt") == 0){ closeallparts(); close(0); threadexitsall(nil); }else if(strcmp(a[0], "sync") == 0){ syncallparts(); }else{ print("unknown command: %s\n", a[0]); } } } static void rstart(Srv *) { threadnotify(note, 1); proccreate(cmdsrv, nil, mainstacksize); } static void rend(Srv *) { closeallparts(); close(0); 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 resolve] [-s SRVNAME]\n", argv0); fprint(2, "mkfs: %s -M (2|3|4) [-L LABEL] [-b BLKSIZE] [-N NUMINODES] [-I INODESZ] DEVICE\n", argv0); threadexitsall("usage"); } static int linkmode(char *m) { if(strcmp(m, "resolve") == 0) return Lresolve; if(strcmp(m, "hide") == 0) return Lhide; usage(); return -1; } void threadmain(int argc, char **argv) { char *gr; vlong sz; int f; rfork(RFNOTEG); srvname = "ext4"; ARGBEGIN{ case 'D': chatty9p++; nomkfs: if(opts.fstype > 0) usage(); opts.fstype = 0; break; case 'C': opts.cachewb = 1; goto nomkfs; case 'l': opts.linkmode = linkmode(EARGF(usage())); goto nomkfs; case 's': srvname = EARGF(usage()); goto nomkfs; 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; goto nomkfs; case 'R': opts.asroot = 1; goto nomkfs; case 'M': if(!opts.fstype) usage(); opts.fstype = atoi(EARGF(usage())); if(opts.fstype < 2 || opts.fstype > 4) usage(); break; case 'b': opts.blksz = atoi(EARGF(usage())); if(opts.blksz != 1024 && opts.blksz != 2048 && opts.blksz != 4096) usage(); yesmkfs: if(opts.fstype < 1) usage(); break; case 'L': opts.label = EARGF(usage()); goto yesmkfs; case 'I': opts.inodesz = atoi(EARGF(usage())); if(opts.inodesz < 128 || ((opts.inodesz-1) & opts.inodesz) != 0) usage(); goto yesmkfs; case 'N': opts.ninode = atoi(EARGF(usage())); if(opts.ninode < 1) usage(); goto yesmkfs; default: usage(); }ARGEND if(opts.fstype > 1){ if(argc != 1) usage(); if(openpart(argv[0], &opts) == nil) sysfatal("%r"); closeallparts(); threadexitsall(nil); }else{ if(argc != 0) usage(); threadpostmountsrv(&fs, srvname, nil, 0); threadexits(nil); } }