| #include <u.h> |
| #include <libc.h> |
| #include <bio.h> |
| #include <auth.h> |
| #include <fcall.h> |
| #include <disk.h> |
| |
| enum { |
| LEN = 8*1024, |
| HUNKS = 128 |
| }; |
| |
| #undef warn |
| #define warn protowarn |
| |
| #undef getmode |
| #define getmode protogetmode |
| |
| 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; |
| volatile 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, '/') + 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); |
| } |