| #include <u.h> |
| #include <libc.h> |
| #include <auth.h> |
| #include <bio.h> |
| |
| #define mkdir plan9mkdir |
| #define getmode plan9_getmode |
| #define setuid plan9_setuid |
| |
| enum{ |
| LEN = 8*1024, |
| HUNKS = 128, |
| |
| /* |
| * types of destination file sytems |
| */ |
| Kfs = 0, |
| Fs, |
| Archive, |
| }; |
| |
| typedef struct File File; |
| |
| struct File{ |
| char *new; |
| char *elem; |
| char *old; |
| char *uid; |
| char *gid; |
| ulong mode; |
| }; |
| |
| void arch(Dir*); |
| void copy(Dir*); |
| int copyfile(File*, Dir*, int); |
| void* emalloc(ulong); |
| void error(char *, ...); |
| void freefile(File*); |
| File* getfile(File*); |
| char* getmode(char*, ulong*); |
| char* getname(char*, char**); |
| char* getpath(char*); |
| void kfscmd(char *); |
| void mkdir(Dir*); |
| int mkfile(File*); |
| void mkfs(File*, int); |
| char* mkpath(char*, char*); |
| void mktree(File*, int); |
| void mountkfs(char*); |
| void printfile(File*); |
| void setnames(File*); |
| void setusers(void); |
| void skipdir(void); |
| char* strdup(char*); |
| int uptodate(Dir*, char*); |
| void usage(void); |
| void warn(char *, ...); |
| |
| Biobuf *b; |
| Biobuf bout; /* stdout when writing archive */ |
| uchar boutbuf[2*LEN]; |
| char newfile[LEN]; |
| char oldfile[LEN]; |
| char *proto; |
| char *cputype; |
| char *users; |
| char *oldroot; |
| char *newroot; |
| char *prog = "mkfs"; |
| int lineno; |
| char *buf; |
| char *zbuf; |
| int buflen = 1024-8; |
| int indent; |
| int verb; |
| int modes; |
| int ream; |
| int debug; |
| int xflag; |
| int sfd; |
| int fskind; /* Kfs, Fs, Archive */ |
| int setuid; /* on Fs: set uid and gid? */ |
| char *user; |
| |
| void |
| main(int argc, char **argv) |
| { |
| File file; |
| char *name; |
| int i, errs; |
| |
| quotefmtinstall(); |
| user = getuser(); |
| name = ""; |
| memset(&file, 0, sizeof file); |
| file.new = ""; |
| file.old = 0; |
| oldroot = ""; |
| newroot = "/n/kfs"; |
| users = 0; |
| fskind = Kfs; |
| ARGBEGIN{ |
| case 'a': |
| if(fskind != Kfs) { |
| fprint(2, "cannot use -a with -d\n"); |
| usage(); |
| } |
| fskind = Archive; |
| newroot = ""; |
| Binits(&bout, 1, OWRITE, boutbuf, sizeof boutbuf); |
| break; |
| case 'd': |
| if(fskind != Kfs) { |
| fprint(2, "cannot use -d with -a\n"); |
| usage(); |
| } |
| fskind = Fs; |
| newroot = ARGF(); |
| break; |
| case 'D': |
| debug = 1; |
| break; |
| case 'n': |
| name = EARGF(usage()); |
| break; |
| case 'p': |
| modes = 1; |
| break; |
| case 'r': |
| ream = 1; |
| break; |
| case 's': |
| oldroot = ARGF(); |
| break; |
| case 'u': |
| users = ARGF(); |
| break; |
| case 'U': |
| setuid = 1; |
| break; |
| case 'v': |
| verb = 1; |
| break; |
| case 'x': |
| xflag = 1; |
| break; |
| case 'z': |
| buflen = atoi(ARGF())-8; |
| break; |
| default: |
| usage(); |
| }ARGEND |
| |
| if(!argc) |
| usage(); |
| |
| buf = emalloc(buflen); |
| zbuf = emalloc(buflen); |
| memset(zbuf, 0, buflen); |
| |
| mountkfs(name); |
| kfscmd("allow"); |
| proto = "users"; |
| setusers(); |
| cputype = getenv("cputype"); |
| if(cputype == 0) |
| cputype = "68020"; |
| |
| errs = 0; |
| for(i = 0; i < argc; i++){ |
| proto = argv[i]; |
| fprint(2, "processing %q\n", proto); |
| |
| b = Bopen(proto, OREAD); |
| if(!b){ |
| fprint(2, "%q: can't open %q: skipping\n", prog, proto); |
| errs++; |
| continue; |
| } |
| |
| lineno = 0; |
| indent = 0; |
| mkfs(&file, -1); |
| Bterm(b); |
| } |
| fprint(2, "file system made\n"); |
| kfscmd("disallow"); |
| kfscmd("sync"); |
| if(errs) |
| exits("skipped protos"); |
| if(fskind == Archive){ |
| Bprint(&bout, "end of archive\n"); |
| Bterm(&bout); |
| } |
| exits(0); |
| } |
| |
| void |
| mkfs(File *me, int level) |
| { |
| File *child; |
| int rec; |
| |
| child = getfile(me); |
| if(!child) |
| return; |
| if((child->elem[0] == '+' || child->elem[0] == '*') && child->elem[1] == '\0'){ |
| rec = child->elem[0] == '+'; |
| free(child->new); |
| child->new = strdup(me->new); |
| setnames(child); |
| mktree(child, rec); |
| freefile(child); |
| child = getfile(me); |
| } |
| while(child && indent > level){ |
| if(mkfile(child)) |
| mkfs(child, indent); |
| freefile(child); |
| child = getfile(me); |
| } |
| if(child){ |
| freefile(child); |
| Bseek(b, -Blinelen(b), 1); |
| lineno--; |
| } |
| } |
| |
| void |
| mktree(File *me, int rec) |
| { |
| File child; |
| Dir *d; |
| int i, n, fd; |
| |
| fd = open(oldfile, OREAD); |
| if(fd < 0){ |
| warn("can't open %q: %r", oldfile); |
| return; |
| } |
| |
| child = *me; |
| while((n = dirread(fd, &d)) > 0){ |
| for(i = 0; i < n; i++){ |
| child.new = mkpath(me->new, d[i].name); |
| if(me->old) |
| child.old = mkpath(me->old, d[i].name); |
| child.elem = d[i].name; |
| setnames(&child); |
| if(copyfile(&child, &d[i], 1) && rec) |
| mktree(&child, rec); |
| free(child.new); |
| if(child.old) |
| free(child.old); |
| } |
| } |
| close(fd); |
| } |
| |
| int |
| mkfile(File *f) |
| { |
| Dir *dir; |
| |
| if((dir = dirstat(oldfile)) == nil){ |
| warn("can't stat file %q: %r", oldfile); |
| skipdir(); |
| return 0; |
| } |
| return copyfile(f, dir, 0); |
| } |
| |
| int |
| copyfile(File *f, Dir *d, int permonly) |
| { |
| ulong mode; |
| Dir nd; |
| |
| if(xflag){ |
| Bprint(&bout, "%q\t%ld\t%lld\n", f->new, d->mtime, d->length); |
| return (d->mode & DMDIR) != 0; |
| } |
| if(verb && (fskind == Archive || ream)) |
| fprint(2, "%q\n", f->new); |
| d->name = f->elem; |
| if(d->type != 'M' && d->type != 'Z'){ |
| d->uid = "sys"; |
| d->gid = "sys"; |
| mode = (d->mode >> 6) & 7; |
| d->mode |= mode | (mode << 3); |
| } |
| if(strcmp(f->uid, "-") != 0) |
| d->uid = f->uid; |
| if(strcmp(f->gid, "-") != 0) |
| d->gid = f->gid; |
| if(fskind == Fs && !setuid){ |
| d->uid = ""; |
| d->gid = ""; |
| } |
| if(f->mode != ~0){ |
| if(permonly) |
| d->mode = (d->mode & ~0666) | (f->mode & 0666); |
| else if((d->mode&DMDIR) != (f->mode&DMDIR)) |
| warn("inconsistent mode for %q", f->new); |
| else |
| d->mode = f->mode; |
| } |
| if(!uptodate(d, newfile)){ |
| if(verb && (fskind != Archive && ream == 0)) |
| fprint(2, "%q\n", f->new); |
| if(d->mode & DMDIR) |
| mkdir(d); |
| else |
| copy(d); |
| }else if(modes){ |
| nulldir(&nd); |
| nd.mode = d->mode; |
| nd.gid = d->gid; |
| nd.mtime = d->mtime; |
| if(verb && (fskind != Archive && ream == 0)) |
| fprint(2, "%q\n", f->new); |
| if(dirwstat(newfile, &nd) < 0) |
| warn("can't set modes for %q: %r", f->new); |
| nulldir(&nd); |
| nd.uid = d->uid; |
| dirwstat(newfile, &nd); |
| } |
| return (d->mode & DMDIR) != 0; |
| } |
| |
| /* |
| * check if file to is up to date with |
| * respect to the file represented by df |
| */ |
| int |
| uptodate(Dir *df, char *to) |
| { |
| int ret; |
| Dir *dt; |
| |
| if(fskind == Archive || ream || (dt = dirstat(to)) == nil) |
| return 0; |
| ret = dt->mtime >= df->mtime; |
| free(dt); |
| return ret; |
| } |
| |
| void |
| copy(Dir *d) |
| { |
| char cptmp[LEN], *p; |
| int f, t, n, needwrite, nowarnyet = 1; |
| vlong tot, len; |
| Dir nd; |
| |
| f = open(oldfile, OREAD); |
| if(f < 0){ |
| warn("can't open %q: %r", oldfile); |
| return; |
| } |
| t = -1; |
| if(fskind == Archive) |
| arch(d); |
| else{ |
| strcpy(cptmp, newfile); |
| p = utfrrune(cptmp, L'/'); |
| if(!p) |
| error("internal temporary file error"); |
| strcpy(p+1, "__mkfstmp"); |
| t = create(cptmp, OWRITE, 0666); |
| if(t < 0){ |
| warn("can't create %q: %r", newfile); |
| close(f); |
| return; |
| } |
| } |
| |
| needwrite = 0; |
| for(tot = 0; tot < d->length; tot += n){ |
| len = d->length - tot; |
| /* don't read beyond d->length */ |
| if (len > buflen) |
| len = buflen; |
| n = read(f, buf, len); |
| if(n <= 0) { |
| if(n < 0 && nowarnyet) { |
| warn("can't read %q: %r", oldfile); |
| nowarnyet = 0; |
| } |
| /* |
| * don't quit: pad to d->length (in pieces) to agree |
| * with the length in the header, already emitted. |
| */ |
| memset(buf, 0, len); |
| n = len; |
| } |
| if(fskind == Archive){ |
| if(Bwrite(&bout, buf, n) != n) |
| error("write error: %r"); |
| }else if(memcmp(buf, zbuf, n) == 0){ |
| if(seek(t, n, 1) < 0) |
| error("can't write zeros to %q: %r", newfile); |
| needwrite = 1; |
| }else{ |
| if(write(t, buf, n) < n) |
| error("can't write %q: %r", newfile); |
| needwrite = 0; |
| } |
| } |
| close(f); |
| if(needwrite){ |
| if(seek(t, -1, 1) < 0 || write(t, zbuf, 1) != 1) |
| error("can't write zero at end of %q: %r", newfile); |
| } |
| if(tot != d->length){ |
| /* this should no longer happen */ |
| warn("wrong number of bytes written to %q (was %lld should be %lld)\n", |
| newfile, tot, d->length); |
| if(fskind == Archive){ |
| warn("seeking to proper position\n"); |
| /* does no good if stdout is a pipe */ |
| Bseek(&bout, d->length - tot, 1); |
| } |
| } |
| if(fskind == Archive) |
| return; |
| remove(newfile); |
| nulldir(&nd); |
| nd.mode = d->mode; |
| nd.gid = d->gid; |
| nd.mtime = d->mtime; |
| nd.name = d->name; |
| if(dirfwstat(t, &nd) < 0) |
| error("can't move tmp file to %q: %r", newfile); |
| nulldir(&nd); |
| nd.uid = d->uid; |
| dirfwstat(t, &nd); |
| close(t); |
| } |
| |
| void |
| mkdir(Dir *d) |
| { |
| Dir *d1; |
| Dir nd; |
| int fd; |
| |
| if(fskind == Archive){ |
| arch(d); |
| return; |
| } |
| fd = create(newfile, OREAD, d->mode); |
| nulldir(&nd); |
| nd.mode = d->mode; |
| nd.gid = d->gid; |
| nd.mtime = d->mtime; |
| if(fd < 0){ |
| if((d1 = dirstat(newfile)) == nil || !(d1->mode & DMDIR)){ |
| free(d1); |
| error("can't create %q", newfile); |
| } |
| free(d1); |
| if(dirwstat(newfile, &nd) < 0) |
| warn("can't set modes for %q: %r", newfile); |
| nulldir(&nd); |
| nd.uid = d->uid; |
| dirwstat(newfile, &nd); |
| return; |
| } |
| if(dirfwstat(fd, &nd) < 0) |
| warn("can't set modes for %q: %r", newfile); |
| nulldir(&nd); |
| nd.uid = d->uid; |
| dirfwstat(fd, &nd); |
| close(fd); |
| } |
| |
| void |
| arch(Dir *d) |
| { |
| Bprint(&bout, "%q %luo %q %q %lud %lld\n", |
| newfile, d->mode, d->uid, d->gid, d->mtime, d->length); |
| } |
| |
| char * |
| mkpath(char *prefix, char *elem) |
| { |
| char *p; |
| int n; |
| |
| n = strlen(prefix) + strlen(elem) + 2; |
| p = emalloc(n); |
| sprint(p, "%s/%s", prefix, elem); |
| return p; |
| } |
| |
| char * |
| strdup(char *s) |
| { |
| char *t; |
| |
| t = emalloc(strlen(s) + 1); |
| return strcpy(t, s); |
| } |
| |
| void |
| setnames(File *f) |
| { |
| sprint(newfile, "%s%s", newroot, f->new); |
| if(f->old){ |
| if(f->old[0] == '/') |
| sprint(oldfile, "%s%s", oldroot, f->old); |
| else |
| strcpy(oldfile, f->old); |
| }else |
| sprint(oldfile, "%s%s", oldroot, f->new); |
| if(strlen(newfile) >= sizeof newfile |
| || strlen(oldfile) >= sizeof oldfile) |
| error("name overfile"); |
| } |
| |
| 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 |
| */ |
| void |
| skipdir(void) |
| { |
| char *p, c; |
| int level; |
| |
| if(indent < 0 || b == nil) /* b is nil when copying adm/users */ |
| return; |
| level = indent; |
| for(;;){ |
| indent = 0; |
| p = Brdline(b, '\n'); |
| lineno++; |
| if(!p){ |
| indent = -1; |
| return; |
| } |
| while((c = *p++) != '\n') |
| if(c == ' ') |
| indent++; |
| else if(c == '\t') |
| indent += 8; |
| else |
| break; |
| if(indent <= level){ |
| Bseek(b, -Blinelen(b), 1); |
| lineno--; |
| return; |
| } |
| } |
| } |
| |
| File* |
| getfile(File *old) |
| { |
| File *f; |
| char *elem; |
| char *p; |
| int c; |
| |
| if(indent < 0) |
| return 0; |
| loop: |
| indent = 0; |
| p = Brdline(b, '\n'); |
| lineno++; |
| if(!p){ |
| indent = -1; |
| return 0; |
| } |
| while((c = *p++) != '\n') |
| if(c == ' ') |
| indent++; |
| else if(c == '\t') |
| indent += 8; |
| else |
| break; |
| if(c == '\n' || c == '#') |
| goto loop; |
| p--; |
| f = emalloc(sizeof *f); |
| p = getname(p, &elem); |
| if(debug) |
| fprint(2, "getfile: %q root %q\n", elem, old->new); |
| f->new = mkpath(old->new, elem); |
| f->elem = utfrrune(f->new, L'/') + 1; |
| p = getmode(p, &f->mode); |
| p = getname(p, &f->uid); |
| if(!*f->uid) |
| f->uid = "-"; |
| p = getname(p, &f->gid); |
| if(!*f->gid) |
| f->gid = "-"; |
| f->old = getpath(p); |
| if(f->old && strcmp(f->old, "-") == 0){ |
| free(f->old); |
| f->old = 0; |
| } |
| setnames(f); |
| |
| if(debug) |
| printfile(f); |
| |
| return f; |
| } |
| |
| char* |
| getpath(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(n + 1); |
| memcpy(new, p, n); |
| new[n] = 0; |
| return new; |
| } |
| |
| char* |
| getname(char *p, char **buf) |
| { |
| char *s, *start; |
| int c; |
| |
| while((c = *p) == ' ' || c == '\t') |
| p++; |
| |
| start = p; |
| while((c = *p) != '\n' && c != ' ' && c != '\t' && c != '\0') |
| p++; |
| |
| *buf = malloc(p+1-start); |
| if(*buf == nil) |
| return nil; |
| memmove(*buf, start, p-start); |
| (*buf)[p-start] = '\0'; |
| |
| if(**buf == '$'){ |
| s = getenv(*buf+1); |
| if(s == 0){ |
| warn("can't read environment variable %q", *buf+1); |
| skipdir(); |
| free(*buf); |
| return nil; |
| } |
| free(*buf); |
| *buf = s; |
| } |
| return p; |
| } |
| |
| char* |
| getmode(char *p, ulong *xmode) |
| { |
| char *buf, *s; |
| ulong m; |
| |
| *xmode = ~0; |
| p = getname(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("bad mode specification %q", buf); |
| free(buf); |
| return p; |
| } |
| *xmode = m | strtoul(s, 0, 8); |
| free(buf); |
| return p; |
| } |
| |
| void |
| setusers(void) |
| { |
| File file; |
| int m; |
| |
| if(fskind != Kfs) |
| return; |
| m = modes; |
| modes = 1; |
| file.uid = "adm"; |
| file.gid = "adm"; |
| file.mode = DMDIR|0775; |
| file.new = "/adm"; |
| file.elem = "adm"; |
| file.old = 0; |
| setnames(&file); |
| strcpy(oldfile, file.new); /* Don't use root for /adm */ |
| mkfile(&file); |
| file.new = "/adm/users"; |
| file.old = users; |
| file.elem = "users"; |
| file.mode = 0664; |
| setnames(&file); |
| if (file.old) |
| strcpy(oldfile, file.old); /* Don't use root for /adm/users */ |
| mkfile(&file); |
| kfscmd("user"); |
| mkfile(&file); |
| file.mode = DMDIR|0775; |
| file.new = "/adm"; |
| file.old = "/adm"; |
| file.elem = "adm"; |
| setnames(&file); |
| strcpy(oldfile, file.old); /* Don't use root for /adm */ |
| mkfile(&file); |
| modes = m; |
| } |
| |
| void |
| mountkfs(char *name) |
| { |
| if(fskind != Kfs) |
| return; |
| sysfatal("no kfs: use -a or -d"); |
| } |
| |
| void |
| kfscmd(char *cmd) |
| { |
| char buf[4*1024]; |
| int n; |
| |
| if(fskind != Kfs) |
| return; |
| if(write(sfd, cmd, strlen(cmd)) != strlen(cmd)){ |
| fprint(2, "%q: error writing %q: %r", prog, cmd); |
| return; |
| } |
| for(;;){ |
| n = read(sfd, buf, sizeof buf - 1); |
| if(n <= 0) |
| return; |
| buf[n] = '\0'; |
| if(strcmp(buf, "done") == 0 || strcmp(buf, "success") == 0) |
| return; |
| if(strcmp(buf, "unknown command") == 0){ |
| fprint(2, "%q: command %q not recognized\n", prog, cmd); |
| return; |
| } |
| } |
| } |
| |
| void * |
| emalloc(ulong n) |
| { |
| void *p; |
| |
| if((p = malloc(n)) == 0) |
| error("out of memory"); |
| return p; |
| } |
| |
| void |
| error(char *fmt, ...) |
| { |
| char buf[1024]; |
| va_list arg; |
| |
| sprint(buf, "%q: %q:%d: ", prog, proto, lineno); |
| va_start(arg, fmt); |
| vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg); |
| va_end(arg); |
| fprint(2, "%s\n", buf); |
| kfscmd("disallow"); |
| kfscmd("sync"); |
| exits(0); |
| } |
| |
| void |
| warn(char *fmt, ...) |
| { |
| char buf[1024]; |
| va_list arg; |
| |
| sprint(buf, "%q: %q:%d: ", prog, proto, lineno); |
| va_start(arg, fmt); |
| vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg); |
| va_end(arg); |
| fprint(2, "%s\n", buf); |
| } |
| |
| void |
| printfile(File *f) |
| { |
| if(f->old) |
| fprint(2, "%q from %q %q %q %lo\n", f->new, f->old, f->uid, f->gid, f->mode); |
| else |
| fprint(2, "%q %q %q %lo\n", f->new, f->uid, f->gid, f->mode); |
| } |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: %q [-aprvx] [-d root] [-n name] [-s source] [-u users] [-z n] proto ...\n", prog); |
| exits("usage"); |
| } |