jehanne/sys/src/cmd/hjfs/fs2.c

806 lines
16 KiB
C

/* Copyright (c) 20XX 9front
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <u.h>
#include <lib9.h>
#include <thread.h>
#include <9P2000.h>
#include "dat.h"
#include "fns.h"
Chan *
chanattach(Fs *fs, int flags)
{
Chan *ch;
ch = emalloc(sizeof(*ch));
ch->fs = fs;
ch->flags = flags;
ch->loc = cloneloc(fs, (flags & CHFDUMP) != 0 ? fs->dumprootloc : fs->rootloc);
return ch;
}
Chan *
chanclone(Chan *ch)
{
Chan *d;
chbegin(ch);
d = emalloc(sizeof(*d));
d->fs = ch->fs;
d->flags = ch->flags;
d->uid = ch->uid;
d->loc = cloneloc(ch->fs, ch->loc);
chend(ch);
return d;
}
int
chanwalk(Chan *ch, char *name)
{
Buf *b;
Dentry *d;
Loc *l;
FLoc f;
if(name == nil || name[0] == 0 || name[0] == '.' && name[1] == 0)
return 1;
chbegin(ch);
if(ch->open != 0){
werrstr(Einval);
chend(ch);
return -1;
}
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil){
chend(ch);
return -1;
}
d = getdent(ch->loc, b);
if(d == nil)
goto error;
if((d->type & QTDIR) == 0){
werrstr(Enotadir);
goto error;
}
if(!permcheck(ch->fs, d, ch->uid, OEXEC)){
werrstr(Eperm);
goto error;
}
if(strcmp(name, "..") == 0){
l = ch->loc->next;
if(l == nil)
goto done;
putloc(ch->fs, ch->loc, 0);
ch->loc = l;
goto done;
}
if(findentry(ch->fs, ch->loc, b, name, &f, ch->flags & CHFDUMP) <= 0)
goto error;
ch->loc = getloc(ch->fs, f, ch->loc);
done:
putbuf(b);
chend(ch);
return 1;
error:
putbuf(b);
chend(ch);
return -1;
}
int
namevalid(char *name)
{
char *p;
if(name == nil || name[0] == 0)
return 0;
if(name[0] == '.' && (name[1] == 0 || name[1] == '.' && name[2] == 0))
return 0;
for(p = name; *p; p++)
if((uint8_t) *p < ' ' || *p == '/')
return 0;
return p - name < NAMELEN;
}
int
chancreat(Chan *ch, char *name, int perm, int mode)
{
Buf *b;
Dentry *d;
int isdir;
Loc *l;
FLoc f;
b = nil;
l = nil;
chbegin(ch);
if(!namevalid(name) || ch->open != 0)
goto inval;
if((ch->flags & CHFRO) != 0)
goto inval;
if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
goto error;
if(isdir = ((perm & DMDIR) != 0))
if((mode & (NP_OWRITE | NP_OEXEC | NP_ORCLOSE | NP_OTRUNC)) != 0)
goto inval;
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil)
goto error;
d = getdent(ch->loc, b);
if(d == nil)
goto error;
if((d->type & QTDIR) == 0){
werrstr(Enotadir);
goto error;
}
if((ch->flags & CHFNOPERM) == 0){
if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE)){
werrstr(Eperm);
goto error;
}
if(isdir)
perm &= ~0777 | d->mode & 0777;
else
perm &= ~0666 | d->mode & 0666;
}
if(newentry(ch->fs, ch->loc, b, name, &f, 0) <= 0)
goto error;
f.type = perm >> 24;
if(newqid(ch->fs, &f.path) < 0)
goto error;
l = getloc(ch->fs, f, ch->loc);
modified(ch, d);
b->op |= BDELWRI;
putbuf(b);
b = nil;
if(willmodify(ch->fs, l, ch->flags & CHFNOLOCK) < 0)
goto error;
b = getbuf(ch->fs->d, l->blk, TDENTRY, 0);
if(b == nil)
goto error;
ch->loc = l;
d = &b->de[l->deind];
memset(d, 0, sizeof(*d));
d->Qid = l->Qid;
strcpy(d->name, name);
d->mtime = time(0);
d->atime = d->mtime;
d->gid = d->uid = d->muid = ch->uid;
d->mode = DALLOC | perm & 0777;
if((d->type & QTEXCL) != 0){
qlock(&ch->loc->ex);
ch->loc->exlock = ch;
ch->loc->lwrite = d->atime;
qunlock(&ch->loc->ex);
}
b->op |= BDELWRI;
putbuf(b);
switch(mode & NP_OEXEC){
case NP_ORDWR:
ch->open |= CHREAD;
case NP_OWRITE:
ch->open |= CHWRITE;
break;
case NP_OEXEC:
case NP_OREAD:
ch->open |= CHREAD;
break;
}
if((mode & NP_ORCLOSE) != 0)
ch->open |= CHRCLOSE;
chend(ch);
return 1;
inval:
werrstr(Einval);
error:
if(l != nil)
putloc(ch->fs, l, 0);
if(b != nil)
putbuf(b);
chend(ch);
return -1;
}
int
chanopen(Chan *ch, int mode)
{
Buf *b;
Dentry *d;
b = nil;
chbegin(ch);
if(ch->open != 0)
goto inval;
if((ch->flags & CHFRO) != 0 && (mode & (NP_ORCLOSE | NP_OTRUNC | NP_OWRITE | NP_ORDWR)) != 0)
goto inval;
if((mode & NP_OTRUNC) != 0)
if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
goto error;
if((mode & NP_ORCLOSE) != 0){
if(ch->loc->next == nil)
goto inval;
b = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
if(b == nil)
goto error;
d = getdent(ch->loc->next, b);
if(d == nil)
goto error;
if((ch->flags & CHFNOPERM) == 0)
if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE))
goto perm;
putbuf(b);
}
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil)
goto error;
d = getdent(ch->loc, b);
if(d == nil)
goto error;
if((d->type & QTAPPEND) != 0)
mode &= ~NP_OTRUNC;
if((d->type & QTDIR) != 0 && (mode & (NP_ORCLOSE | NP_OTRUNC | NP_OWRITE | NP_ORDWR)) != 0)
goto inval;
if((ch->flags & CHFNOPERM) == 0){
if(!permcheck(ch->fs, d, ch->uid, mode & NP_OEXEC))
goto perm;
if((mode & NP_OTRUNC) != 0 && !permcheck(ch->fs, d, ch->uid, NP_OWRITE))
goto perm;
}
if((ch->loc->type & QTEXCL) != 0){
qlock(&ch->loc->ex);
if(ch->loc->exlock == nil || ch->loc->lwrite < time(0) - EXCLDUR){
ch->loc->exlock = ch;
ch->loc->lwrite = time(0);
qunlock(&ch->loc->ex);
}else{
qunlock(&ch->loc->ex);
werrstr(Elocked);
goto error;
}
}
switch(mode & NP_OEXEC){
case NP_ORDWR:
ch->open |= CHREAD;
case NP_OWRITE:
ch->open |= CHWRITE;
break;
case NP_OEXEC:
case NP_OREAD:
ch->open |= CHREAD;
break;
}
if((mode & NP_OTRUNC) != 0){
trunc(ch->fs, ch->loc, b, 0);
modified(ch, d);
b->op |= BDELWRI;
}
if((mode & NP_ORCLOSE) != 0)
ch->open |= CHRCLOSE;
putbuf(b);
chend(ch);
return 1;
inval:
werrstr(Einval);
goto error;
perm:
werrstr(Eperm);
error:
if(b != nil)
putbuf(b);
chend(ch);
return -1;
}
static int
checklock(Chan *ch)
{
int rc;
qlock(&ch->loc->ex);
rc = 1;
if(ch->loc->exlock == ch){
if(ch->loc->lwrite < time(0) - EXCLDUR){
ch->loc->exlock = nil;
werrstr("lock broken");
rc = -1;
}else
ch->loc->lwrite = time(0);
}else{
werrstr(Elocked);
rc = -1;
}
qunlock(&ch->loc->ex);
return rc;
}
int
chanwrite(Chan *ch, void *buf, uint32_t n, uint64_t off)
{
uint64_t i, e, bl;
int r, rn, rc;
Buf *b, *c;
Dentry *d;
uint8_t *p;
if(n == 0)
return 0;
if((ch->flags & CHFRO) != 0){
werrstr(Einval);
return -1;
}
if((ch->open & CHWRITE) == 0){
werrstr(Einval);
return -1;
}
chbegin(ch);
if((ch->loc->type & QTEXCL) != 0 && checklock(ch) < 0){
chend(ch);
return -1;
}
if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0){
chend(ch);
return -1;
}
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil){
chend(ch);
return -1;
}
d = getdent(ch->loc, b);
if(d == nil){
putbuf(b);
chend(ch);
return -1;
}
if((d->type & QTAPPEND) != 0)
off = d->size;
e = off + n;
i = off;
p = buf;
while(i < e){
bl = i / RBLOCK;
r = i % RBLOCK;
rn = RBLOCK - r;
if(i + rn > e)
rn = e - i;
rc = getblk(ch->fs, ch->loc, b, bl, &bl, rn == RBLOCK ? GBOVERWR : GBCREATE);
if(rc < 0)
goto done;
c = getbuf(ch->fs->d, bl, TRAW, rc == 0 || rn == RBLOCK);
if(c == nil)
goto done;
if(rc == 0 && rn != RBLOCK)
memset(c->data, 0, sizeof(c->data));
memcpy(c->data + r, p, rn);
i += rn;
c->op |= i != e ? BWRIM : BDELWRI;
putbuf(c);
p += rn;
}
done:
modified(ch, d);
e = off + (p - (uint8_t *) buf);
if(e > d->size)
d->size = e;
b->op |= BDELWRI;
putbuf(b);
chend(ch);
if(p == buf)
return -1;
return p - (uint8_t *) buf;
}
static int chandirread(Chan *, void *, uint32_t, uint64_t);
int
chanread(Chan *ch, void *buf, uint32_t n, uint64_t off)
{
uint64_t i, e, bl;
int r, rn, rc;
uint8_t *p;
Buf *b, *c;
Dentry *d;
if((ch->open & CHREAD) == 0){
werrstr(Einval);
return -1;
}
chbegin(ch);
if((ch->loc->type & QTEXCL) != 0 && checklock(ch) < 0){
chend(ch);
return -1;
}
if((ch->loc->Qid.type & QTDIR) != 0)
return chandirread(ch, buf, n, off);
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil){
chend(ch);
return -1;
}
d = getdent(ch->loc, b);
if(d == nil)
goto error;
if(off >= d->size)
n = 0;
else if(off + n > d->size)
n = d->size - off;
if(n == 0){
putbuf(b);
chend(ch);
return 0;
}
e = off + n;
i = off;
p = buf;
while(i < e){
bl = i / RBLOCK;
r = i % RBLOCK;
rn = RBLOCK - r;
if(i + rn > e)
rn = e - i;
rc = getblk(ch->fs, ch->loc, b, bl, &bl, GBREAD);
if(rc < 0)
goto error;
if(rc == 0)
memset(p, 0, rn);
else{
c = getbuf(ch->fs->d, bl, TRAW, 0);
if(c == nil)
goto error;
memcpy(p, c->data + r, rn);
putbuf(c);
}
i += rn;
p += rn;
}
putbuf(b);
chend(ch);
return n;
error:
putbuf(b);
chend(ch);
return -1;
}
static void
statbuf(Fs *fs, Dentry *d, Dir *di, char *buf)
{
di->qid = d->Qid;
di->mode = (d->mode & 0777) | (d->Qid.type << 24);
di->mtime = d->mtime;
di->atime = d->atime;
di->length = d->size;
if(d->type & QTDIR)
di->length = 0;
if(buf == nil){
di->name = estrdup(d->name);
di->uid = uid2name(fs, d->uid, nil);
di->gid = uid2name(fs, d->gid, nil);
di->muid = uid2name(fs, d->muid, nil);
}else{
memset(buf, 0, NAMELEN + 3 * USERLEN);
strncpy(buf, d->name, NAMELEN - 1);
di->name = buf;
di->uid = uid2name(fs, d->uid, buf + NAMELEN);
di->gid = uid2name(fs, d->gid, buf + NAMELEN + USERLEN);
di->muid = uid2name(fs, d->muid, buf + NAMELEN + 2 * USERLEN);
}
}
int
chanstat(Chan *ch, Dir *di)
{
Buf *b;
Dentry *d;
chbegin(ch);
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil){
chend(ch);
return -1;
}
d = getdent(ch->loc, b);
if(d == nil){
putbuf(b);
chend(ch);
return -1;
}
statbuf(ch->fs, d, di, nil);
putbuf(b);
chend(ch);
return 0;
}
static int
chandirread(Chan *ch, void *buf, uint32_t n, uint64_t off)
{
Buf *b, *c;
Dentry *d;
uint64_t i, blk;
int j;
int rc;
uint32_t wr;
Dir di;
char cbuf[NAMELEN + 3 * USERLEN];
if(off == 0){
ch->dwloff = 0;
ch->dwblk = 0;
ch->dwind = 0;
}else if(ch->dwloff != off){
werrstr(Einval);
chend(ch);
return -1;
}
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil){
chend(ch);
return -1;
}
d = getdent(ch->loc, b);
if(d == nil){
putbuf(b);
chend(ch);
return -1;
}
if(ch->dwblk >= d->size){
putbuf(b);
chend(ch);
return 0;
}
c = nil;
wr = 0;
i = ch->dwblk;
j = ch->dwind;
for(;;){
if(c == nil){
rc = getblk(ch->fs, ch->loc, b, i, &blk, GBREAD);
if(rc < 0)
goto error;
if(rc == 0){
j = 0;
if(++i >= d->size)
break;
continue;
}
c = getbuf(ch->fs->d, blk, TDENTRY, 0);
if(c == nil)
goto error;
}
if((c->de[j].mode & DALLOC) == 0)
goto next;
if((ch->flags & CHFDUMP) != 0 && (c->de[j].type & QTTMP) != 0)
goto next;
statbuf(ch->fs, &c->de[j], &di, cbuf);
rc = convD2M(&di, (uint8_t *) buf + wr, n - wr);
if(rc <= BIT16SZ)
break;
wr += rc;
next:
if(++j >= DEPERBLK){
j = 0;
if(c != nil)
putbuf(c);
c = nil;
if(++i >= d->size)
break;
}
}
ch->dwblk = i;
ch->dwind = j;
ch->dwloff += wr;
if(c != nil)
putbuf(c);
putbuf(b);
chend(ch);
return wr;
error:
putbuf(b);
chend(ch);
return -1;
}
int
chanclunk(Chan *ch)
{
Buf *b, *p;
int rc;
Dentry *d;
rc = 1;
b = p = nil;
chbegin(ch);
if(ch->open & CHRCLOSE){
if((ch->flags & CHFRO) != 0)
goto inval;
if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
goto error;
if(ch->loc->next == nil)
goto inval;
p = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
if(p == nil)
goto error;
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil)
goto error;
d = getdent(ch->loc->next, p);
if(d == nil)
goto error;
if((ch->flags & CHFNOPERM) == 0)
if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE)){
werrstr(Eperm);
goto error;
}
d = getdent(ch->loc, b);
if(d == nil)
goto error;
if((d->type & QTDIR) != 0 && findentry(ch->fs, ch->loc, b, nil, nil, ch->flags & CHFDUMP) != 0)
goto inval;
if((d->mode & DGONE) != 0)
goto done;
qlock(&ch->fs->loctree);
if(ch->loc->ref > 1){
d->mode &= ~DALLOC;
d->mode |= DGONE; /* aaaaand it's gone */
ch->loc->flags |= LGONE;
qunlock(&ch->fs->loctree);
}else{
ch->loc->flags &= ~LGONE;
qunlock(&ch->fs->loctree);
rc = delete(ch->fs, ch->loc, b);
}
b->op |= BDELWRI;
}
done:
if(b != nil)
putbuf(b);
if(p != nil)
putbuf(p);
if((ch->loc->type & QTEXCL) != 0){
qlock(&ch->loc->ex);
if(ch->loc->exlock == ch)
ch->loc->exlock = nil;
qunlock(&ch->loc->ex);
}
putloc(ch->fs, ch->loc, 1);
chend(ch);
free(ch);
return rc;
inval:
werrstr(Einval);
error:
rc = -1;
goto done;
}
int
chanwstat(Chan *ch, Dir *di)
{
Buf *b, *pb;
Dentry *d;
int isdir, owner, rc;
short nuid, ngid;
b = pb = nil;
chbegin(ch);
if((ch->flags & CHFRO) != 0)
goto inval;
if(willmodify(ch->fs, ch->loc, ch->flags & CHFNOLOCK) < 0)
goto error;
if(*di->name){
FLoc f;
if(!namevalid(di->name) || ch->loc->next == nil)
goto inval;
pb = getbuf(ch->fs->d, ch->loc->next->blk, TDENTRY, 0);
if(pb == nil)
goto error;
d = getdent(ch->loc->next, pb);
if(d == nil)
goto error;
rc = findentry(ch->fs, ch->loc->next, pb, di->name, &f, ch->flags & CHFDUMP);
if(rc < 0)
goto error;
else if(rc == 0){
if((ch->flags & CHFNOPERM) == 0)
if(!permcheck(ch->fs, d, ch->uid, NP_OWRITE))
goto perm;
} else if(f.blk != ch->loc->blk || f.deind != ch->loc->deind){
werrstr(Eexists);
goto error;
}
}
b = getbuf(ch->fs->d, ch->loc->blk, TDENTRY, 0);
if(b == nil)
goto error;
d = getdent(ch->loc, b);
if(d == nil)
goto error;
isdir = (d->type & QTDIR) != 0;
owner = ch->uid == d->uid ||
ingroup(ch->fs, ch->uid, d->gid, 1) ||
(ch->fs->flags & FSNOPERM) != 0 ||
(ch->flags & CHFNOPERM) != 0;
if(di->length != ~0){
if(isdir && di->length != 0)
goto inval;
if((ch->flags & CHFNOPERM) == 0)
if(di->length != d->size && !permcheck(ch->fs, d, ch->uid, NP_OWRITE))
goto perm;
}
if(di->mtime != ~0 && !owner)
goto perm;
if(di->mode != ~0 && !owner)
goto perm;
nuid = d->uid;
ngid = d->gid;
if(*di->uid != 0 && name2uid(ch->fs, di->uid, &nuid) < 0)
goto inval;
if(*di->gid != 0 && name2uid(ch->fs, di->gid, &ngid) < 0)
goto inval;
if(nuid != d->uid && (ch->fs->flags & FSCHOWN) == 0)
goto perm;
if((nuid != d->uid || ngid != d->gid) && !owner)
goto perm;
d->uid = nuid;
d->gid = ngid;
if(di->length != ~0 && di->length != d->size && !isdir){
trunc(ch->fs, ch->loc, b, di->length);
modified(ch, d);
}
if(di->mtime != ~0)
d->mtime = di->mtime;
if(di->mode != ~0){
d->mode = d->mode & ~0777 | di->mode & 0777;
ch->loc->type = d->type = di->mode >> 24;
}
if(*di->name){
memset(d->name, 0, NAMELEN);
strcpy(d->name, di->name);
}
b->op |= BDELWRI;
if(pb != nil)
putbuf(pb);
putbuf(b);
chend(ch);
return 1;
inval:
werrstr(Einval);
goto error;
perm:
werrstr(Eperm);
error:
if(pb != nil)
putbuf(pb);
if(b != nil)
putbuf(b);
chend(ch);
return -1;
}
int
chanremove(Chan *ch)
{
ch->open |= CHRCLOSE;
return chanclunk(ch);
}