ref: 1b09060f468f530af2f4ef75441a40084e912e54
dir: /sys/src/cmd/ext4srv/ext4srv.c/
#include "ext4_config.h" #include "ext4.h" #include <fcall.h> #include <thread.h> #include <9p.h> #include <bio.h> #include "ext4_inode.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; bool rclose; }; enum { Adir, Afile, Root = 0, }; static Opts opts = { .label = "", }; static u8int zero[65536]; static char *srvname; static char *device; static Part *devpart; static int haveperm(Aux *a, int p, struct ext4_inode *inodeout) { struct ext4_mountpoint *mp; struct ext4_inode inode; u32int ino, id; int m, fm; Group *g; switch(p & 3){ case OREAD: p = AREAD; break; case ORCLOSE: case OWRITE: p = AWRITE; break; case ORDWR: p = AREAD|AWRITE; break; case OEXEC: p = AEXEC; break; default: return 0; } if(p & OTRUNC) p |= AWRITE; mp = &a->p->mp; if(ext4_raw_inode_fill(mp, a->path, &ino, &inode) != 0){ werrstr("%s: %r", a->path); return -1; } 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) { Aux *a; if((a = calloc(1, sizeof(*a))) == nil) respond(r, "memory"); if(r->ifcall.aname && *r->ifcall.aname){ if((a->p = openpart(r->ifcall.aname, &opts)) == nil){ free(a); responderror(r); return; } }else if((a->p = devpart) == nil){ respond(r, "no file specified"); return; } if(opts.asroot || findgroup(&a->p->groups, r->ifcall.uname, &a->uid) == nil) a->uid = Root; incref(a->p); a->type = Adir; a->path = estrdup9p(""); 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) { struct ext4_mountpoint *mp; char *path; int res; Aux *a; a = r->fid->aux; mp = &a->p->mp; 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 = estrdup9p(a->path)) == nil){ free(a->dir); a->dir = nil; goto Nomem; } res = ext4_dir_open(mp, a->dir, path); free(path); if(res != 0){ free(a->dir); a->dir = nil; responderror(r); 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 = estrdup9p(a->path)) == nil){ free(a->file); a->file = nil; goto Nomem; } res = ext4_fopen2(mp, a->file, path, toext4mode(r->ifcall.mode, 0, 0)); free(path); if(res != 0){ free(a->file); a->file = nil; responderror(r); return; } a->rclose = (r->ifcall.mode & ORCLOSE) != 0; break; Nomem: respond(r, "memory"); return; } r->ofcall.iounit = 0; respond(r, nil); } static void rcreate(Req *r) { u32int perm, dirperm, t, iflags; struct ext4_mountpoint *mp; struct ext4_inode inode; int mkdir, isroot; long tm; char *s; Aux *a; a = r->fid->aux; mp = &a->p->mp; s = nil; if(a->file != nil || a->dir != nil){ werrstr("double create"); goto error; } if(!haveperm(a, OWRITE, &inode)){ werrstr(Eperm); goto error; } /* first make sure this is a directory */ isroot = r->fid->qid.path == a->p->qidmask.path; if(!isroot){ t = ext4_inode_type(a->p->sb, &inode); if(t != EXT4_INODE_MODE_DIRECTORY){ werrstr("create in non-directory"); goto error; } } ext4_mode_get(mp, a->path, &dirperm); /* check if the entry already exists */ s = isroot ? estrdup9p(r->ifcall.name) : smprint("%s/%s", a->path, r->ifcall.name); if(ext4_inode_exist(mp, s, EXT4_DE_UNKNOWN) == 0){ werrstr("file 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(ext4_dir_mk(mp, s) < 0) goto error; a->dir = emalloc9p(sizeof(*a->dir)); if(ext4_dir_open(mp, a->dir, s) < 0){ free(a->dir); a->dir = nil; goto ext4errorrm; } }else{ a->type = Afile; a->file = emalloc9p(sizeof(*a->file)); if(ext4_fopen2(mp, a->file, s, toext4mode(r->ifcall.mode, perm, 1)) < 0){ free(a->file); a->file = nil; goto error; } } iflags = 0; if(r->ifcall.perm & DMAPPEND) iflags |= EXT4_INODE_FLAG_APPEND; if(r->ifcall.perm & DMTMP) iflags |= EXT4_INODE_FLAG_NODUMP; if(ext4_mode_set(mp, s, perm, iflags) < 0) goto ext4errorrm; ext4_owner_set(mp, s, a->uid, a->uid); tm = time(nil); ext4_mtime_set(mp, s, tm); ext4_ctime_set(mp, s, tm); 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 = s; r->ofcall.iounit = 0; respond(r, nil); return; ext4errorrm: if(mkdir) ext4_dir_rm(mp, s); else ext4_fremove(mp, s); error: free(s); responderror(r); } static int dirfill(Dir *dir, Aux *a, char *path) { struct ext4_mountpoint *mp; u32int t, ino, id, iflags; struct ext4_inode inode; char tmp[16]; Group *g; char *s; int r; memset(dir, 0, sizeof(*dir)); mp = &a->p->mp; if(path == nil){ r = ext4_raw_inode_fill(mp, a->path, &ino, &inode); }else{ s = smprint("%s%s%s", a->path, *a->path ? "/" : "", path); r = ext4_raw_inode_fill(mp, s, &ino, &inode); free(s); } if(r < 0){ werrstr("inode: %s: %r", path ? path : "/"); return -1; } dir->mode = ext4_inode_get_mode(a->p->sb, &inode) & 0777; dir->qid.path = a->p->qidmask.path | ino; dir->qid.vers = ext4_inode_get_generation(&inode); dir->qid.type = 0; 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); iflags = ext4_inode_get_flags(&inode); if(iflags & EXT4_INODE_FLAG_APPEND){ dir->qid.type |= QTAPPEND; dir->mode |= DMAPPEND; } if(iflags & EXT4_INODE_FLAG_NODUMP){ dir->qid.type |= QTTMP; dir->mode |= DMTMP; } if(path != nil && (s = strrchr(path, '/')) != nil) path = s+1; dir->name = estrdup9p(path != nil ? 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); 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(e->inode_type != EXT4_DE_REG_FILE && e->inode_type != EXT4_DE_DIR) 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; 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, 0) != 0) n = 0; else if(ext4_fread(a->file, r->ofcall.data, r->ifcall.count, &n) < 0){ responderror(r); return; } r->ofcall.count = n; } respond(r, nil); } static void rwrite(Req *r) { usize n, sz; Aux *a; a = r->fid->aux; if(a->type == Afile){ while(ext4_fsize(a->file) < r->ifcall.offset){ ext4_fseek(a->file, 0, 2); sz = MIN(r->ifcall.offset-ext4_fsize(a->file), sizeof(zero)); if(ext4_fwrite(a->file, zero, sz, &n) < 0) goto error; } if((a->file->flags & O_APPEND) == 0){ if(ext4_fseek(a->file, r->ifcall.offset, 0) < 0) goto error; if(ext4_ftell(a->file) != r->ifcall.offset){ werrstr("could not seek"); goto error; } } if(ext4_fwrite(a->file, r->ifcall.data, r->ifcall.count, &n) < 0) goto error; assert(r->ifcall.count >= n); r->ofcall.count = n; respond(r, nil); return; }else{ werrstr(Eperm); } error: responderror(r); } static void rremove(Req *r) { struct ext4_mountpoint *mp; struct ext4_inode inode; const ext4_direntry *e; u32int ino, t, empty; ext4_dir dir; Group *g; Aux *a; a = r->fid->aux; mp = &a->p->mp; /* do not resolve links here as most likely it's JUST the link we want to remove */ if(ext4_raw_inode_fill(mp, a->path, &ino, &inode) < 0) goto error; 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(mp, &dir, a->path) == 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){ werrstr("directory not empty"); goto error; }else if(ext4_dir_rm(mp, a->path) < 0) goto error; }else if(ext4_fremove(mp, a->path) < 0) goto error; }else{ werrstr(Eperm); goto error; } respond(r, nil); return; error: responderror(r); } static void rstat(Req *r) { Aux *a; a = r->fid->aux; if(dirfill(&r->d, a, nil) != 0) responderror(r); else respond(r, nil); } static void rwstat(Req *r) { int res, isdir, wrperm, isowner, n; struct ext4_mountpoint *mp; struct ext4_inode inode; u32int uid, gid, iflags; ext4_file f; Aux *a, o; Group *g; char *s; a = r->fid->aux; mp = &a->p->mp; /* can't do anything to root, can't change the owner */ if(*a->path == 0 || (r->d.uid != nil && r->d.uid[0] != 0)){ werrstr(Eperm); goto error; } 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)){ werrstr(Eperm); goto error; } /* 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))){ werrstr(Eperm); goto error; } } /* permission to change mtime */ if(r->d.mtime != ~0 && !isowner){ werrstr(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(!isowner || (g = findgroup(&a->p->groups, r->d.gid, &gid)) == nil || !ingroup(g, a->uid)){ werrstr(Eperm); goto error; } } /* check for permission to rename and do the rename */ if(r->d.name != nil && r->d.name[0] != 0){ /* check parent write permission */ o = *a; o.path = a->path; if(!haveperm(&o, OWRITE, nil)){ werrstr(Eperm); goto error; } if((s = strrchr(a->path, '/')) != nil){ n = s - a->path; s = emalloc9p(n + 1 + strlen(r->d.name) + 1); memmove(s, a->path, n); s[n++] = '/'; strcpy(s+n, r->d.name); }else{ s = estrdup9p(r->d.name); } if(ext4_frename(mp, a->path, s) < 0){ free(s); goto error; } free(a->path); a->path = s; } /* truncate */ if(r->d.length >= 0 && ext4_inode_can_truncate(a->p->sb, &inode)){ if(ext4_fopen2(mp, &f, a->path, toext4mode(OWRITE, 0, 0)) < 0) goto error; res = ext4_ftruncate(&f, r->d.length); ext4_fclose(&f); if(res != 0) goto error; } /* mode */ if(r->d.mode != ~0){ iflags = 0; if(r->d.mode & DMAPPEND) iflags |= EXT4_INODE_FLAG_APPEND; if(r->d.mode & DMTMP) iflags |= EXT4_INODE_FLAG_NODUMP; if(ext4_mode_set(mp, a->path, r->d.mode & 0777, iflags) < 0) goto error; } /* mtime */ if(r->d.mtime != ~0 && ext4_mtime_set(mp, a->path, r->d.mtime) < 0) goto error; /* gid */ if(r->d.gid != nil && r->d.gid[0] != 0 && ext4_owner_set(mp, a->path, uid, gid) < 0) goto error; /* inode changed - update the time */ ext4_ctime_set(mp, a->path, time(nil)); respond(r, nil); return; error: responderror(r); } static char * rwalk1(Fid *fid, char *name, Qid *qid) { struct ext4_mountpoint *mp; static char errbuf[ERRMAX]; struct ext4_inode inode; u32int ino, t, iflags; Aux *a, dir; int isroot; char *s; a = fid->aux; mp = &a->p->mp; isroot = fid->qid.path == a->p->qidmask.path; s = nil; if(isroot && strcmp(name, "..") == 0){ *qid = a->p->qidmask; fid->qid = a->p->qidmask; return nil; } if(ext4_raw_inode_fill(mp, a->path, &ino, &inode) < 0){ err: free(s); rerrstr(errbuf, sizeof(errbuf)); return errbuf; } t = ext4_inode_type(a->p->sb, &inode); if(t != EXT4_INODE_MODE_DIRECTORY) return "not a directory"; dir = *a; dir.path = a->path; if(!haveperm(&dir, OEXEC, nil)) return Eperm; s = isroot ? estrdup9p(name) : smprint("%s/%s", a->path, name); cleanname(s); if(s[0] == '.' && s[1] == 0) /* special case - root */ *s = 0; if(ext4_raw_inode_fill(mp, s, &ino, &inode) < 0) goto err; t = ext4_inode_type(a->p->sb, &inode); if(t != EXT4_INODE_MODE_FILE && t != EXT4_INODE_MODE_DIRECTORY){ free(s); return "directory entry not found"; } 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; iflags = ext4_inode_get_flags(&inode); if(iflags & EXT4_INODE_FLAG_APPEND) qid->type |= QTAPPEND; if(iflags & EXT4_INODE_FLAG_NODUMP) qid->type |= QTTMP; free(a->path); a->path = s; fid->qid = *qid; return nil; } static char * rclone(Fid *oldfid, Fid *newfid) { Aux *a, *c; a = oldfid->aux; if((c = malloc(sizeof(*c))) == nil) return "memory"; memmove(c, a, sizeof(*c)); c->path = estrdup9p(a->path); c->file = nil; c->dir = nil; c->rclose = false; 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(a->rclose) ext4_fremove(&a->p->mp, a->path); } 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 [-Ss] [-f file] [-g groupfile] [-n srvname] [-r (2|3|4)]" " [-b blksize] [-I inodesize] [-L label]\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char **argv) { char *gr; vlong sz; int f, stdio; rfork(RFNOTEG); stdio = 0; device = nil; ARGBEGIN{ case 'D': chatty9p++; break; case 'd': ext4_dmask_set(strtoul(EARGF(usage()), nil, 0)); break; case 'f': if(device != nil) usage(); device = EARGF(usage()); break; case 'g': gr = EARGF(usage()); if((f = open(gr, OREAD)) < 0) sysfatal("%r"); sz = seek(f, 0, 2); if(sz < 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 'n': if(stdio != 0) usage(); srvname = EARGF(usage()); break; case 's': stdio = 1; if(srvname != nil) usage(); break; case 'S': opts.asroot = 1; break; case 'b': opts.blksz = atoi(EARGF(usage())); if(opts.blksz != 1024 && opts.blksz != 2048 && opts.blksz != 4096) usage(); break; case 'I': opts.inodesz = atoi(EARGF(usage())); if(opts.inodesz < 128 || ((opts.inodesz-1) & opts.inodesz) != 0) usage(); break; case 'L': opts.label = EARGF(usage()); break; case 'r': if(opts.ream > 0) usage(); opts.ream = atoi(EARGF(usage())); if(opts.ream < 2 || opts.ream > 4) usage(); break; default: usage(); }ARGEND if(argc != 0) usage(); if(device == nil && opts.ream > 1) usage(); if(device != nil && (devpart = openpart(device, &opts)) == nil) sysfatal("%r"); if(stdio){ fs.infd = 0; fs.outfd = 1; threadsrv(&fs); }else{ if(srvname == nil) srvname = "ext4"; threadpostsrv(&fs, srvname); } threadexits(nil); }