Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;
}