Subversion Repositories planix.SVN

Rev

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

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include <fcall.h>
#include <disk.h>

enum {
        LEN     = 8*1024,
        HUNKS   = 128,
};

typedef struct File File;
struct File{
        char    *new;
        char    *elem;
        char    *old;
        char    *uid;
        char    *gid;
        ulong   mode;
};

typedef void Mkfserr(char*, void*);
typedef void Mkfsenum(char*, char*, Dir*, void*);

typedef struct Name Name;
struct Name {
        int n;
        char *s;
};

typedef struct Mkaux Mkaux;
struct Mkaux {
        Mkfserr *warn;
        Mkfsenum *mkenum;
        char *root;
        char *proto;
        jmp_buf jmp;
        Biobuf *b;

        Name oldfile;
        Name fullname;
        int     lineno;
        int     indent;

        void *a;
};

static void domkfs(Mkaux *mkaux, File *me, int level);

static int      copyfile(Mkaux*, File*, Dir*, int);
static void     freefile(File*);
static File*    getfile(Mkaux*, File*);
static char*    getmode(Mkaux*, char*, ulong*);
static char*    getname(Mkaux*, char*, char**);
static char*    getpath(Mkaux*, char*);
static int      mkfile(Mkaux*, File*);
static char*    mkpath(Mkaux*, char*, char*);
static void     mktree(Mkaux*, File*, int);
static void     setnames(Mkaux*, File*);
static void     skipdir(Mkaux*);
static void     warn(Mkaux*, char *, ...);

//static void
//mprint(char *new, char *old, Dir *d, void*)
//{
//      print("%s %s %D\n", new, old, d);
//}

int
rdproto(char *proto, char *root, Mkfsenum *mkenum, Mkfserr *mkerr, void *a)
{
        Mkaux mx, *m;
        File file;
        int rv;

        m = &mx;
        memset(&mx, 0, sizeof mx);
        if(root == nil)
                root = "/";

        m->root = root;
        m->warn = mkerr;
        m->mkenum = mkenum;
        m->a = a;
        m->proto = proto;
        m->lineno = 0;
        m->indent = 0;
        if((m->b = Bopen(proto, OREAD)) == nil) {
                werrstr("open '%s': %r", proto);
                return -1;
        }

        memset(&file, 0, sizeof file);
        file.new = "";
        file.old = nil;

        rv = 0;
        if(setjmp(m->jmp) == 0)
                domkfs(m, &file, -1);
        else
                rv = -1;
        free(m->oldfile.s);
        free(m->fullname.s);
        return rv;
}

static void*
emalloc(Mkaux *mkaux, ulong n)
{
        void *v;

        v = malloc(n);
        if(v == nil)
                longjmp(mkaux->jmp, 1); /* memory leak */
        memset(v, 0, n);
        return v;
}

static char*
estrdup(Mkaux *mkaux, char *s)
{
        s = strdup(s);
        if(s == nil)
                longjmp(mkaux->jmp, 1); /* memory leak */
        return s;
}

static void
domkfs(Mkaux *mkaux, File *me, int level)
{
        File *child;
        int rec;

        child = getfile(mkaux, me);
        if(!child)
                return;
        if((child->elem[0] == '+' || child->elem[0] == '*') && child->elem[1] == '\0'){
                rec = child->elem[0] == '+';
                free(child->new);
                child->new = estrdup(mkaux, me->new);
                setnames(mkaux, child);
                mktree(mkaux, child, rec);
                freefile(child);
                child = getfile(mkaux, me);
        }
        while(child && mkaux->indent > level){
                if(mkfile(mkaux, child))
                        domkfs(mkaux, child, mkaux->indent);
                freefile(child);
                child = getfile(mkaux, me);
        }
        if(child){
                freefile(child);
                Bseek(mkaux->b, -Blinelen(mkaux->b), 1);
                mkaux->lineno--;
        }
}

