diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/replica/applylog.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/replica/applylog.c')
-rwxr-xr-x | sys/src/cmd/replica/applylog.c | 1253 |
1 files changed, 1253 insertions, 0 deletions
diff --git a/sys/src/cmd/replica/applylog.c b/sys/src/cmd/replica/applylog.c new file mode 100755 index 000000000..183011400 --- /dev/null +++ b/sys/src/cmd/replica/applylog.c @@ -0,0 +1,1253 @@ +#include "all.h" + +#define Nwork 16 + +int localdirstat(char*, Dir*); +int ismatch(char*); +void conflict(char*, char*, ...); +void error(char*, ...); +int isdir(char*); + +void worker(int fdf, int fdt, char *from, char *to); +vlong nextoff(void); +void failure(void *, char *note); + +QLock lk; +vlong off; + +int errors; +int nconf; +int donothing; +int verbose; +char **match; +int nmatch; +int tempspool = 1; +int safeinstall = 1; +char *lroot; +char *rroot; +Db *clientdb; +int skip; +int douid; +char *mkname(char*, int, char*, char*); +char localbuf[10240]; +char remotebuf[10240]; +int copyfile(char*, char*, char*, Dir*, int, int*); +ulong maxnow; +int maxn; +char *timefile; +int timefd; +int samecontents(char*, char*); + +Db *copyerr; + +typedef struct Res Res; +struct Res +{ + char c; + char *name; +}; + +Res *res; +int nres; + +void +addresolve(int c, char *name) +{ + if(name[0] == '/') + name++; + res = erealloc(res, (nres+1)*sizeof res[0]); + res[nres].c = c; + res[nres].name = name; + nres++; +} + +int +resolve(char *name) +{ + int i, len; + + for(i=0; i<nres; i++){ + len = strlen(res[i].name); + if(len == 0) + return res[i].c; + if(strncmp(name, res[i].name, len) == 0 && (name[len]=='/' || name[len] == 0)) + return res[i].c; + } + return '?'; +} + +void +readtimefile(void) +{ + int n; + char buf[24]; + + if((timefd = open(timefile, ORDWR)) < 0 + && (timefd = create(timefile, ORDWR|OEXCL, 0666)) < 0) + return; + + n = readn(timefd, buf, sizeof buf); + if(n < sizeof buf) + return; + + maxnow = atoi(buf); + maxn = atoi(buf+12); +} + +void +writetimefile(void) +{ + char buf[24+1]; + + snprint(buf, sizeof buf, "%11lud %11d ", maxnow, maxn); + pwrite(timefd, buf, 24, 0); +} + +static void membogus(char**); + +void +addce(char *local) +{ + char e[ERRMAX]; + Dir d; + + memset(&d, 0, sizeof d); + rerrstr(e, sizeof e); + d.name = atom(e); + d.uid = ""; + d.gid = ""; + insertdb(copyerr, atom(local), &d); +} + +void +delce(char *local) +{ + removedb(copyerr, local); +} + +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 +usage(void) +{ + fprint(2, "usage: replica/applylog [-cnSstuv] [-T timefile] clientdb clientroot serverroot [path ...]\n"); + exits("usage"); +} + +int +notexists(char *path) +{ + char buf[ERRMAX]; + + if(access(path, AEXIST) >= 0) + return 0; + + rerrstr(buf, sizeof buf); + if(strstr(buf, "entry not found") || strstr(buf, "not exist")) + return 1; + + /* some other error, like network hangup */ + return 0; +} + +void +main(int argc, char **argv) +{ + char *f[10], *local, *name, *remote, *s, *t, verb; + int fd, havedb, havelocal, i, k, n, nf, resolve1, skip; + int checkedmatch1, checkedmatch2, + checkedmatch3, checkedmatch4; + ulong now; + Biobuf bin; + Dir dbd, ld, nd, rd; + Avlwalk *w; + Entry *e; + + membogus(argv); + quotefmtinstall(); + ARGBEGIN{ + case 's': + case 'c': + i = ARGC(); + addresolve(i, EARGF(usage())); + break; + case 'n': + donothing = 1; + verbose = 1; + break; + case 'S': + safeinstall = 0; + break; + case 'T': + timefile = EARGF(usage()); + break; + case 't': + tempspool = 0; + break; + case 'u': + douid = 1; + break; + case 'v': + verbose++; + break; + default: + usage(); + }ARGEND + + if(argc < 3) + usage(); + + if(timefile) + readtimefile(); + + lroot = argv[1]; + if(!isdir(lroot)) + sysfatal("bad local root directory"); + rroot = argv[2]; + if(!isdir(rroot)) + sysfatal("bad remote root directory"); + + match = argv+3; + nmatch = argc-3; + for(i=0; i<nmatch; i++) + if(match[i][0] == '/') + match[i]++; + + if((clientdb = opendb(argv[0])) == nil) + sysfatal("opendb %q: %r", argv[2]); + + copyerr = opendb(nil); + + skip = 0; + 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){ + skip = 1; + fprint(2, "warning: skipping bad log entry <%s>\n", t); + free(t); + continue; + } + free(t); + now = strtoul(f[0], 0, 0); + n = atoi(f[1]); + verb = f[2][0]; + name = f[3]; + if(now < maxnow || (now==maxnow && n <= maxn)) + continue; + local = mkname(localbuf, sizeof localbuf, lroot, name); + if(strcmp(f[4], "-") == 0) + f[4] = f[3]; + remote = mkname(remotebuf, sizeof remotebuf, rroot, f[4]); + rd.name = 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); + havedb = finddb(clientdb, name, &dbd)>=0; + havelocal = localdirstat(local, &ld)>=0; + + resolve1 = resolve(name); + + /* + * if(!ismatch(name)){ + * skip = 1; + * continue; + * } + * + * This check used to be right here, but we want + * the time to be able to move forward past entries + * that don't match and have already been applied. + * So now every path below must checked !ismatch(name) + * before making any changes to the local file + * system. The fake variable checkedmatch + * tracks whether !ismatch(name) has been checked. + * If the compiler doesn't produce any used/set + * warnings, then all the paths should be okay. + * Even so, we have the asserts to fall back on. + */ + switch(verb){ + case 'd': /* delete file */ + delce(local); + if(!havelocal) /* doesn't exist; who cares? */ + break; + if(access(remote, AEXIST) >= 0) /* got recreated! */ + break; + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch1); + if(!havedb){ + if(resolve1 == 's') + goto DoRemove; + else if(resolve1 == 'c') + goto DoRemoveDb; + conflict(name, "locally created; will not remove"); + skip = 1; + continue; + } + assert(havelocal && havedb); + if(dbd.mtime > rd.mtime) /* we have a newer file than what was deleted */ + break; + if(samecontents(local, remote) > 0){ /* going to get recreated */ + chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + break; + } + if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* locally modified since we downloaded it */ + if(resolve1 == 's') + goto DoRemove; + else if(resolve1 == 'c') + break; + conflict(name, "locally modified; will not remove"); + skip = 1; + continue; + } + DoRemove: + USED(checkedmatch1); + assert(ismatch(name)); + chat("d %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + if(donothing) + break; + if(remove(local) < 0){ + error("removing %q: %r", name); + skip = 1; + continue; + } + DoRemoveDb: + USED(checkedmatch1); + assert(ismatch(name)); + removedb(clientdb, name); + break; + + case 'a': /* add file */ + if(!havedb){ + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch2); + if(!havelocal) + goto DoCreate; + if((ld.mode&DMDIR) && (rd.mode&DMDIR)) + break; + if(samecontents(local, remote) > 0){ + chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + goto DoCreateDb; + } + if(resolve1 == 's') + goto DoCreate; + else if(resolve1 == 'c') + goto DoCreateDb; + conflict(name, "locally created; will not overwrite"); + skip = 1; + continue; + } + assert(havedb); + if(dbd.mtime >= rd.mtime) /* already created this file; ignore */ + break; + if(havelocal){ + if((ld.mode&DMDIR) && (rd.mode&DMDIR)) + break; + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch2); + if(samecontents(local, remote) > 0){ + chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + goto DoCreateDb; + } + if(dbd.mtime==ld.mtime && dbd.length==ld.length) + goto DoCreate; + if(resolve1=='s') + goto DoCreate; + else if(resolve1 == 'c') + break; + conflict(name, "locally modified; will not overwrite"); + skip = 1; + continue; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch2); + DoCreate: + USED(checkedmatch2); + assert(ismatch(name)); + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break;; + } + chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + if(donothing) + break; + if(rd.mode&DMDIR){ + fd = create(local, OREAD, DMDIR); + if(fd < 0 && isdir(local)) + fd = open(local, OREAD); + if(fd < 0){ + error("mkdir %q: %r", name); + skip = 1; + continue; + } + nulldir(&nd); + nd.mode = rd.mode; + if(dirfwstat(fd, &nd) < 0) + fprint(2, "warning: cannot set mode on %q\n", local); + nulldir(&nd); + nd.gid = rd.gid; + if(dirfwstat(fd, &nd) < 0) + fprint(2, "warning: cannot set gid on %q\n", local); + if(douid){ + nulldir(&nd); + nd.uid = rd.uid; + if(dirfwstat(fd, &nd) < 0) + fprint(2, "warning: cannot set uid on %q\n", local); + } + close(fd); + rd.mtime = now; + }else{ + if(copyfile(local, remote, name, &rd, 1, &k) < 0){ + if(k) + addce(local); + skip = 1; + continue; + } + } + DoCreateDb: + USED(checkedmatch2); + assert(ismatch(name)); + insertdb(clientdb, name, &rd); + break; + + case 'c': /* change contents */ + if(!havedb){ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch3); + if(resolve1 == 's') + goto DoCopy; + else if(resolve1=='c') + goto DoCopyDb; + if(samecontents(local, remote) > 0){ + chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + goto DoCopyDb; + } + if(havelocal) + conflict(name, "locally created; will not update"); + else + conflict(name, "not replicated; will not update"); + skip = 1; + continue; + } + if(dbd.mtime >= rd.mtime) /* already have/had this version; ignore */ + break; + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch3); + if(!havelocal){ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(resolve1 == 's') + goto DoCopy; + else if(resolve1 == 'c') + break; + conflict(name, "locally removed; will not update"); + skip = 1; + continue; + } + assert(havedb && havelocal); + if(dbd.mtime != ld.mtime || dbd.length != ld.length){ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(samecontents(local, remote) > 0){ + chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + goto DoCopyDb; + } + if(resolve1 == 's') + goto DoCopy; + else if(resolve1 == 'c') + break; + conflict(name, "locally modified; will not update [%llud %lud -> %llud %lud]", dbd.length, dbd.mtime, ld.length, ld.mtime); + skip = 1; + continue; + } + DoCopy: + USED(checkedmatch3); + assert(ismatch(name)); + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + chat("c %q\n", name); + if(donothing) + break; + if(copyfile(local, remote, name, &rd, 0, &k) < 0){ + if(k) + addce(local); + skip = 1; + continue; + } + DoCopyDb: + USED(checkedmatch3); + assert(ismatch(name)); + if(!havedb){ + if(havelocal) + dbd = ld; + else + dbd = rd; + } + dbd.mtime = rd.mtime; + dbd.length = rd.length; + insertdb(clientdb, name, &dbd); + break; + + case 'm': /* change metadata */ + if(!havedb){ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch4); + if(resolve1 == 's'){ + USED(checkedmatch4); + SET(checkedmatch2); + goto DoCreate; + } + else if(resolve1 == 'c') + goto DoMetaDb; + if(havelocal) + conflict(name, "locally created; will not update metadata"); + else + conflict(name, "not replicated; will not update metadata"); + skip = 1; + continue; + } + if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime) /* have newer version; ignore */ + break; + if((dbd.mode&DMDIR) && dbd.mtime > now) + break; + if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode) + break; + if(!havelocal){ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch4); + if(resolve1 == 's'){ + USED(checkedmatch4); + SET(checkedmatch2); + goto DoCreate; + } + else if(resolve1 == 'c') + break; + conflict(name, "locally removed; will not update metadata"); + skip = 1; + continue; + } + if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* this check might be overkill */ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch4); + if(resolve1 == 's' || samecontents(local, remote) > 0) + goto DoMeta; + else if(resolve1 == 'c') + break; + conflict(name, "contents locally modified (%s); will not update metadata to %s %s %luo", + dbd.mtime != ld.mtime ? "mtime" : + dbd.length != ld.length ? "length" : + "unknown", + rd.uid, rd.gid, rd.mode); + skip = 1; + continue; + } + if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){ + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch4); + if(resolve1 == 's') + goto DoMeta; + else if(resolve1 == 'c') + break; + conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode); + skip = 1; + continue; + } + if(!ismatch(name)){ + if(!skip) + fprint(2, "stopped updating log apply time because of %s\n", name); + skip = 1; + continue; + } + SET(checkedmatch4); + DoMeta: + USED(checkedmatch4); + assert(ismatch(name)); + if(notexists(remote)){ + addce(local); + /* no skip=1 */ + break; + } + chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); + if(donothing) + break; + nulldir(&nd); + nd.gid = rd.gid; + nd.mode = rd.mode; + if(douid) + nd.uid = rd.uid; + if(dirwstat(local, &nd) < 0){ + error("dirwstat %q: %r", name); + skip = 1; + continue; + } + DoMetaDb: + USED(checkedmatch4); + assert(ismatch(name)); + if(!havedb){ + if(havelocal) + dbd = ld; + else + dbd = rd; + } + if(dbd.mode&DMDIR) + dbd.mtime = now; + dbd.gid = rd.gid; + dbd.mode = rd.mode; + if(douid) + dbd.uid = rd.uid; + insertdb(clientdb, name, &dbd); + break; + } + if(!skip && !donothing){ + maxnow = now; + maxn = n; + } + } + + w = avlwalk(copyerr->avl); + while(e = (Entry*)avlnext(w)) + error("copying %q: %s\n", e->name, e->d.name); + + if(timefile) + writetimefile(); + if(nconf) + exits("conflicts"); + + if(errors) + exits("errors"); + exits(nil); +} + + +char* +mkname(char *buf, int nbuf, char *a, char *b) +{ + if(strlen(a)+strlen(b)+2 > nbuf) + sysfatal("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 +conflict(char *name, char *f, ...) +{ + char *s; + va_list arg; + + va_start(arg, f); + s = vsmprint(f, arg); + va_end(arg); + + fprint(2, "! %s: %s\n", name, s); + free(s); + + nconf++; +} + +void +error(char *f, ...) +{ + char *s; + va_list arg; + + va_start(arg, f); + s = vsmprint(f, arg); + va_end(arg); + fprint(2, "error: %s\n", s); + free(s); + errors = 1; +} + +int +ismatch(char *s) +{ + int i, len; + + if(nmatch == 0) + return 1; + for(i=0; i<nmatch; i++){ + len = strlen(match[i]); + if(len == 0) + return 1; + if(strncmp(s, match[i], len) == 0 && (s[len]=='/' || s[len] == 0)) + return 1; + } + return 0; +} + +int +localdirstat(char *name, Dir *d) +{ + static Dir *d2; + + free(d2); + if((d2 = dirstat(name)) == nil) + return -1; + *d = *d2; + return 0; +} + +enum { DEFB = 8192 }; + +static int +cmp1(int fd1, int fd2) +{ + char buf1[DEFB]; + char buf2[DEFB]; + int n1, n2; + + for(;;){ + n1 = readn(fd1, buf1, DEFB); + n2 = readn(fd2, buf2, DEFB); + if(n1 < 0 || n2 < 0) + return -1; + if(n1 != n2) + return 0; + if(n1 == 0) + return 1; + if(memcmp(buf1, buf2, n1) != 0) + return 0; + } +} + +static int +copy1(int fdf, int fdt, char *from, char *to) +{ + int i, n, rv, pid[Nwork]; + Waitmsg *w; + + n = 0; + off = 0; + for(i=0; i<Nwork; i++){ + switch(pid[n] = rfork(RFPROC|RFMEM)){ + case 0: + notify(failure); + worker(fdf, fdt, from, to); + case -1: + break; + default: + n++; + break; + } + } + if(n == 0){ + fprint(2, "cp: rfork: %r\n"); + return -1; + } + + rv = 0; + while((w = wait()) != nil){ + if(w->msg[0]){ + rv = -1; + for(i=0; i<n; i++) + if(pid[i] > 0) + postnote(PNPROC, pid[i], "failure"); + } + free(w); + } + return rv; +} + +void +worker(int fdf, int fdt, char *from, char *to) +{ + char buf[DEFB], *bp; + long len, n; + vlong o; + + len = sizeof(buf); + bp = buf; + o = nextoff(); + + while(n = pread(fdf, bp, len, o)){ + if(n < 0){ + fprint(2, "reading %s: %r\n", from); + _exits("bad"); + } + if(pwrite(fdt, buf, n, o) != n){ + fprint(2, "writing %s: %r\n", to); + _exits("bad"); + } + bp += n; + o += n; + len -= n; + if(len == 0){ + len = sizeof buf; + bp = buf; + o = nextoff(); + } + } + _exits(nil); +} + +vlong +nextoff(void) +{ + vlong o; + + qlock(&lk); + o = off; + off += DEFB; + qunlock(&lk); + + return o; +} + +void +failure(void*, char *note) +{ + if(strcmp(note, "failure") == 0) + _exits(nil); + noted(NDFLT); +} + + +static int +opentemp(char *template) +{ + int fd, i; + char *p; + + p = estrdup(template); + fd = -1; + for(i=0; i<10; i++){ + mktemp(p); + if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0) + break; + strcpy(p, template); + } + if(fd < 0) + return -1; + strcpy(template, p); + free(p); + return fd; +} + +int +copyfile(char *local, char *remote, char *name, Dir *d, int dowstat, int *printerror) +{ + Dir *d0, *d1, *dl; + Dir nd; + int rfd, tfd, wfd, didcreate; + char tmp[32], *p, *safe; + char err[ERRMAX]; + +Again: + *printerror = 0; + if((rfd = open(remote, OREAD)) < 0) + return -1; + + d0 = dirfstat(rfd); + if(d0 == nil){ + close(rfd); + return -1; + } + *printerror = 1; + if(!tempspool){ + tfd = rfd; + goto DoCopy; + } + strcpy(tmp, "/tmp/replicaXXXXXXXX"); + tfd = opentemp(tmp); + if(tfd < 0){ + close(rfd); + free(d0); + return -1; + } + if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){ + close(rfd); + close(tfd); + free(d0); + return -1; + } + close(rfd); + if(d0->qid.path != d1->qid.path + || d0->qid.vers != d1->qid.vers + || d0->mtime != d1->mtime + || d0->length != d1->length){ + /* file changed underfoot; go around again */ + close(tfd); + free(d0); + free(d1); + goto Again; + } + free(d1); + if(seek(tfd, 0, 0) != 0){ + close(tfd); + free(d0); + return -1; + } + +DoCopy: + /* + * clumsy but important hack to do safeinstall-like installs. + */ + p = strchr(name, '/'); + if(safeinstall && p && strncmp(p, "/bin/", 5) == 0 && access(local, AEXIST) >= 0){ + /* + * remove bin/_targ + */ + safe = emalloc(strlen(local)+2); + strcpy(safe, local); + p = strrchr(safe, '/')+1; + memmove(p+1, p, strlen(p)+1); + p[0] = '_'; + remove(safe); /* ignore failure */ + + /* + * rename bin/targ to bin/_targ + */ + nulldir(&nd); + nd.name = p; + if(dirwstat(local, &nd) < 0) + fprint(2, "warning: rename %s to %s: %r\n", local, p); + } + + didcreate = 0; + if((dl = dirstat(local)) == nil){ + if((wfd = create(local, OWRITE, 0)) >= 0){ + didcreate = 1; + goto okay; + } + goto err; + }else{ + if((wfd = open(local, OTRUNC|OWRITE)) >= 0) + goto okay; + rerrstr(err, sizeof err); + if(strstr(err, "permission") == nil) + goto err; + nulldir(&nd); + /* + * Assume the person running pull is in the appropriate + * groups. We could set 0666 instead, but I'm worried + * about leaving the file world-readable or world-writable + * when it shouldn't be. + */ + nd.mode = dl->mode | 0660; + if(nd.mode == dl->mode) + goto err; + if(dirwstat(local, &nd) < 0) + goto err; + if((wfd = open(local, OTRUNC|OWRITE)) >= 0){ + nd.mode = dl->mode; + if(dirfwstat(wfd, &nd) < 0) + fprint(2, "warning: set mode on %s to 0660 to open; cannot set back to %luo: %r\n", local, nd.mode); + goto okay; + } + nd.mode = dl->mode; + if(dirwstat(local, &nd) < 0) + fprint(2, "warning: set mode on %s to %luo to open; open failed; cannot set mode back to %luo: %r\n", local, nd.mode|0660, nd.mode); + goto err; + } + +err: + close(tfd); + free(d0); + free(dl); + return -1; + +okay: + free(dl); + if(copy1(tfd, wfd, tmp, local) < 0){ + close(tfd); + close(wfd); + free(d0); + return -1; + } + close(tfd); + if(didcreate || dowstat){ + nulldir(&nd); + nd.mode = d->mode; + if(dirfwstat(wfd, &nd) < 0) + fprint(2, "warning: cannot set mode on %s\n", local); + nulldir(&nd); + nd.gid = d->gid; + if(dirfwstat(wfd, &nd) < 0) + fprint(2, "warning: cannot set gid on %s\n", local); + if(douid){ + nulldir(&nd); + nd.uid = d->uid; + if(dirfwstat(wfd, &nd) < 0) + fprint(2, "warning: cannot set uid on %s\n", local); + } + } + d->mtime = d0->mtime; + d->length = d0->length; + nulldir(&nd); + nd.mtime = d->mtime; + if(dirfwstat(wfd, &nd) < 0) + fprint(2, "warning: cannot set mtime on %s\n", local); + free(d0); + + close(wfd); + return 0; +} + +int +samecontents(char *local, char *remote) +{ + Dir *d0, *d1; + int rfd, tfd, lfd, ret; + char tmp[32]; + + /* quick check: sizes must match */ + d1 = nil; + if((d0 = dirstat(local)) == nil || (d1 = dirstat(remote)) == nil){ + free(d0); + free(d1); + return -1; + } + if(d0->length != d1->length){ + free(d0); + free(d1); + return 0; + } + +Again: + if((rfd = open(remote, OREAD)) < 0) + return -1; + d0 = dirfstat(rfd); + if(d0 == nil){ + close(rfd); + return -1; + } + + strcpy(tmp, "/tmp/replicaXXXXXXXX"); + tfd = opentemp(tmp); + if(tfd < 0){ + close(rfd); + free(d0); + return -1; + } + if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){ + close(rfd); + close(tfd); + free(d0); + return -1; + } + close(rfd); + if(d0->qid.path != d1->qid.path + || d0->qid.vers != d1->qid.vers + || d0->mtime != d1->mtime + || d0->length != d1->length){ + /* file changed underfoot; go around again */ + close(tfd); + free(d0); + free(d1); + goto Again; + } + free(d1); + free(d0); + if(seek(tfd, 0, 0) != 0){ + close(tfd); + return -1; + } + + /* + * now compare + */ + if((lfd = open(local, OREAD)) < 0){ + close(tfd); + return -1; + } + + ret = cmp1(lfd, tfd); + close(lfd); + close(tfd); + return ret; +} + +/* + * Applylog might try to overwrite itself. + * To avoid problems with this, we copy ourselves + * into /tmp and then re-exec. + */ +char *rmargv0; + +static void +rmself(void) +{ + remove(rmargv0); +} + +static int +genopentemp(char *template, int mode, int perm) +{ + int fd, i; + char *p; + + p = estrdup(template); + fd = -1; + for(i=0; i<10; i++){ + mktemp(p); + if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0) + break; + strcpy(p, template); + } + if(fd < 0) + sysfatal("could not create temporary file"); + + strcpy(template, p); + free(p); + + return fd; +} + +static void +membogus(char **argv) +{ + int n, fd, wfd; + char template[50], buf[1024]; + + if(strncmp(argv[0], "/tmp/_applylog_", 1+3+1+1+8+1)==0) { + rmargv0 = argv[0]; + atexit(rmself); + return; + } + + if((fd = open(argv[0], OREAD)) < 0) + return; + + strcpy(template, "/tmp/_applylog_XXXXXX"); + if((wfd = genopentemp(template, OWRITE, 0700)) < 0) + return; + + while((n = read(fd, buf, sizeof buf)) > 0) + if(write(wfd, buf, n) != n) + goto Error; + + if(n != 0) + goto Error; + + close(fd); + close(wfd); + + argv[0] = template; + exec(template, argv); + fprint(2, "exec error %r\n"); + +Error: + close(fd); + close(wfd); + remove(template); + return; +} |