/*
* directory reading
* from /sys/src/libc/9sys/dirread.c
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "ureg.h"
enum
{
DIRSIZE = STATFIXLEN + 16 * 4 /* enough for encoded stat buf + some reasonable strings */
};
Dir*
dirchstat(Chan *chan)
{
Dir *d;
uchar *buf;
int n, nd, i;
nd = DIRSIZE;
for(i=0; i<2; i++){ /* should work by the second try */
d = malloc(sizeof(Dir) + BIT16SZ + nd);
if(d == nil)
return nil;
buf = (uchar*)&d[1];
n = devtab[chan->type]->stat(chan, buf, BIT16SZ + nd);
if(n < BIT16SZ){
free(d);
return nil;
}
nd = GBIT16((uchar*)buf); /* upper bound on size of Dir + strings */
if(nd <= n){
convM2D(buf, n, d, (char*)&d[1]);
return d;
}
/* else sizeof(Dir)+BIT16SZ+nd is plenty */
free(d);
}
return nil;
}
long
dirpackage(uchar *buf, long ts, Dir **d)
{
char *s;
long ss, i, n, nn, m;
*d = nil;
if(ts <= 0)
return 0;
/*
* first find number of all stats, check they look like stats, & size all associated strings
*/
ss = 0;
n = 0;
for(i = 0; i < ts; i += m){
m = BIT16SZ + GBIT16(&buf[i]);
if(statcheck(&buf[i], m) < 0)
break;
ss += m;
n++;
}
if(i != ts)
return -1;
*d = malloc(n * sizeof(Dir) + ss);
if(*d == nil)
return -1;
/*
* then convert all buffers
*/
s = (char*)*d + n * sizeof(Dir);
nn = 0;
for(i = 0; i < ts; i += m){
m = BIT16SZ + GBIT16((uchar*)&buf[i]);
if(nn >= n || convM2D(&buf[i], m, *d + nn, s) != m){
free(*d);
*d = nil;
return -1;
}
nn++;
s += m;
}
return nn;
}
/*
* directory reading, from sysfile.c
*/
long
unionread(Chan *c, void *va, long n)
{
int i;
long nr;
Mhead *m;
Mount *mount;
qlock(&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 && !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){
cclose(c->umc);
c->umc = nil;
}
mount = mount->next;
}
runlock(&m->lock);
qunlock(&c->umqlock);
return nr;
}
void
unionrewind(Chan *c)
{
qlock(&c->umqlock);
c->uri = 0;
if(c->umc){
cclose(c->umc);
c->umc = nil;
}
qunlock(&c->umqlock);
}
static int
dirfixed(uchar *p, uchar *e, Dir *d)
{
int len;
len = GBIT16(p)+BIT16SZ;
if(p + len > e)
return -1;
p += BIT16SZ; /* ignore size */
d->type = devno(GBIT16(p), 1);
p += BIT16SZ;
d->dev = GBIT32(p);
p += BIT32SZ;
d->qid.type = GBIT8(p);
p += BIT8SZ;
d->qid.vers = GBIT32(p);
p += BIT32SZ;
d->qid.path = GBIT64(p);
p += BIT64SZ;
d->mode = GBIT32(p);
p += BIT32SZ;
d->atime = GBIT32(p);
p += BIT32SZ;
d->mtime = GBIT32(p);
p += BIT32SZ;
d->length = GBIT64(p);
return len;
}
static char*
dirname(uchar *p, int *n)
{
p += BIT16SZ+BIT16SZ+BIT32SZ+BIT8SZ+BIT32SZ+BIT64SZ
+ BIT32SZ+BIT32SZ+BIT32SZ+BIT64SZ;
*n = GBIT16(p);
return (char*)p+BIT16SZ;
}
static long
dirsetname(char *name, int len, uchar *p, long n, long maxn)
{
char *oname;
int olen;
long nn;
if(n == BIT16SZ)
return BIT16SZ;
oname = dirname(p, &olen);
nn = n+len-olen;
PBIT16(p, nn-BIT16SZ);
if(nn > maxn)
return BIT16SZ;
if(len != olen)
memmove(oname+len, oname+olen, p+n-(uchar*)(oname+olen));
PBIT16((uchar*)(oname-2), len);
memmove(oname, name, len);
return nn;
}
/*
* 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.
*/
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;
}
if(p == op){
qunlock(&c->rockqlock);
return 0;
}
/* shift the rest */
if(rp != erp)
memmove(c->dirrock, rp, erp-rp);
c->nrock = erp - rp;
*nn = p - op;
qunlock(&c->rockqlock);
return 1;
}
void
mountrewind(Chan *c)
{
c->nrock = 0;
}
/*
* 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.
*/
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; 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;
}
/*
* Rewrite directory entry.
*/
memmove(p, buf, l);
Norewrite:
cclose(nc);
putmhead(mh);
}
}
if(buf)
free(buf);
if(p != e)
error("oops in rockfix");
return e-op;
}
|