static void
mktree(Mkaux *mkaux, File *me, int rec)
{
        File child;
        Dir *d;
        int i, n, fd;

        fd = open(mkaux->oldfile.s, OREAD);
        if(fd < 0){
                warn(mkaux, "can't open %s: %r", mkaux->oldfile.s);
                return;
        }

        child = *me;
        while((n = dirread(fd, &d)) > 0){
                for(i = 0; i < n; i++){
                        child.new = mkpath(mkaux, me->new, d[i].name);
                        if(me->old)
                                child.old = mkpath(mkaux, me->old, d[i].name);
                        child.elem = d[i].name;
                        setnames(mkaux, &child);
                        if((!(d[i].mode&DMDIR) || rec) && copyfile(mkaux, &child, &d[i], 1) && rec)
                                mktree(mkaux, &child, rec);
                        free(child.new);
                        if(child.old)
                                free(child.old);
                }
        }
        close(fd);
}

static int
mkfile(Mkaux *mkaux, File *f)
{
        Dir *d;

        if((d = dirstat(mkaux->oldfile.s)) == nil){
                warn(mkaux, "can't stat file %s: %r", mkaux->oldfile.s);
                skipdir(mkaux);
                return 0;
        }
        return copyfile(mkaux, f, d, 0);
}

enum {
        SLOP = 30
};

static void
setname(Mkaux *mkaux, Name *name, char *s1, char *s2)
{
        int l;

        l = strlen(s1)+strlen(s2)+1;
        if(name->n < l+SLOP/2) {
                free(name->s);
                name->s = emalloc(mkaux, l+SLOP);
                name->n = l+SLOP;
        }
        snprint(name->s, name->n, "%s%s%s", s1, s1[0]==0 || s1[strlen(s1)-1]!='/' ? "/" : "", s2);
}

static int
copyfile(Mkaux *mkaux, File *f, Dir *d, int permonly)
{
        Dir *nd;
        ulong xmode;
        char *p;

        setname(mkaux, &mkaux->fullname, mkaux->root, f->old ? f->old : f->new);
        /*
         * Extra stat here is inefficient but accounts for binds.
         */
        if((nd = dirstat(mkaux->fullname.s)) != nil)
                d = nd;

        d->name = f->elem;
        if(d->type != 'M'){
                d->uid = "sys";
                d->gid = "sys";
                xmode = (d->mode >> 6) & 7;
                d->mode |= xmode | (xmode << 3);
        }
        if(strcmp(f->uid, "-") != 0)
                d->uid = f->uid;
        if(strcmp(f->gid, "-") != 0)
                d->gid = f->gid;
        if(f->mode != ~0){
                if(permonly)
                        d->mode = (d->mode & ~0666) | (f->mode & 0666);
                else if((d->mode&DMDIR) != (f->mode&DMDIR))
                        warn(mkaux, "inconsistent mode for %s", f->new);
                else
                        d->mode = f->mode;
        }

        if(p = strrchr(f->new, '/'))
                d->name = p+1;
        else
                d->name = f->new;

        mkaux->mkenum(f->new, mkaux->fullname.s, d, mkaux->a);
        xmode = d->mode;
        free(nd);
        return (xmode&DMDIR) != 0;
}

static char *
mkpath(Mkaux *mkaux, char *prefix, char *elem)
{
        char *p;
        int n;

        n = strlen(prefix) + strlen(elem) + 2;
        p = emalloc(mkaux, n);
        strcpy(p, prefix);
        strcat(p, "/");
        strcat(p, elem);
        return p;
}

static void
setnames(Mkaux *mkaux, File *f)
{
        
        if(f->old){
                if(f->old[0] == '/')
                        setname(mkaux, &mkaux->oldfile, f->old, "");
                else
                        setname(mkaux, &mkaux->oldfile, mkaux->root, f->old);
        } else
                setname(mkaux, &mkaux->oldfile, mkaux->root, f->new);
}

static void
freefile(File *f)
{
        if(f->old)
                free(f->old);
        if(f->new)
                free(f->new);
        free(f);
}

/*
 * skip all files in the proto that
 * could be in the current dir
 */
static void
skipdir(Mkaux *mkaux)
{
        char *p, c;
        int level;

        if(mkaux->indent < 0)
                return;
        level = mkaux->indent;
        for(;;){
                mkaux->indent = 0;
                p = Brdline(mkaux->b, '\n');
                mkaux->lineno++;
                if(!p){
                        mkaux->indent = -1;
                        return;
                }
                while((c = *p++) != '\n')
                        if(c == ' ')
                                mkaux->indent++;
                        else if(c == '\t')
                                mkaux->indent += 8;
                        else
                                break;
                if(mkaux->indent <= level){
                        Bseek(mkaux->b, -Blinelen(mkaux->b), 1);
                        mkaux->lineno--;
                        return;
                }
        }
}

