#include "all.h" enum{ Dst, Src, Osrc, }; int lineno; int errors; int errorexit = 1; int donothing; int verbose; char *root[3]; char nbuf[3][10240]; char *mkname(char*, int, char*, char*); int copyfile(char*, char*, char*, Dir*, int, int*); void chat(char *f, ...) { Fmt fmt; char buf[256]; va_list arg; if(!verbose) return; fmtfdinit(&fmt, 1, buf, sizeof buf); va_start(arg, f); fmtvprint(&fmt, f, arg); va_end(arg); fmtfdflush(&fmt); } void fatal(char *f, ...) { char *s; va_list arg; va_start(arg, f); s = vsmprint(f, arg); va_end(arg); fprint(2, "%d: error: %s\n", lineno, s); free(s); exits("errors"); } void error(char *f, ...) { char *s; va_list arg; va_start(arg, f); s = vsmprint(f, arg); va_end(arg); fprint(2, "%d: error: %s\n", lineno, s); free(s); errors = 1; if(errorexit) exits("errors"); } void warn(char *f, ...) { char *s; va_list arg; va_start(arg, f); s = vsmprint(f, arg); va_end(arg); fprint(2, "%d: warn: %s\n", lineno, s); free(s); } int reason(char *s) { char e[ERRMAX]; rerrstr(e, sizeof e); if(strstr(s, e) == 0) return 0; return -1; } char* mkname(char *buf, int nbuf, char *a, char *b) { if(strlen(a)+strlen(b)+2 > nbuf) fatal("name too long"); strcpy(buf, a); if(a[strlen(a)-1] != '/') strcat(buf, "/"); strcat(buf, b); return buf; } int isdir(char *s) { ulong m; Dir *d; if((d = dirstat(s)) == nil) return 0; m = d->mode; free(d); return (m&DMDIR) != 0; } void usage(void) { fprint(2, "usage: replica/cphist [-nv] dstroot newsrcroot oldsrcroot < log\n"); exits("usage"); } void notexists(char *path) { if(access(path, AEXIST) == -1) fatal("%r"); } void updatestat(char *name, int fd, Dir *t) { if(dirfwstat(fd, t) < 0) error("dirfwstat %q: %r", name); } long preadn(int fd, void *av, long n, vlong p) { char *a; long m, t; a = av; t = 0; while(t < n){ m = pread(fd, a+t, n-t, p); if(m <= 0){ if(t == 0) return m; break; } t += m; p += m; } return t; } char buf[8192]; char obuf[8192]; int copy(int fdf, int fdt, char *from, char *to, vlong p) { long n; while(n = pread(fdf, buf, sizeof buf, p)){ if(n < 0) error("reading %s: %r", from); if(pwrite(fdt, buf, n, p) != n) error("writing %s: %r\n", to); p += n; // len -= n; } return 0; } int change0(int fd[3], char *name[3], Dir *d[3]) { vlong o, sz; long n; if(d[Osrc]->qid.path != d[Src]->qid.path){ close(fd[Dst]); if(remove(name[Dst]) == -1) fatal("can't remove old file %q: %r", name[Dst]); if((fd[Dst] = create(name[Dst], ORDWR, 0666)) == -1) fatal("create %q: %r", name[Dst]); return copy(fd[Src], fd[Dst], name[Src], name[Dst], 0); } if((d[Osrc]->qid.type&QTAPPEND) && (d[Src]->qid.type&QTAPPEND)) return copy(fd[Src], fd[Dst], name[Src], name[Dst], d[Osrc]->length); /* hard case. we need to copy only changed blocks */ sz = d[Src]->length; if(sz > d[Osrc]->length) sz = d[Osrc]->length; for(o = 0; o < sz; o += n){ n = 8192; if(n > sz-o) n = sz-o; if(preadn(fd[Src], buf, n, o) != n) fatal("pread %q: short read", name[Src]); if(preadn(fd[Osrc], obuf, n, o) != n) fatal("pread %q: short read", name[Src]); if(memcmp(buf, obuf, n) != 0){ if(pwrite(fd[Dst], buf, n, o) != n) fatal("pwrite %q: %r", name[Dst]); } } if(sz < d[Src]->length) return copy(fd[Src], fd[Dst], name[Src], name[Dst], sz); return 0; } static char muid[28]; void assignmuid(int fd, char *name, Dir *rd) { Dir *d; if((d = dirfstat(fd)) == 0) error("dirfstat %q: %r", name); strecpy(muid, muid+sizeof muid-1, d->muid); rd->muid = muid; free(d); } int change(int fd[3], char *name[3], Dir *rd) { Dir *d[3]; int n, i; for(i = 0; i < 3; i++) if((d[i] = dirfstat(fd[i])) == 0) error("dirfstat %q: %r", name[i]); n = change0(fd, name, d); strecpy(muid, muid+sizeof muid-1, d[Src]->muid); rd->muid = muid; for(i = 0; i < 3; i++) free(d[i]); return n; } char * basename(char *name) { char *r; r = strrchr(name, '/'); if(r) return r+1; return name; } int mtab[] = { [Dst] ORDWR, [Src] OREAD, [Osrc] OREAD, }; void doclose(int fd[3]) { for(int i = 0; i < 3; i++){ close(fd[i]); fd[i] = -1; } } void fddump(char *file[3], int fd[3]) { for(int i = 0; i < 3; i++) print(">> %s: %d %d %.3s\n", file[i], fd[i], mtab[i], "dstsrcosr"+3*i); } int doopen(char *file[3], int fd[3]) { int i; for(i = 0; i < 3; i++){ if((fd[i] = open(file[i], mtab[i])) == -1){ if(reason("file is locked") == -1) error("open: %r", file[i]); else{ // gruesome hack to get around exclusive mode files. if(i == 2) fd[2] = dup(fd[1], -1); if(fd[i] == -1) warn("open %d: %r", mtab[i], file[i]); else continue; } fddump(file, fd); doclose(fd); error("open: %r", file[i]); return -1; } } return 0; } void main(int argc, char **argv) { char *f[10], *name, *s, *t, verb, *file[3]; int fd[3], i, nf, line0; Dir rd; Biobuf bin; quotefmtinstall(); line0 = 0; ARGBEGIN{ case 'n': donothing = 1; verbose = 1; break; case 'e': errorexit ^= 1; break; case 'l': line0 = atoi(EARGF(usage())); break; case 'v': verbose++; break; default: usage(); }ARGEND if(argc != 3) usage(); for(i = 0; i < 3; i++) if(!isdir(root[i] = argv[i])) fatal("bad root directory %q", argv[i]); Binit(&bin, 0, OREAD); for(; s=Brdstr(&bin, '\n', 1); free(s)){ t = estrdup(s); nf = tokenize(s, f, nelem(f)); if(nf != 10 || strlen(f[2]) != 1){ error("bad log entry <%s>\n", t); free(t); continue; } free(t); // now = strtoul(f[0], 0, 0); lineno = atoi(f[1]); if(lineno < line0) continue; verb = f[2][0]; name = f[3]; file[Dst] = mkname(nbuf[Dst], sizeof nbuf[Dst], root[Dst], name); if(strcmp(f[4], "-") == 0) f[4] = f[3]; file[Src] = mkname(nbuf[Src], sizeof nbuf[Src], root[Src], f[4]); file[Osrc] = mkname(nbuf[Osrc], sizeof nbuf[Osrc], root[Osrc], f[4]); nulldir(&rd); // rd.name = basename(f[4]); rd.mode = strtoul(f[5], 0, 8); rd.uid = f[6]; rd.gid = f[7]; rd.mtime = strtoul(f[8], 0, 10); rd.length = strtoll(f[9], 0, 10); switch(verb){ case 'd': chat("d %q\n", name); if(donothing) break; if(remove(file[Dst]) == -1){ if(access(file[Dst], AEXIST) == -1) warn("remove: %r", file[Dst]); else error("remove: %r", file[Dst]); continue; } break; case 'a': chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); if(donothing) break; if((fd[Src] = open(file[Src], OREAD)) == -1) fatal("open %q: %r", file[Src]); if(rd.mode&DMDIR) i = OREAD|OEXEC; else i = ORDWR; if((fd[Dst] = create(file[Dst], i, rd.mode)) == -1) fatal("create: %r"); if((rd.mode&DMDIR) == 0) copy(fd[Src], fd[Dst], file[Src], file[Dst], 0); assignmuid(fd[Src], file[Src], &rd); updatestat(file[Dst], fd[Dst], &rd); close(fd[Src]); close(fd[Dst]); break; case 'c': /* change contents */ chat("c %q\n", name); if(donothing) break; werrstr(""); if(doopen(file, fd) == -1) continue; change(fd, file, &rd); updatestat(file[Dst], fd[Dst], &rd); doclose(fd); break; case 'm': /* change metadata */ notexists(file[Src]); chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); if(donothing) break; if((fd[Src] = open(file[Src], OREAD)) < 0) fatal("open %q: %r", file[Src]); if((fd[Dst] = open(file[Dst], OREAD)) < 0) fatal("open %q: %r", file[Dst]); if(rd.mode&DMDIR) rd.length = ~0ULL; assignmuid(fd[Src], file[Src], &rd); updatestat(file[Dst], fd[Dst], &rd); close(fd[Src]); close(fd[Dst]); break; } } if(errors) exits("errors"); exits(nil); }