#include "all.h"
/*
* stuff from /sys/include/libc.h for 9P2000
*/
#define STATMAX 65535U /* max length of machine-independent stat structure */
#define DIRMAX (sizeof(Dir)+STATMAX) /* max length of Dir structure */
#define ERRMAX 128 /* max length of error string */
/* bits in Dir.mode */
#define DMDIR 0x80000000 /* mode bit for directories */
#define DMAPPEND 0x40000000 /* mode bit for append only files */
#define DMEXCL 0x20000000 /* mode bit for exclusive use files */
#define DMMOUNT 0x10000000 /* mode bit for mounted channel */
#define DMREAD 0x4 /* mode bit for read permission */
#define DMWRITE 0x2 /* mode bit for write permission */
#define DMEXEC 0x1 /* mode bit for execute permission */
typedef
struct Dir {
/* system-modified data */
ushort type; /* server type */
uint dev; /* server subtype */
/* file data */
Qid qid; /* unique id from server */
ulong mode; /* permissions */
ulong atime; /* last read time */
ulong mtime; /* last write time */
vlong length; /* file length: see <u.h> */
char *name; /* last element of path */
char *uid; /* owner name */
char *gid; /* group name */
char *muid; /* last modifier name */
} Dir;
#define MSIZE (MAXDAT+MAXMSG)
#include "fcall.h"
/*
* buggery to give false qid for
* the top 2 levels of the dump fs
*/
void
mkqid(Qid* qid, Dentry *d, int buggery)
{
int c;
if(buggery && d->qid.path == (QPDIR|QPROOT)){
c = d->name[0];
if(c >= '0' && c <= '9'){
qid->path = 3;
qid->vers = d->qid.version;
qid->type = QTDIR;
c = (c-'0')*10 + (d->name[1]-'0');
if(c >= 1 && c <= 12)
qid->path = 4;
return;
}
}
mkqid9p2(qid, &d->qid, d->mode);
}
int
mkqidcmp(Qid* qid, Dentry *d)
{
Qid tmp;
mkqid(&tmp, d, 1);
if(qid->path == tmp.path && qid->type == tmp.type)
return 0;
return Eqid;
}
int
doremove(File *f)
{
Iobuf *p, *p1;
Dentry *d, *d1;
Off addr;
int slot, err;
p = 0;
p1 = 0;
if(f->fs->dev->type == Devro) {
err = Eronly;
goto out;
}
/*
* check on parent directory of file to be deleted
*/
if(f->wpath == 0 || f->wpath->addr == f->addr) {
err = Ephase;
goto out;
}
p1 = getbuf(f->fs->dev, f->wpath->addr, Bread);
d1 = getdir(p1, f->wpath->slot);
if(!d1 || checktag(p1, Tdir, QPNONE) || !(d1->mode & DALLOC)) {
err = Ephase;
goto out;
}
if(iaccess(f, d1, DWRITE)) {
err = Eaccess;
goto out;
}
accessdir(p1, d1, FWRITE, f->uid);
putbuf(p1);
p1 = 0;
/*
* check on file to be deleted
*/
p = getbuf(f->fs->dev, f->addr, Bread);
d = getdir(p, f->slot);
if(!d || checktag(p, Tdir, QPNONE) || !(d->mode & DALLOC)) {
err = Ealloc;
goto out;
}
if(err = mkqidcmp(&f->qid, d))
goto out;
/*
* if deleting a directory, make sure it is empty
*/
if((d->mode & DDIR))
for(addr=0;; addr++) {
p1 = dnodebuf(p, d, addr, 0, f->uid);
if(!p1)
break;
if(checktag(p1, Tdir, d->qid.path)) {
err = Ephase;
goto out;
}
for(slot=0; slot<DIRPERBUF; slot++) {
d1 = getdir(p1, slot);
if(!(d1->mode & DALLOC))
continue;
err = Eempty;
goto out;
}
putbuf(p1);
}
/*
* do it
*/
dtrunc(p, d, f->uid);
memset(d, 0, sizeof(Dentry));
settag(p, Tdir, QPNONE);
out:
if(p1)
putbuf(p1);
if(p)
putbuf(p);
return err;
}
static int
mkmode9p1(ulong mode9p2)
{
int mode;
/*
* Assume this is for an allocated entry.
*/
mode = DALLOC|(mode9p2 & 0777);
if(mode9p2 & DMEXCL)
mode |= DLOCK;
if(mode9p2 & DMAPPEND)
mode |= DAPND;
if(mode9p2 & DMDIR)
mode |= DDIR;
return mode;
}
void
mkqid9p1(Qid9p1* qid9p1, Qid* qid)
{
if(qid->path & 0xFFFFFFFF00000000LL)
panic("mkqid9p1: path %lluX\n", (Wideoff)qid->path);
qid9p1->path = qid->path & 0xFFFFFFFF;
if(qid->type & QTDIR)
qid9p1->path |= QPDIR;
qid9p1->version = qid->vers;
}
static int
mktype9p2(int mode9p1)
{
int type;
type = 0;
if(mode9p1 & DLOCK)
type |= QTEXCL;
if(mode9p1 & DAPND)
type |= QTAPPEND;
if(mode9p1 & DDIR)
type |= QTDIR;
return type;
}
static ulong
mkmode9p2(int mode9p1)
{
ulong mode;
mode = mode9p1 & 0777;
if(mode9p1 & DLOCK)
mode |= DMEXCL;
if(mode9p1 & DAPND)
mode |= DMAPPEND;
if(mode9p1 & DDIR)
mode |= DMDIR;
return mode;
}
void
mkqid9p2(Qid* qid, Qid9p1* qid9p1, int mode9p1)
{
qid->path = (ulong)(qid9p1->path & ~QPDIR);
qid->vers = qid9p1->version;
qid->type = mktype9p2(mode9p1);
}
static int
mkdir9p2(Dir* dir, Dentry* dentry, void* strs)
{
char *op, *p;
memset(dir, 0, sizeof(Dir));
mkqid(&dir->qid, dentry, 1);
dir->mode = mkmode9p2(dentry->mode);
dir->atime = dentry->atime;
dir->mtime = dentry->mtime;
dir->length = dentry->size;
op = p = strs;
dir->name = p;
p += sprint(p, "%s", dentry->name)+1;
dir->uid = p;
uidtostr(p, dentry->uid, 1);
p += strlen(p)+1;
dir->gid = p;
uidtostr(p, dentry->gid, 1);
p += strlen(p)+1;
dir->muid = p;
uidtostr(p, dentry->muid, 1);
p += strlen(p)+1;
return p-op;
}
static int
checkname9p2(char* name)
{
char *p;
/*
* Return error or 0 if OK.
*/
if(name == nil || *name == 0)
return Ename;
for(p = name; *p != 0; p++){
if(p-name >= NAMELEN-1)
return Etoolong;
if((uchar)*p <= 040)
return Ename;
}
if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
return Edot;
return 0;
}
static int
version(Chan* chan, Fcall* f, Fcall* r)
{
if(chan->protocol != nil)
return Eversion;
if(f->msize < MSIZE)
r->msize = f->msize;
else
r->msize = MSIZE;
/*
* Should check the '.' stuff here.
*/
if(strcmp(f->version, VERSION9P) == 0){
r->version = VERSION9P;
chan->protocol = serve9p;
chan->msize = r->msize;
}
else
r->version = "unknown";
fileinit(chan);
return 0;
}
struct
{
Lock;
ulong hi;
} authpath;
static int
auth(Chan* chan, Fcall* f, Fcall* r)
{
char *aname;
File *file;
Filsys *fs;
int error;
if(cons.flags & authdisableflag)
return Eauthdisabled;
error = 0;
aname = f->aname;
if(strcmp(f->uname, "none") == 0)
return Eauthnone;
if(!aname[0]) /* default */
aname = "main";
file = filep(chan, f->afid, 1);
if(file == nil){
error = Efidinuse;
goto out;
}
fs = fsstr(aname);
if(fs == nil){
error = Ebadspc;
goto out;
}
lock(&authpath);
file->qid.path = authpath.hi++;
unlock(&authpath);
file->qid.type = QTAUTH;
file->qid.vers = 0;
file->fs = fs;
file->open = FREAD+FWRITE;
freewp(file->wpath);
file->wpath = 0;
file->auth = authnew(f->uname, f->aname);
if(file->auth == nil){
error = Eauthfile;
goto out;
}
r->aqid = file->qid;
out:
if((cons.flags & attachflag) && error)
print("9p2: auth %s %T SUCK EGGS --- %s\n", f->uname, time(), errstr9p[error]);
if(file != nil){
qunlock(file);
if(error)
freefp(file);
}
return error;
}
#define aprint(...) if(db) print(__VA_ARGS__)
int
authorize(Chan* chan, Fcall* f)
{
File* af;
int db, uid;
uid = -1;
db = cons.flags&authdebugflag;
if(strcmp(f->uname, "none") == 0){
uid = strtouid(f->uname);
aprint("permission granted to none: uid %s = %d\n", f->uname, uid);
return uid;
}
if(cons.flags & authdisableflag){
uid = strtouid(f->uname);
aprint("permission granted by authdisable uid %s = %d\n", f->uname, uid);
return uid;
}
af = filep(chan, f->afid, 0);
if(af == nil){
aprint("authorize: af == nil\n");
return -1;
}
if(af->auth == nil){
aprint("authorize: af->auth == nil\n");
goto out;
}
if(strcmp(f->uname, authuname(af->auth)) != 0){
aprint("authorize: strcmp(f->uname, authuname(af->auth)) != 0\n");
goto out;
}
if(strcmp(f->aname, authaname(af->auth)) != 0){
aprint("authorize: strcmp(f->aname, authaname(af->auth)) != 0\n");
goto out;
}
uid = authuid(af->auth);
aprint("authorize: uid is %d\n", uid);
out:
qunlock(af);
return uid;
}
static int
attach(Chan* chan, Fcall* f, Fcall* r)
{
char *aname;
Iobuf *p;
Dentry *d;
File *file;
Filsys *fs;
Off raddr;
int error, u;
aname = f->aname;
if(!aname[0])
aname = "main";
p = nil;
error = 0;
file = filep(chan, f->fid, 1);
if(file == nil){
error = Efidinuse;
goto out;
}
u = -1;
if(chan != cons.chan){
if(noattach && strcmp(f->uname, "none")) {
error = Enoattach;
goto out;
}
u = authorize(chan, f);
if(u < 0){
error = Ebadu;
goto out;
}
}
file->uid = u;
fs = fsstr(aname);
if(fs == nil){
error = Ebadspc;
goto out;
}
raddr = getraddr(fs->dev);
p = getbuf(fs->dev, raddr, Bread);
if(p == nil || checktag(p, Tdir, QPROOT)){
error = Ealloc;
goto out;
}
d = getdir(p, 0);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if (iaccess(file, d, DEXEC) ||
file->uid == 0 && fs->dev->type == Devro) {
/*
* 'none' not allowed on dump
*/
error = Eaccess;
goto out;
}
accessdir(p, d, FREAD, file->uid);
mkqid(&file->qid, d, 1);
file->fs = fs;
file->addr = raddr;
file->slot = 0;
file->open = 0;
freewp(file->wpath);
file->wpath = 0;
r->qid = file->qid;
strncpy(chan->whoname, f->uname, sizeof(chan->whoname));
chan->whotime = time();
if(cons.flags & attachflag)
print("9p2: attach %s %T to \"%s\" C%d\n",
chan->whoname, chan->whotime, fs->name, chan->chan);
out:
if((cons.flags & attachflag) && error)
print("9p2: attach %s %T SUCK EGGS --- %s\n",
f->uname, time(), errstr9p[error]);
if(p)
putbuf(p);
if(file){
qunlock(file);
if(error)
freefp(file);
}
return error;
}
static int
flush(Chan* chan, Fcall*, Fcall*)
{
runlock(&chan->reflock);
wlock(&chan->reflock);
wunlock(&chan->reflock);
rlock(&chan->reflock);
return 0;
}
static void
clone(File* nfile, File* file)
{
Wpath *wpath;
nfile->qid = file->qid;
lock(&wpathlock);
nfile->wpath = file->wpath;
for(wpath = nfile->wpath; wpath != nil; wpath = wpath->up)
wpath->refs++;
unlock(&wpathlock);
nfile->fs = file->fs;
nfile->addr = file->addr;
nfile->slot = file->slot;
nfile->uid = file->uid;
nfile->open = file->open & ~FREMOV;
}
static int
walkname(File* file, char* wname, Qid* wqid)
{
Wpath *w;
Iobuf *p, *p1;
Dentry *d, *d1;
int error, slot;
Off addr, qpath;
p = p1 = nil;
/*
* File must not have been opened for I/O by an open
* or create message and must represent a directory.
*/
if(file->open != 0){
error = Emode;
goto out;
}
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Edir1;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if(!(d->mode & DDIR)){
error = Edir1;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
/*
* For walked elements the implied user must
* have permission to search the directory.
*/
if(iaccess(file, d, DEXEC)){
error = Eaccess;
goto out;
}
accessdir(p, d, FREAD, file->uid);
if(strcmp(wname, ".") == 0){
setdot:
if(wqid != nil)
*wqid = file->qid;
goto out;
}
if(strcmp(wname, "..") == 0){
if(file->wpath == 0)
goto setdot;
putbuf(p);
p = nil;
addr = file->wpath->addr;
slot = file->wpath->slot;
p1 = getbuf(file->fs->dev, addr, Bread);
if(p1 == nil || checktag(p1, Tdir, QPNONE)){
error = Edir1;
goto out;
}
d1 = getdir(p1, slot);
if(d == nil || !(d1->mode & DALLOC)){
error = Ephase;
goto out;
}
lock(&wpathlock);
file->wpath->refs--;
file->wpath = file->wpath->up;
unlock(&wpathlock);
goto found;
}
for(addr = 0; ; addr++){
if(p == nil){
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
}
qpath = d->qid.path;
p1 = dnodebuf1(p, d, addr, 0, file->uid);
p = nil;
if(p1 == nil || checktag(p1, Tdir, qpath)){
error = Eentry;
goto out;
}
for(slot = 0; slot < DIRPERBUF; slot++){
d1 = getdir(p1, slot);
if (!(d1->mode & DALLOC) ||
strncmp(wname, d1->name, NAMELEN) != 0)
continue;
/*
* update walk path
*/
if((w = newwp()) == nil){
error = Ewalk;
goto out;
}
w->addr = file->addr;
w->slot = file->slot;
w->up = file->wpath;
file->wpath = w;
slot += DIRPERBUF*addr;
goto found;
}
putbuf(p1);
p1 = nil;
}
found:
file->addr = p1->addr;
mkqid(&file->qid, d1, 1);
putbuf(p1);
p1 = nil;
file->slot = slot;
if(wqid != nil)
*wqid = file->qid;
out:
if(p1 != nil)
putbuf(p1);
if(p != nil)
putbuf(p);
return error;
}
#ifdef p9grovel
static void
prfids(Chan *c)
{
File *f;
int h, nc;
nc = 0;
lock(&flock);
for(h = 0; h < nelem(flist); h++)
for(f=flist[h]; f; f=f->next){
nc++;
if(f->cp == c)
print("fid %ld\n", f->fid);
}
unlock(&flock);
print("prfid: %d chan\n", nc);
}
#endif
static int
walk(Chan* chan, Fcall* f, Fcall* r)
{
int error, nwname;
File *file, *nfile, tfile;
/*
* The file identified by f->fid must be valid in the
* current session and must not have been opened for I/O
* by an open or create message.
*/
//prfids(chan);
if((file = filep(chan, f->fid, 0)) == nil)
return Efid;
if(file->open != 0){
qunlock(file);
return Emode;
}
/*
* If newfid is not the same as fid, allocate a new file;
* a side effect is checking newfid is not already in use (error);
* if there are no names to walk this will be equivalent to a
* simple 'clone' operation.
* Otherwise, fid and newfid are the same and if there are names
* to walk make a copy of 'file' to be used during the walk as
* 'file' must only be updated on success.
* Finally, it's a no-op if newfid is the same as fid and f->nwname
* is 0.
*/
r->nwqid = 0;
if(f->newfid != f->fid){
if((nfile = filep(chan, f->newfid, 1)) == nil){
qunlock(file);
return Efidinuse;
}
}
else if(f->nwname != 0){
nfile = &tfile;
memset(nfile, 0, sizeof(File));
nfile->cp = chan;
nfile->fid = ~0;
}
else{
qunlock(file);
return 0;
}
clone(nfile, file);
/*
* Should check name is not too long.
*/
error = 0;
for(nwname = 0; nwname < f->nwname; nwname++){
error = walkname(nfile, f->wname[nwname], &r->wqid[r->nwqid]);
if(error != 0 || ++r->nwqid >= MAXDAT/sizeof(Qid))
break;
}
if(f->nwname == 0){
/*
* Newfid must be different to fid (see above)
* so this is a simple 'clone' operation - there's
* nothing to do except unlock unless there's
* an error.
*/
if(error){
freewp(nfile->wpath);
qunlock(nfile);
freefp(nfile);
}
else
qunlock(nfile);
}
else if(r->nwqid < f->nwname){
/*
* Didn't walk all elements, 'clunk' nfile
* and leave 'file' alone.
* Clear error if some of the elements were
* walked OK.
*/
freewp(nfile->wpath);
if(nfile != &tfile){
qunlock(nfile);
freefp(nfile);
}
if(r->nwqid != 0)
error = 0;
}
else{
/*
* Walked all elements. If newfid is the same
* as fid must update 'file' from the temporary
* copy used during the walk.
* Otherwise just unlock (when using tfile there's
* no need to unlock as it's a local).
*/
if(nfile == &tfile){
file->qid = nfile->qid;
freewp(file->wpath);
file->wpath = nfile->wpath;
file->addr = nfile->addr;
file->slot = nfile->slot;
}
else
qunlock(nfile);
}
qunlock(file);
return error;
}
static int
open(Chan* chan, Fcall* f, Fcall* r)
{
Iobuf *p;
Dentry *d;
File *file;
Tlock *t;
Qid qid;
int error, ro, fmod;
p = nil;
if((file = filep(chan, f->fid, 0)) == nil){
error = Efid;
goto out;
}
if(file->open != 0){
error = Emode;
goto out;
}
/*
* if remove on close, check access here
*/
ro = file->fs->dev->type == Devro;
if(f->mode & ORCLOSE){
if(ro){
error = Eronly;
goto out;
}
/*
* check on parent directory of file to be deleted
*/
if(file->wpath == 0 || file->wpath->addr == file->addr){
error = Ephase;
goto out;
}
p = getbuf(file->fs->dev, file->wpath->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ephase;
goto out;
}
d = getdir(p, file->wpath->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ephase;
goto out;
}
if(iaccess(file, d, DWRITE)){
error = Eaccess;
goto out;
}
putbuf(p);
}
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
mkqid(&qid, d, 1);
switch(f->mode & 7){
case OREAD:
if(iaccess(file, d, DREAD))
goto badaccess;
fmod = FREAD;
break;
case OWRITE:
if((d->mode & DDIR) || iaccess(file, d, DWRITE))
goto badaccess;
if(ro){
error = Eronly;
goto out;
}
fmod = FWRITE;
break;
case ORDWR:
if((d->mode & DDIR)
|| iaccess(file, d, DREAD)
|| iaccess(file, d, DWRITE))
goto badaccess;
if(ro){
error = Eronly;
goto out;
}
fmod = FREAD+FWRITE;
break;
case OEXEC:
if((d->mode & DDIR) || iaccess(file, d, DEXEC))
goto badaccess;
fmod = FREAD;
break;
default:
error = Emode;
goto out;
}
if(f->mode & OTRUNC){
if((d->mode & DDIR) || iaccess(file, d, DWRITE))
goto badaccess;
if(ro){
error = Eronly;
goto out;
}
}
t = 0;
if(d->mode & DLOCK){
if((t = tlocked(p, d)) == nil){
error = Elocked;
goto out;
}
}
if(f->mode & ORCLOSE)
fmod |= FREMOV;
file->open = fmod;
if((f->mode & OTRUNC) && !(d->mode & DAPND)){
dtrunc(p, d, file->uid);
qid.vers = d->qid.version;
}
r->qid = qid;
file->tlock = t;
if(t != nil)
t->file = file;
file->lastra = 1;
goto out;
badaccess:
error = Eaccess;
file->open = 0;
out:
if(p != nil)
putbuf(p);
if(file != nil)
qunlock(file);
r->iounit = chan->msize-IOHDRSZ;
return error;
}
static int
create(Chan* chan, Fcall* f, Fcall* r)
{
Iobuf *p, *p1;
Dentry *d, *d1;
File *file;
int error, slot, slot1, fmod;
Off addr, addr1, path;
Tlock *t;
Wpath *w;
p = nil;
if((file = filep(chan, f->fid, 0)) == nil){
error = Efid;
goto out;
}
if(file->fs->dev->type == Devro){
error = Eronly;
goto out;
}
if(file->qid.type & QTAUTH){
error = Emode;
goto out;
}
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
if(!(d->mode & DDIR)){
error = Edir2;
goto out;
}
if(iaccess(file, d, DWRITE)) {
error = Eaccess;
goto out;
}
accessdir(p, d, FREAD, file->uid);
/*
* Check the name is valid.
*/
if(error = checkname9p2(f->name))
goto out;
addr1 = 0;
slot1 = 0; /* set */
for(addr = 0; ; addr++){
if((p1 = dnodebuf(p, d, addr, 0, file->uid)) == nil){
if(addr1 != 0)
break;
p1 = dnodebuf(p, d, addr, Tdir, file->uid);
}
if(p1 == nil){
error = Efull;
goto out;
}
if(checktag(p1, Tdir, d->qid.path)){
putbuf(p1);
goto phase;
}
for(slot = 0; slot < DIRPERBUF; slot++){
d1 = getdir(p1, slot);
if(!(d1->mode & DALLOC)){
if(addr1 == 0){
addr1 = p1->addr;
slot1 = slot + addr*DIRPERBUF;
}
continue;
}
if(strncmp(f->name, d1->name, sizeof(d1->name)) == 0){
putbuf(p1);
error = Eexist;
goto out;
}
}
putbuf(p1);
}
switch(f->mode & 7){
case OEXEC:
case OREAD: /* seems only useful to make directories */
fmod = FREAD;
break;
case OWRITE:
fmod = FWRITE;
break;
case ORDWR:
fmod = FREAD+FWRITE;
break;
default:
error = Emode;
goto out;
}
if(f->perm & PDIR)
if((f->mode & OTRUNC) || (f->perm & PAPND) || (fmod & FWRITE))
goto badaccess;
/*
* do it
*/
path = qidpathgen(file->fs->dev);
if((p1 = getbuf(file->fs->dev, addr1, Bread|Bimm|Bmod)) == nil)
goto phase;
d1 = getdir(p1, slot1);
if(d1 == nil || checktag(p1, Tdir, d->qid.path)) {
putbuf(p1);
goto phase;
}
if(d1->mode & DALLOC){
putbuf(p1);
goto phase;
}
strncpy(d1->name, f->name, sizeof(d1->name));
if(chan == cons.chan){
d1->uid = cons.uid;
d1->gid = cons.gid;
}
else{
d1->uid = file->uid;
d1->gid = d->gid;
f->perm &= d->mode | ~0666;
if(f->perm & PDIR)
f->perm &= d->mode | ~0777;
}
d1->qid.path = path;
d1->qid.version = 0;
d1->mode = DALLOC | (f->perm & 0777);
if(f->perm & PDIR) {
d1->mode |= DDIR;
d1->qid.path |= QPDIR;
}
if(f->perm & PAPND)
d1->mode |= DAPND;
t = nil;
if(f->perm & PLOCK){
d1->mode |= DLOCK;
t = tlocked(p1, d1);
/* if nil, out of tlock structures */
}
accessdir(p1, d1, FWRITE, file->uid);
mkqid(&r->qid, d1, 0);
putbuf(p1);
accessdir(p, d, FWRITE, file->uid);
/*
* do a walk to new directory entry
*/
if((w = newwp()) == nil){
error = Ewalk;
goto out;
}
w->addr = file->addr;
w->slot = file->slot;
w->up = file->wpath;
file->wpath = w;
file->qid = r->qid;
file->tlock = t;
if(t != nil)
t->file = file;
file->lastra = 1;
if(f->mode & ORCLOSE)
fmod |= FREMOV;
file->open = fmod;
file->addr = addr1;
file->slot = slot1;
goto out;
badaccess:
error = Eaccess;
goto out;
phase:
error = Ephase;
out:
if(p != nil)
putbuf(p);
if(file != nil)
qunlock(file);
r->iounit = chan->msize-IOHDRSZ;
return error;
}
static int
read(Chan* chan, Fcall* f, Fcall* r)
{
Iobuf *p, *p1;
File *file;
Dentry *d, *d1;
Tlock *t;
Off addr, offset, start;
Timet tim;
int error, iounit, nread, count, n, o, slot;
Msgbuf *dmb;
uchar *data;
Dir dir;
p = nil;
error = 0;
count = f->count;
offset = f->offset;
data = (uchar*)f->data;
nread = 0;
if((file = filep(chan, f->fid, 0)) == nil){
error = Efid;
goto out;
}
if(!(file->open & FREAD)){
error = Eopen;
goto out;
}
iounit = chan->msize-IOHDRSZ;
if(count < 0 || count > iounit){
error = Ecount;
goto out;
}
if(offset < 0){
error = Eoffset;
goto out;
}
if(file->qid.type & QTAUTH){
nread = authread(file, (uchar*)data, count);
if(nread < 0)
error = Eauth2;
goto out;
}
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
if(t = file->tlock){
tim = toytime();
if(t->time < tim || t->file != file){
error = Ebroken;
goto out;
}
/* renew the lock */
t->time = tim + TLOCK;
}
accessdir(p, d, FREAD, file->uid);
if(d->mode & DDIR)
goto dread;
if(offset >= d->size)
count = 0;
else if(offset+count > d->size)
count = d->size - offset;
while(count > 0){
if(p == nil){
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
}
addr = offset / BUFSIZE;
file->lastra = dbufread(p, d, addr, file->lastra, file->uid);
o = offset % BUFSIZE;
n = BUFSIZE - o;
if(n > count)
n = count;
p1 = dnodebuf1(p, d, addr, 0, file->uid);
p = nil;
if(p1 != nil){
if(checktag(p1, Tfile, QPNONE)){
error = Ephase;
putbuf(p1);
goto out;
}
memmove(data+nread, p1->iobuf+o, n);
putbuf(p1);
}
else
memset(data+nread, 0, n);
count -= n;
nread += n;
offset += n;
}
goto out;
dread:
/*
* Pick up where we left off last time if nothing has changed,
* otherwise must scan from the beginning.
*/
if(offset == file->doffset /*&& file->qid.vers == file->dvers*/){
addr = file->dslot/DIRPERBUF;
slot = file->dslot%DIRPERBUF;
start = offset;
}
else{
addr = 0;
slot = 0;
start = 0;
}
dmb = mballoc(iounit, chan, Mbreply1);
for (;;) {
if(p == nil){
/*
* This is just a check to ensure the entry hasn't
* gone away during the read of each directory block.
*/
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out1;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out1;
}
}
p1 = dnodebuf1(p, d, addr, 0, file->uid);
p = nil;
if(p1 == nil)
goto out1;
if(checktag(p1, Tdir, QPNONE)){
error = Ephase;
putbuf(p1);
goto out1;
}
for(; slot < DIRPERBUF; slot++){
d1 = getdir(p1, slot);
if(!(d1->mode & DALLOC))
continue;
mkdir9p2(&dir, d1, dmb->data);
n = convD2M(&dir, data+nread, iounit - nread);
if(n <= BIT16SZ){
putbuf(p1);
goto out1;
}
start += n;
if(start < offset)
continue;
if(count < n){
putbuf(p1);
goto out1;
}
count -= n;
nread += n;
offset += n;
}
putbuf(p1);
slot = 0;
addr++;
}
out1:
mbfree(dmb);
if(error == 0){
file->doffset = offset;
file->dvers = file->qid.vers;
file->dslot = slot+DIRPERBUF*addr;
}
out:
/*
* Do we need this any more?
count = f->count - nread;
if(count > 0)
memset(data+nread, 0, count);
*/
if(p != nil)
putbuf(p);
if(file != nil)
qunlock(file);
r->count = nread;
r->data = (char*)data;
return error;
}
static int
write(Chan* chan, Fcall* f, Fcall* r)
{
Iobuf *p, *p1;
Dentry *d;
File *file;
Tlock *t;
Off offset, addr, qpath;
Timet tim;
int count, error, nwrite, o, n;
error = 0;
offset = f->offset;
count = f->count;
nwrite = 0;
p = nil;
if((file = filep(chan, f->fid, 0)) == nil){
error = Efid;
goto out;
}
if(!(file->open & FWRITE)){
error = Eopen;
goto out;
}
if(count < 0 || count > chan->msize-IOHDRSZ){
error = Ecount;
goto out;
}
if(offset < 0) {
error = Eoffset;
goto out;
}
if(file->qid.type & QTAUTH){
nwrite = authwrite(file, (uchar*)f->data, count);
if(nwrite < 0)
error = Eauth2;
goto out;
}
else if(file->fs->dev->type == Devro){
error = Eronly;
goto out;
}
if ((p = getbuf(file->fs->dev, file->addr, Bread|Bmod)) == nil ||
(d = getdir(p, file->slot)) == nil || !(d->mode & DALLOC)) {
error = Ealloc;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
if(t = file->tlock) {
tim = toytime();
if(t->time < tim || t->file != file){
error = Ebroken;
goto out;
}
/* renew the lock */
t->time = tim + TLOCK;
}
accessdir(p, d, FWRITE, file->uid);
if(d->mode & DAPND)
offset = d->size;
if(offset+count > d->size)
d->size = offset+count;
while(count > 0){
if(p == nil){
p = getbuf(file->fs->dev, file->addr, Bread|Bmod);
if(p == nil){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
}
addr = offset / BUFSIZE;
o = offset % BUFSIZE;
n = BUFSIZE - o;
if(n > count)
n = count;
qpath = d->qid.path;
p1 = dnodebuf1(p, d, addr, Tfile, file->uid);
p = nil;
if(p1 == nil) {
error = Efull;
goto out;
}
if(checktag(p1, Tfile, qpath)){
putbuf(p1);
error = Ephase;
goto out;
}
memmove(p1->iobuf+o, f->data+nwrite, n);
p1->flags |= Bmod;
putbuf(p1);
count -= n;
nwrite += n;
offset += n;
}
out:
if(p != nil)
putbuf(p);
if(file != nil)
qunlock(file);
r->count = nwrite;
return error;
}
static int
_clunk(File* file, int remove)
{
Tlock *t;
int error;
error = 0;
if(t = file->tlock){
if(t->file == file)
t->time = 0; /* free the lock */
file->tlock = 0;
}
if(remove && (file->qid.type & QTAUTH) == 0)
error = doremove(file);
file->open = 0;
freewp(file->wpath);
authfree(file->auth);
freefp(file);
qunlock(file);
return error;
}
static int
clunk(Chan* chan, Fcall* f, Fcall*)
{
File *file;
if((file = filep(chan, f->fid, 0)) == nil)
return Efid;
_clunk(file, file->open & FREMOV);
return 0;
}
static int
remove(Chan* chan, Fcall* f, Fcall*)
{
File *file;
if((file = filep(chan, f->fid, 0)) == nil)
return Efid;
return _clunk(file, 1);
}
static int
stat(Chan* chan, Fcall* f, Fcall* r, uchar* data)
{
Dir dir;
Iobuf *p;
Dentry *d, dentry;
File *file;
int error, len;
error = 0;
p = nil;
if((file = filep(chan, f->fid, 0)) == nil)
return Efid;
if(file->qid.type & QTAUTH){
memset(&dentry, 0, sizeof dentry);
d = &dentry;
mkqid9p1(&d->qid, &file->qid);
strcpy(d->name, "#¿");
d->uid = authuid(file->auth);
d->gid = d->uid;
d->muid = d->uid;
d->atime = time();
d->mtime = d->atime;
d->size = 0;
} else {
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Edir1;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
if(d->qid.path == QPROOT) /* stat of root gives time */
d->atime = time();
}
len = mkdir9p2(&dir, d, data);
data += len;
if((r->nstat = convD2M(&dir, data, chan->msize - len)) == 0)
error = Eedge;
r->stat = data;
out:
if(p != nil)
putbuf(p);
if(file != nil)
qunlock(file);
return error;
}
static int
wstat(Chan* chan, Fcall* f, Fcall*, char* strs)
{
Iobuf *p, *p1;
Dentry *d, *d1;
File *file;
int error, err, gid, gl, muid, op, slot, tsync, uid;
long addr;
Dir dir;
if(convM2D(f->stat, f->nstat, &dir, strs) == 0)
return Econvert;
/*
* Get the file.
* If user 'none' (uid == 0), can't do anything;
* if filesystem is read-only, can't change anything.
*/
if((file = filep(chan, f->fid, 0)) == nil)
return Efid;
p = p1 = nil;
if(file->uid == 0){
error = Eaccess;
goto out;
}
if(file->fs->dev->type == Devro){
error = Eronly;
goto out;
}
if(file->qid.type & QTAUTH){
error = Emode;
goto out;
}
/*
* Get the current entry and check it is still valid.
*/
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ealloc;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ealloc;
goto out;
}
if(error = mkqidcmp(&file->qid, d))
goto out;
/*
* Run through each of the (sub-)fields in the provided Dir
* checking for validity and whether it's a default:
* .type, .dev and .atime are completely ignored and not checked;
* .qid.path, .qid.vers and .muid are checked for validity but
* any attempt to change them is an error.
* .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
* possibly be changed (and .muid iff wstatallow).
*
* 'Op' flags there are changed fields, i.e. it's not a no-op.
* 'Tsync' flags all fields are defaulted.
*
* Wstatallow and writeallow are set to allow changes during the
* fileserver bootstrap phase.
*/
tsync = 1;
if(dir.qid.path != ~0){
if(dir.qid.path != file->qid.path){
error = Ewstatp;
goto out;
}
tsync = 0;
}
if(dir.qid.vers != ~0){
if(dir.qid.vers != file->qid.vers){
error = Ewstatv;
goto out;
}
tsync = 0;
}
/*
* .qid.type and .mode have some bits in common. Only .mode
* is currently needed for comparisons with the old mode but
* if there are changes to the bits also encoded in .qid.type
* then file->qid must be updated appropriately later.
*/
if(dir.qid.type == (uchar)~0){
if(dir.mode == ~0)
dir.qid.type = mktype9p2(d->mode);
else
dir.qid.type = dir.mode>>24;
}
else
tsync = 0;
if(dir.mode == ~0)
dir.mode = mkmode9p2(d->mode);
else
tsync = 0;
/*
* Check dir.qid.type and dir.mode agree, check for any unknown
* type/mode bits, check for an attempt to change the directory bit.
*/
if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
error = Ewstatq;
goto out;
}
if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
error = Ewstatb;
goto out;
}
op = dir.mode^mkmode9p2(d->mode);
if(op & DMDIR){
error = Ewstatd;
goto out;
}
if(dir.mtime != ~0){
if(dir.mtime != d->mtime)
op = 1;
tsync = 0;
}
else
dir.mtime = d->mtime;
if(dir.length == ~(Off)0)
dir.length = d->size;
else {
if (dir.length < 0) {
error = Ewstatl;
goto out;
} else if(dir.length != d->size)
op = 1;
tsync = 0;
}
/*
* Check for permission to change .mode, .mtime or .length,
* must be owner or leader of either group, for which test gid
* is needed; permission checks on gid will be done later.
* 'Gl' counts whether neither, one or both groups are led.
*/
if(dir.gid != nil && *dir.gid != '\0'){
gid = strtouid(dir.gid);
tsync = 0;
}
else
gid = d->gid;
gl = leadgroup(file->uid, gid) != 0;
gl += leadgroup(file->uid, d->gid) != 0;
if(op && !ALLOW(chan) && d->uid != file->uid && !gl){
error = Ewstato;
goto out;
}
/*
* Rename.
* Check .name is valid and different to the current.
*/
if(dir.name != nil && *dir.name != '\0'){
if(error = checkname9p2(dir.name))
goto out;
if(strncmp(dir.name, d->name, NAMELEN))
op = 1;
else
dir.name = d->name;
tsync = 0;
}
else
dir.name = d->name;
/*
* If the name is really to be changed check it's unique
* and there is write permission in the parent.
*/
if(dir.name != d->name){
/*
* First get parent.
* Must drop current entry to prevent
* deadlock when searching that new name
* already exists below.
*/
putbuf(p);
p = nil;
if(file->wpath == nil){
error = Ephase;
goto out;
}
p1 = getbuf(file->fs->dev, file->wpath->addr, Bread);
if(p1 == nil || checktag(p1, Tdir, QPNONE)){
error = Ephase;
goto out;
}
d1 = getdir(p1, file->wpath->slot);
if(d1 == nil || !(d1->mode & DALLOC)){
error = Ephase;
goto out;
}
/*
* Check entries in parent for new name.
*/
for(addr = 0; ; addr++){
if((p = dnodebuf(p1, d1, addr, 0, file->uid)) == nil)
break;
if(checktag(p, Tdir, d1->qid.path)){
putbuf(p);
continue;
}
for(slot = 0; slot < DIRPERBUF; slot++){
d = getdir(p, slot);
if(!(d->mode & DALLOC) ||
strncmp(dir.name, d->name, sizeof d->name))
continue;
error = Eexist;
goto out;
}
putbuf(p);
}
/*
* Reacquire entry and check it's still OK.
*/
p = getbuf(file->fs->dev, file->addr, Bread);
if(p == nil || checktag(p, Tdir, QPNONE)){
error = Ephase;
goto out;
}
d = getdir(p, file->slot);
if(d == nil || !(d->mode & DALLOC)){
error = Ephase;
goto out;
}
/*
* Check write permission in the parent.
*/
if(iaccess(file, d1, DWRITE)){
error = Eaccess;
goto out;
}
}
/*
* Check for permission to change owner - must be god.
*/
if(dir.uid != nil && *dir.uid != '\0'){
uid = strtouid(dir.uid);
if(uid != d->uid){
if(!ALLOW(chan)){
error = Ewstatu;
goto out;
}
op = 1;
}
tsync = 0;
}
else
uid = d->uid;
if(dir.muid != nil && *dir.muid != '\0'){
muid = strtouid(dir.muid);
if(muid != d->muid){
if(!ALLOW(chan)){
error = Ewstatm;
goto out;
}
op = 1;
}
tsync = 0;
}
else
muid = d->muid;
/*
* Check for permission to change group, must be
* either owner and in new group or leader of both groups.
*/
if(gid != d->gid){
if(!ALLOW(chan)
&& !(d->uid == file->uid && ingroup(file->uid, gid))
&& !(gl == 2)){
error = Ewstatg;
goto out;
}
op = 1;
}
/*
* Checks all done, update if necessary.
*/
if(op){
d->mode = mkmode9p1(dir.mode);
file->qid.type = mktype9p2(d->mode);
d->mtime = dir.mtime;
if (dir.length < d->size) {
err = dtrunclen(p, d, dir.length, uid);
if (error == 0)
error = err;
}
d->size = dir.length;
if(dir.name != d->name)
strncpy(d->name, dir.name, sizeof(d->name));
d->uid = uid;
d->gid = gid;
d->muid = muid;
}
if(!tsync)
accessdir(p, d, FREAD, file->uid);
out:
if(p != nil)
putbuf(p);
if(p1 != nil)
putbuf(p1);
qunlock(file);
return error;
}
int
call9p2(Chan *chan, Fcall *f, Fcall *r, char *ename)
{
int error, type, n;
static int once;
if(once == 0){
fmtinstall('F', fcallfmt);
fmtinstall('M', dirmodefmt);
once = 1;
}
cons.work.count++;
chan->work.count++;
if(f->type == Tread){
n = f->count;
cons.rate.count += n;
chan->rate.count += n;
}
if(CHAT(chan))
print("9p2: f %F\n", f);
type = f->type;
r->type = type+1;
r->tag = f->tag;
r->ename = 0;
error = 0;
switch(f->type){
default:
r->type = Rerror;
snprint(ename, 64, "unknown message: %F", f);
r->ename = ename;
break;
case Tversion:
error = version(chan, f, r);
break;
case Tauth:
error = auth(chan, f, r);
break;
case Tattach:
error = attach(chan, f, r);
break;
case Tflush:
error = flush(chan, f, r);
break;
case Twalk:
error = walk(chan, f, r);
break;
case Topen:
error = open(chan, f, r);
break;
case Tcreate:
error = create(chan, f, r);
break;
case Tread:
error = read(chan, f, r);
break;
case Twrite:
error = write(chan, f, r);
break;
case Tclunk:
error = clunk(chan, f, r);
break;
case Tremove:
error = remove(chan, f, r);
break;
case Tstat:
error = stat(chan, f, r, (uchar*)f->data);
break;
case Twstat:
error = wstat(chan, f, r, f->data);
break;
}
if(error != 0){
r->type = Rerror;
if(error >= MAXERR){
snprint(ename, 64, "error %d", error);
r->ename = ename;
}
else
r->ename = errstr9p[error];
}
if(CHAT(chan))
print("9p2: r %F\n", r);
return error;
}
int
serve9p(Msgbuf* mb)
{
Chan *chan;
Fcall f, r;
Msgbuf *data, *rmb;
char ename[64];
int n, type;
/*
* 0 return means i don't understand this message,
* 1 return means i dealt with it, including error
* replies.
*/
if(convM2S(mb->data, mb->count, &f) != mb->count)
return 0;
type = f.type;
if(type < Tversion || type >= Tmax || (type & 1) || type == Terror)
return 0;
chan = mb->chan;
data = nil;
if(type == Tread || type == Twstat || type == Tstat){
data = mballoc(chan->msize, chan, Mbreply1);
f.data = (char*)data->data;
}
n = mb->count;
chan->rate.count += n;
cons.rate.count += n;
call9p2(chan, &f, &r, ename);
rmb = mballoc(chan->msize, chan, Mbreply2);
n = convS2M(&r, rmb->data, chan->msize);
if(data != nil)
mbfree(data);
if(n == 0){
type = r.type;
r.type = Rerror;
/*
* If a Tversion has not been seen on the chan then
* chan->msize will be 0. In that case craft a special
* Rerror message. It's fortunate that the mballoc above
* for rmb will have returned a Msgbuf of MAXMSG size
* when given a request with count of 0...
*/
if(chan->msize == 0){
r.ename = "Tversion not seen";
n = convS2M(&r, rmb->data, MAXMSG);
}
else{
snprint(ename, sizeof(ename), "9p2: convS2M: type %d", type);
r.ename = ename;
n = convS2M(&r, rmb->data, chan->msize);
}
print("%s\n", r.ename);
if(n == 0){
/*
* What to do here, the failure notification failed?
*/
mbfree(rmb);
return 1;
}
}
rmb->count = n;
rmb->param = mb->param;
send(chan->reply, rmb);
return 1;
}
|