ref: a6e5d4bae6075c741a39fcba62a365d9dffaed93
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);
}