long
unionread(Chan *c, void *va, long n)
{
int i;
long nr;
Mhead *m;
Mount *mount;
eqlock(&c->umqlock);
m = c->umh;
rlock(&m->lock);
mount = m->mount;
/* bring mount in sync with c->uri and c->umc */
for(i = 0; mount != nil && i < c->uri; i++)
mount = mount->next;
nr = 0;
while(mount != nil){
/* Error causes component of union to be skipped */
if(mount->to != nil && !waserror()){
if(c->umc == nil){
c->umc = cclone(mount->to);
c->umc = devtab[c->umc->type]->open(c->umc, OREAD);
}
nr = devtab[c->umc->type]->read(c->umc, va, n, c->umc->offset);
c->umc->offset += nr;
poperror();
}
if(nr > 0)
break;
/* Advance to next element */
c->uri++;
if(c->umc != nil){
cclose(c->umc);
c->umc = nil;
}
mount = mount->next;
}
runlock(&m->lock);
qunlock(&c->umqlock);
return nr;
}
/*
* Mountfix might have caused the fixed results of the directory read
* to overflow the buffer. Catch the overflow in c->dirrock.
*/
static void
mountrock(Chan *c, uchar *p, uchar **pe)
{
uchar *e, *r;
int len, n;
e = *pe;
/* find last directory entry */
for(;;){
len = BIT16SZ+GBIT16(p);
if(p+len >= e)
break;
p += len;
}
/* save it away */
qlock(&c->rockqlock);
if(c->nrock+len > c->mrock){
n = ROUND(c->nrock+len, 1024);
r = smalloc(n);
memmove(r, c->dirrock, c->nrock);
free(c->dirrock);
c->dirrock = r;
c->mrock = n;
}
memmove(c->dirrock+c->nrock, p, len);
c->nrock += len;
qunlock(&c->rockqlock);
/* drop it */
*pe = p;
}
/*
* Satisfy a directory read with the results saved in c->dirrock.
*/
static int
mountrockread(Chan *c, uchar *op, long n, long *nn)
{
long dirlen;
uchar *rp, *erp, *ep, *p;
/* common case */
if(c->nrock == 0)
return 0;
/* copy out what we can */
qlock(&c->rockqlock);
rp = c->dirrock;
erp = rp+c->nrock;
p = op;
ep = p+n;
while(rp+BIT16SZ <= erp){
dirlen = BIT16SZ+GBIT16(rp);
if(p+dirlen > ep)
break;
memmove(p, rp, dirlen);
p += dirlen;
rp += dirlen;
}
/*
* Rewrite the results of a directory read to reflect current
* name space bindings and mounts. Specifically, replace
* directory entries for bind and mount points with the results
* of statting what is mounted there. Except leave the old names.
*/
static long
mountfix(Chan *c, uchar *op, long n, long maxn)
{
char *name;
int nbuf, nname;
Chan *nc;
Mhead *mh;
Mount *m;
uchar *p;
int dirlen, rest;
long l;
uchar *buf, *e;
Dir d;
p = op;
buf = nil;
nbuf = 0;
for(e=&p[n]; p+BIT16SZ<e; p+=dirlen){
dirlen = dirfixed(p, e, &d);
if(dirlen < 0)
break;
nc = nil;
mh = nil;
if(findmount(&nc, &mh, d.type, d.dev, d.qid)){
/*
* If it's a union directory and the original is
* in the union, don't rewrite anything.
*/
for(m = mh->mount; m != nil; m = m->next)
if(eqchantdqid(m->to, d.type, d.dev, d.qid, 1))
goto Norewrite;
name = dirname(p, &nname);
/*
* Do the stat but fix the name. If it fails, leave old entry.
* BUG: If it fails because there isn't room for the entry,
* what can we do? Nothing, really. Might as well skip it.
*/
if(buf == nil){
buf = smalloc(4096);
nbuf = 4096;
}
if(waserror())
goto Norewrite;
l = devtab[nc->type]->stat(nc, buf, nbuf);
l = dirsetname(name, nname, buf, l, nbuf);
if(l == BIT16SZ)
error("dirsetname");
poperror();
/*
* Shift data in buffer to accomodate new entry,
* possibly overflowing into rock.
*/
rest = e - (p+dirlen);
if(l > dirlen){
while(p+l+rest > op+maxn){
mountrock(c, p, &e);
if(e == p){
dirlen = 0;
goto Norewrite;
}
rest = e - (p+dirlen);
}
}
if(l != dirlen){
memmove(p+l, p+dirlen, rest);
dirlen = l;
e = p+dirlen+rest;
}
static long
read(int fd, uchar *p, long n, vlong *offp)
{
long nn, nnn;
Chan *c;
vlong off;
validaddr((uintptr)p, n, 1);
c = fdtochan(fd, OREAD, 1, 1);
if(waserror()){
cclose(c);
nexterror();
}
/*
* The offset is passed through on directories, normally.
* Sysseek complains, but pread is used by servers like exportfs,
* that shouldn't need to worry about this issue.
*
* Notice that c->devoffset is the offset that c's dev is seeing.
* The number of bytes read on this fd (c->offset) may be different
* due to rewritings in rockfix.
*/
if(offp == nil) /* use and maintain channel's offset */
off = c->offset;
else
off = *offp;
if(off < 0)
error(Enegoff);
if(off == 0){ /* rewind to the beginning of the directory */
if(offp == nil || (c->qid.type & QTDIR)){
c->offset = 0;
c->devoffset = 0;
}
mountrewind(c);
unionrewind(c);
}
if(c->qid.type & QTDIR){
if(mountrockread(c, p, n, &nn)){
/* do nothing: mountrockread filled buffer */
}else if(c->umh != nil)
nn = unionread(c, p, n);
else{
if(off != c->offset)
error(Edirseek);
nn = devtab[c->type]->read(c, p, n, c->devoffset);
}
nnn = mountfix(c, p, nn, n);
}else
nnn = nn = devtab[c->type]->read(c, p, n, off);
uintptr
sysseek(va_list list)
{
int fd, t;
vlong n, *v;
v = va_arg(list, vlong*);
evenaddr((uintptr)v);
validaddr((uintptr)v, sizeof(vlong), 1);
fd = va_arg(list, int);
n = va_arg(list, vlong);
t = va_arg(list, int);
*v = sseek(fd, n, t);
return 0;
}
uintptr
sysoseek(va_list list)
{
int fd, t;
long n;
fd = va_arg(list, int);
n = va_arg(list, long);
t = va_arg(list, int);
return (uintptr)sseek(fd, n, t);
}
void
validstat(uchar *s, int n)
{
int m;
char buf[64];
if(statcheck(s, n) < 0)
error(Ebadstat);
/* verify that name entry is acceptable */
s += STATFIXLEN - 4*BIT16SZ; /* location of first string */
/*
* s now points at count for first string.
* if it's too long, let the server decide; this is
* only for his protection anyway. otherwise
* we'd have to allocate and waserror.
*/
m = GBIT16(s);
s += BIT16SZ;
if(m+1 > sizeof buf)
return;
memmove(buf, s, m);
buf[m] = '\0';
/* name could be '/' */
if(strcmp(buf, "/") != 0)
validname(buf, 0);
}
name = va_arg(list, char*);
old = va_arg(list, char*);
cmounted = nil;
validaddr((uintptr)old, 1, 0);
cmount = namec(old, Amount, 0, 0);
if(waserror()) {
cclose(cmount);
if(cmounted != nil)
cclose(cmounted);
nexterror();
}
if(name != nil) {
/*
* This has to be namec(..., Aopen, ...) because
* if arg[0] is something like /srv/cs or /fd/0,
* opening it is the only way to get at the real
* Chan underneath.
*/
validaddr((uintptr)name, 1, 0);
cmounted = namec(name, Aopen, OREAD, 0);
}
cunmount(cmount, cmounted);
poperror();
cclose(cmount);
if(cmounted != nil)
cclose(cmounted);
return 0;
}
name = va_arg(list, char*);
validaddr((uintptr)name, 1, 0);
c = namec(name, Aremove, 0, 0);
/*
* Removing mount points is disallowed to avoid surprises
* (which should be removed: the mount point or the mounted Chan?).
*/
if(c->ismtpt){
cclose(c);
error(Eismtpt);
}
if(waserror()){
c->type = 0; /* see below */
cclose(c);
nexterror();
}
devtab[c->type]->remove(c);
/*
* Remove clunks the fid, but we need to recover the Chan
* so fake it up. rootclose() is known to be a nop.
*/
c->type = 0;
poperror();
cclose(c);
return 0;
}
static long
wstat(Chan *c, uchar *d, int nd)
{
long l;
int namelen;
if(waserror()){
cclose(c);
nexterror();
}
if(c->ismtpt){
/*
* Renaming mount points is disallowed to avoid surprises
* (which should be renamed? the mount point or the mounted Chan?).
*/
dirname(d, &namelen);
if(namelen)
nameerror(chanpath(c), Eismtpt);
}
l = devtab[c->type]->wstat(c, d, nd);
poperror();
cclose(c);
return l;
}
/* lay down old stat buffer - grotty code but it's temporary */
p = buf;
strncpy((char*)p, d->name, 28);
p += 28;
strncpy((char*)p, d->uid, 28);
p += 28;
strncpy((char*)p, d->gid, 28);
p += 28;
q = (ulong)d->qid.path & ~DMDIR; /* make sure doesn't accidentally look like directory */
if(d->qid.type & QTDIR) /* this is the real test of a new directory */
q |= DMDIR;
PBIT32(p, q);
p += BIT32SZ;
PBIT32(p, d->qid.vers);
p += BIT32SZ;
PBIT32(p, d->mode);
p += BIT32SZ;
PBIT32(p, d->atime);
p += BIT32SZ;
PBIT32(p, d->mtime);
p += BIT32SZ;
PBIT64(p, d->length);
p += BIT64SZ;
PBIT16(p, d->type);
p += BIT16SZ;
PBIT16(p, d->dev);
}
uintptr
sys_stat(va_list list)
{
static char old[] = "old stat system call - recompile";
Chan *c;
uint l;
uchar *s, buf[128]; /* old DIRLEN plus a little should be plenty */
char strs[128], *name;
Dir d;
name = va_arg(list, char*);
s = va_arg(list, uchar*);
validaddr((uintptr)s, 116, 1);
validaddr((uintptr)name, 1, 0);
c = namec(name, Aaccess, 0, 0);
if(waserror()){
cclose(c);
nexterror();
}
l = devtab[c->type]->stat(c, buf, sizeof buf);
/* buf contains a new stat buf; convert to old. yuck. */
if(l <= BIT16SZ) /* buffer too small; time to face reality */
error(old);
name = pathlast(c->path);
if(name != nil)
l = dirsetname(name, strlen(name), buf, l, sizeof buf);
l = convM2D(buf, l, &d, strs);
if(l == 0)
error(old);
packoldstat(s, &d);
poperror();
cclose(c);
return 0;
}
uintptr
sys_fstat(va_list list)
{
static char old[] = "old fstat system call - recompile";
Chan *c;
char *name;
uint l;
uchar *s, buf[128]; /* old DIRLEN plus a little should be plenty */
char strs[128];
Dir d;
int fd;
fd = va_arg(list, int);
s = va_arg(list, uchar*);
validaddr((uintptr)s, 116, 1);
c = fdtochan(fd, -1, 0, 1);
if(waserror()){
cclose(c);
nexterror();
}
l = devtab[c->type]->stat(c, buf, sizeof buf);
/* buf contains a new stat buf; convert to old. yuck. */
if(l <= BIT16SZ) /* buffer too small; time to face reality */
error(old);
name = pathlast(c->path);
if(name != nil)
l = dirsetname(name, strlen(name), buf, l, sizeof buf);
l = convM2D(buf, l, &d, strs);
if(l == 0)
error(old);
packoldstat(s, &d);