ref: efbec63da9c094e0908047352d1d5480dfbc4b97
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 *
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 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);
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(findgroup(&a->p->groups, r->ifcall.uname, &a->uid) == nil)
a->uid = Root; /* FIXME need external mapping */
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)
{
Aux *a;
char *path;
int res;
a = r->fid->aux;
switch(a->type){
case Adir:
if(r->ifcall.mode != OREAD || !haveperm(a, r->ifcall.mode)){
respond(r, "permission denied");
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)){
respond(r, "permission denied");
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;
}
respond(r, nil);
}
static void
rcreate(Req *r)
{
respond(r, "nope");
}
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);
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)
{
Aux *a;
ulong n;
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)
{
Aux *a;
ulong n;
int res;
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 = "permission denied";
}
end:
free(s);
respond(r, err);
}
static void
rstat(Req *r)
{
Aux *a;
char err[ERRMAX];
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)
{
respond(r, "nope");
}
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)){
free(s);
return "permission denied";
}
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] [-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;
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;
}ARGEND
if(argc != 0)
usage();
threadpostmountsrv(&fs, srv, nil, 0);
threadexits(nil);
}