static File*
getfile(Mkaux *mkaux, File *old)
{
        File *f;
        char *elem;
        char *p;
        int c;

        if(mkaux->indent < 0)
                return 0;
loop:
        mkaux->indent = 0;
        p = Brdline(mkaux->b, '\n');
        mkaux->lineno++;
        if(!p){
                mkaux->indent = -1;
                return 0;
        }
        while((c = *p++) != '\n')
                if(c == ' ')
                        mkaux->indent++;
                else if(c == '\t')
                        mkaux->indent += 8;
                else
                        break;
        if(c == '\n' || c == '#')
                goto loop;
        p--;
        f = emalloc(mkaux, sizeof *f);
        p = getname(mkaux, p, &elem);
        if(p == nil)
                return nil;

        f->new = mkpath(mkaux, old->new, elem);
        free(elem);
        f->elem = utfrrune(f->new, L'/') + 1;
        p = getmode(mkaux, p, &f->mode);
        p = getname(mkaux, p, &f->uid); /* LEAK */
        if(p == nil)
                return nil;

        if(!*f->uid)
                strcpy(f->uid, "-");
        p = getname(mkaux, p, &f->gid); /* LEAK */
        if(p == nil)
                return nil;

        if(!*f->gid)
                strcpy(f->gid, "-");
        f->old = getpath(mkaux, p);
        if(f->old && strcmp(f->old, "-") == 0){
                free(f->old);
                f->old = 0;
        }
        setnames(mkaux, f);

        return f;
}

static char*
getpath(Mkaux *mkaux, char *p)
{
        char *q, *new;
        int c, n;

        while((c = *p) == ' ' || c == '\t')
                p++;
        q = p;
        while((c = *q) != '\n' && c != ' ' && c != '\t')
                q++;
        if(q == p)
                return 0;
        n = q - p;
        new = emalloc(mkaux, n + 1);
        memcpy(new, p, n);
        new[n] = 0;
        return new;
}

static char*
getname(Mkaux *mkaux, char *p, char **buf)
{
        char *s, *start;
        int c;

        while((c = *p) == ' ' || c == '\t')
                p++;

        start = p;
        while((c = *p) != '\n' && c != ' ' && c != '\t')
                p++;

        *buf = malloc(p+2-start);       /* +2: need at least 2 bytes; might strcpy "-" into buf */
        if(*buf == nil)
                return nil;
        memmove(*buf, start, p-start);

        (*buf)[p-start] = '\0';

        if(**buf == '$'){
                s = getenv(*buf+1);
                if(s == 0){
                        warn(mkaux, "can't read environment variable %s", *buf+1);
                        skipdir(mkaux);
                        free(*buf);
                        return nil;
                }
                free(*buf);
                *buf = s;
        }
        return p;
}

static char*
getmode(Mkaux *mkaux, char *p, ulong *xmode)
{
        char *buf, *s;
        ulong m;

        *xmode = ~0;
        p = getname(mkaux, p, &buf);
        if(p == nil)
                return nil;

        s = buf;
        if(!*s || strcmp(s, "-") == 0)
                return p;
        m = 0;
        if(*s == 'd'){
                m |= DMDIR;
                s++;
        }
        if(*s == 'a'){
                m |= DMAPPEND;
                s++;
        }
        if(*s == 'l'){
                m |= DMEXCL;
                s++;
        }
        if(s[0] < '0' || s[0] > '7'
        || s[1] < '0' || s[1] > '7'
        || s[2] < '0' || s[2] > '7'
        || s[3]){
                warn(mkaux, "bad mode specification %s", buf);
                free(buf);
                return p;
        }
        *xmode = m | strtoul(s, 0, 8);
        free(buf);
        return p;
}

static void
warn(Mkaux *mkaux, char *fmt, ...)
{
        char buf[256];
        va_list va;

        va_start(va, fmt);
        vseprint(buf, buf+sizeof(buf), fmt, va);
        va_end(va);

        if(mkaux->warn)
                mkaux->warn(buf, mkaux->a);
        else
                fprint(2, "warning: %s\n", buf);
}