5 int localdirstat(char*, Dir*);
7 void conflict(char*, char*, ...);
8 void error(char*, ...);
11 void worker(int fdf, int fdt, char *from, char *to);
13 void failure(void *, char *note);
31 char *mkname(char*, int, char*, char*);
33 char remotebuf[10240];
34 int copyfile(char*, char*, char*, Dir*, int, int*);
39 int samecontents(char*, char*);
43 typedef struct Res Res;
54 addresolve(int c, char *name)
58 res = erealloc(res, (nres+1)*sizeof res[0]);
60 res[nres].name = name;
69 for(i=0; i<nres; i++){
70 len = strlen(res[i].name);
73 if(strncmp(name, res[i].name, len) == 0 && (name[len]=='/' || name[len] == 0))
85 if((timefd = open(timefile, ORDWR)) < 0
86 && (timefd = create(timefile, ORDWR|OEXCL, 0666)) < 0)
89 n = readn(timefd, buf, sizeof buf);
102 snprint(buf, sizeof buf, "%11lud %11d ", maxnow, maxn);
103 pwrite(timefd, buf, 24, 0);
106 static void membogus(char**);
114 memset(&d, 0, sizeof d);
115 rerrstr(e, sizeof e);
119 insertdb(copyerr, atom(local), &d);
125 removedb(copyerr, local);
138 fmtfdinit(&fmt, 1, buf, sizeof buf);
140 fmtvprint(&fmt, f, arg);
148 fprint(2, "usage: replica/applylog [-cnSstuv] [-T timefile] clientdb clientroot serverroot [path ...]\n");
153 notexists(char *path)
157 if(access(path, AEXIST) >= 0)
160 rerrstr(buf, sizeof buf);
161 if(strstr(buf, "entry not found") || strstr(buf, "not exist"))
164 /* some other error, like network hangup */
169 main(int argc, char **argv)
171 char *f[10], *local, *name, *remote, *s, *t, verb;
172 int fd, havedb, havelocal, i, k, n, nf, resolve1, skip;
173 int checkedmatch1, checkedmatch2,
174 checkedmatch3, checkedmatch4;
186 addresolve(i, EARGF(usage()));
196 timefile = EARGF(usage());
219 sysfatal("bad local root directory");
222 sysfatal("bad remote root directory");
226 for(i=0; i<nmatch; i++)
227 if(match[i][0] == '/')
230 if((clientdb = opendb(argv[0])) == nil)
231 sysfatal("opendb %q: %r", argv[2]);
233 copyerr = opendb(nil);
236 Binit(&bin, 0, OREAD);
237 for(; s=Brdstr(&bin, '\n', 1); free(s)){
239 nf = tokenize(s, f, nelem(f));
240 if(nf != 10 || strlen(f[2]) != 1){
242 fprint(2, "warning: skipping bad log entry <%s>\n", t);
247 now = strtoul(f[0], 0, 0);
251 if(now < maxnow || (now==maxnow && n <= maxn))
253 local = mkname(localbuf, sizeof localbuf, lroot, name);
254 if(strcmp(f[4], "-") == 0)
256 remote = mkname(remotebuf, sizeof remotebuf, rroot, f[4]);
258 rd.mode = strtoul(f[5], 0, 8);
261 rd.mtime = strtoul(f[8], 0, 10);
262 rd.length = strtoll(f[9], 0, 10);
263 havedb = finddb(clientdb, name, &dbd)>=0;
264 havelocal = localdirstat(local, &ld)>=0;
266 resolve1 = resolve(name);
269 * if(!ismatch(name)){
274 * This check used to be right here, but we want
275 * the time to be able to move forward past entries
276 * that don't match and have already been applied.
277 * So now every path below must checked !ismatch(name)
278 * before making any changes to the local file
279 * system. The fake variable checkedmatch
280 * tracks whether !ismatch(name) has been checked.
281 * If the compiler doesn't produce any used/set
282 * warnings, then all the paths should be okay.
283 * Even so, we have the asserts to fall back on.
286 case 'd': /* delete file */
288 if(!havelocal) /* doesn't exist; who cares? */
290 if(access(remote, AEXIST) >= 0) /* got recreated! */
294 fprint(2, "stopped updating log apply time because of %s\n", name);
302 else if(resolve1 == 'c')
304 conflict(name, "locally created; will not remove");
308 assert(havelocal && havedb);
309 if(dbd.mtime > rd.mtime) /* we have a newer file than what was deleted */
311 if(samecontents(local, remote) > 0){ /* going to get recreated */
312 chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
315 if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* locally modified since we downloaded it */
318 else if(resolve1 == 'c')
320 conflict(name, "locally modified; will not remove");
326 assert(ismatch(name));
327 chat("d %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
330 if(remove(local) < 0){
331 error("removing %q: %r", name);
337 assert(ismatch(name));
338 removedb(clientdb, name);
341 case 'a': /* add file */
345 fprint(2, "stopped updating log apply time because of %s\n", name);
352 if((ld.mode&DMDIR) && (rd.mode&DMDIR))
354 if(samecontents(local, remote) > 0){
355 chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
360 else if(resolve1 == 'c')
362 conflict(name, "locally created; will not overwrite");
367 if(dbd.mtime >= rd.mtime) /* already created this file; ignore */
370 if((ld.mode&DMDIR) && (rd.mode&DMDIR))
374 fprint(2, "stopped updating log apply time because of %s\n", name);
379 if(samecontents(local, remote) > 0){
380 chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
383 if(dbd.mtime==ld.mtime && dbd.length==ld.length)
387 else if(resolve1 == 'c')
389 conflict(name, "locally modified; will not overwrite");
395 fprint(2, "stopped updating log apply time because of %s\n", name);
402 assert(ismatch(name));
403 if(notexists(remote)){
408 chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
412 fd = create(local, OREAD, DMDIR);
413 if(fd < 0 && isdir(local))
414 fd = open(local, OREAD);
416 error("mkdir %q: %r", name);
422 if(dirfwstat(fd, &nd) < 0)
423 fprint(2, "warning: cannot set mode on %q\n", local);
426 if(dirfwstat(fd, &nd) < 0)
427 fprint(2, "warning: cannot set gid on %q\n", local);
431 if(dirfwstat(fd, &nd) < 0)
432 fprint(2, "warning: cannot set uid on %q\n", local);
437 if(copyfile(local, remote, name, &rd, 1, &k) < 0){
446 assert(ismatch(name));
447 insertdb(clientdb, name, &rd);
450 case 'c': /* change contents */
452 if(notexists(remote)){
459 fprint(2, "stopped updating log apply time because of %s\n", name);
466 else if(resolve1=='c')
468 if(samecontents(local, remote) > 0){
469 chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
473 conflict(name, "locally created; will not update");
475 conflict(name, "not replicated; will not update");
479 if(dbd.mtime >= rd.mtime) /* already have/had this version; ignore */
483 fprint(2, "stopped updating log apply time because of %s\n", name);
489 if(notexists(remote)){
496 else if(resolve1 == 'c')
498 conflict(name, "locally removed; will not update");
502 assert(havedb && havelocal);
503 if(dbd.mtime != ld.mtime || dbd.length != ld.length){
504 if(notexists(remote)){
509 if(samecontents(local, remote) > 0){
510 chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
515 else if(resolve1 == 'c')
517 conflict(name, "locally modified; will not update [%llud %lud -> %llud %lud]", dbd.length, dbd.mtime, ld.length, ld.mtime);
523 assert(ismatch(name));
524 if(notexists(remote)){
529 chat("c %q\n", name);
532 if(copyfile(local, remote, name, &rd, 0, &k) < 0){
540 assert(ismatch(name));
547 dbd.mtime = rd.mtime;
548 dbd.length = rd.length;
549 insertdb(clientdb, name, &dbd);
552 case 'm': /* change metadata */
554 if(notexists(remote)){
561 fprint(2, "stopped updating log apply time because of %s\n", name);
571 else if(resolve1 == 'c')
574 conflict(name, "locally created; will not update metadata");
576 conflict(name, "not replicated; will not update metadata");
580 if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime) /* have newer version; ignore */
582 if((dbd.mode&DMDIR) && dbd.mtime > now)
584 if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode)
587 if(notexists(remote)){
594 fprint(2, "stopped updating log apply time because of %s\n", name);
604 else if(resolve1 == 'c')
606 conflict(name, "locally removed; will not update metadata");
610 if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* this check might be overkill */
611 if(notexists(remote)){
618 fprint(2, "stopped updating log apply time because of %s\n", name);
623 if(resolve1 == 's' || samecontents(local, remote) > 0)
625 else if(resolve1 == 'c')
627 conflict(name, "contents locally modified (%s); will not update metadata to %s %s %luo",
628 dbd.mtime != ld.mtime ? "mtime" :
629 dbd.length != ld.length ? "length" :
631 rd.uid, rd.gid, rd.mode);
635 if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){
636 if(notexists(remote)){
643 fprint(2, "stopped updating log apply time because of %s\n", name);
650 else if(resolve1 == 'c')
652 conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode);
658 fprint(2, "stopped updating log apply time because of %s\n", name);
665 assert(ismatch(name));
666 if(notexists(remote)){
671 chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
679 if(dirwstat(local, &nd) < 0){
680 error("dirwstat %q: %r", name);
686 assert(ismatch(name));
699 insertdb(clientdb, name, &dbd);
702 if(!skip && !donothing){
708 for(e = (Entry*)avlmin(copyerr->avl); e != nil; e = (Entry*)avlnext(e))
709 error("copying %q: %s\n", e->name, e->d.name);
723 mkname(char *buf, int nbuf, char *a, char *b)
725 if(strlen(a)+strlen(b)+2 > nbuf)
726 sysfatal("name too long");
729 if(a[strlen(a)-1] != '/')
741 if((d = dirstat(s)) == nil)
745 return (m&DMDIR) != 0;
749 conflict(char *name, char *f, ...)
755 s = vsmprint(f, arg);
758 fprint(2, "! %s: %s\n", name, s);
771 s = vsmprint(f, arg);
773 fprint(2, "error: %s\n", s);
785 for(i=0; i<nmatch; i++){
786 len = strlen(match[i]);
789 if(strncmp(s, match[i], len) == 0 && (s[len]=='/' || s[len] == 0))
796 localdirstat(char *name, Dir *d)
801 if((d2 = dirstat(name)) == nil)
807 enum { DEFB = 8192 };
810 cmp1(int fd1, int fd2)
817 n1 = readn(fd1, buf1, DEFB);
818 n2 = readn(fd2, buf2, DEFB);
825 if(memcmp(buf1, buf2, n1) != 0)
831 copy1(int fdf, int fdt, char *from, char *to)
833 int i, n, rv, pid[Nwork];
838 for(i=0; i<Nwork; i++){
839 switch(pid[n] = rfork(RFPROC|RFMEM)){
842 worker(fdf, fdt, from, to);
851 fprint(2, "cp: rfork: %r\n");
856 while((w = wait()) != nil){
861 postnote(PNPROC, pid[i], "failure");
869 worker(int fdf, int fdt, char *from, char *to)
879 while(n = pread(fdf, bp, len, o)){
881 fprint(2, "reading %s: %r\n", from);
884 if(pwrite(fdt, buf, n, o) != n){
885 fprint(2, "writing %s: %r\n", to);
914 failure(void*, char *note)
916 if(strcmp(note, "failure") == 0)
923 opentemp(char *template)
928 p = estrdup(template);
932 if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0)
944 copyfile(char *local, char *remote, char *name, Dir *d, int dowstat, int *printerror)
948 int rfd, tfd, wfd, didcreate;
949 char tmp[32], *p, *safe;
954 if((rfd = open(remote, OREAD)) < 0)
967 strcpy(tmp, "/tmp/replicaXXXXXXXX");
974 if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
981 if(d0->qid.path != d1->qid.path
982 || d0->qid.vers != d1->qid.vers
983 || d0->mtime != d1->mtime
984 || d0->length != d1->length){
985 /* file changed underfoot; go around again */
992 if(seek(tfd, 0, 0) != 0){
1000 * clumsy but important hack to do safeinstall-like installs.
1002 p = strchr(name, '/');
1003 if(safeinstall && p && strncmp(p, "/bin/", 5) == 0 && access(local, AEXIST) >= 0){
1007 safe = emalloc(strlen(local)+2);
1008 strcpy(safe, local);
1009 p = strrchr(safe, '/')+1;
1010 memmove(p+1, p, strlen(p)+1);
1012 remove(safe); /* ignore failure */
1015 * rename bin/targ to bin/_targ
1019 if(dirwstat(local, &nd) < 0)
1020 fprint(2, "warning: rename %s to %s: %r\n", local, p);
1024 if((dl = dirstat(local)) == nil){
1025 if((wfd = create(local, OWRITE, 0)) >= 0){
1031 if((wfd = open(local, OTRUNC|OWRITE)) >= 0)
1033 rerrstr(err, sizeof err);
1034 if(strstr(err, "permission") == nil)
1038 * Assume the person running pull is in the appropriate
1039 * groups. We could set 0666 instead, but I'm worried
1040 * about leaving the file world-readable or world-writable
1041 * when it shouldn't be.
1043 nd.mode = dl->mode | 0660;
1044 if(nd.mode == dl->mode)
1046 if(dirwstat(local, &nd) < 0)
1048 if((wfd = open(local, OTRUNC|OWRITE)) >= 0){
1050 if(dirfwstat(wfd, &nd) < 0)
1051 fprint(2, "warning: set mode on %s to 0660 to open; cannot set back to %luo: %r\n", local, nd.mode);
1055 if(dirwstat(local, &nd) < 0)
1056 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);
1068 if(copy1(tfd, wfd, tmp, local) < 0){
1075 if(didcreate || dowstat){
1078 if(dirfwstat(wfd, &nd) < 0)
1079 fprint(2, "warning: cannot set mode on %s\n", local);
1082 if(dirfwstat(wfd, &nd) < 0)
1083 fprint(2, "warning: cannot set gid on %s\n", local);
1087 if(dirfwstat(wfd, &nd) < 0)
1088 fprint(2, "warning: cannot set uid on %s\n", local);
1091 d->mtime = d0->mtime;
1092 d->length = d0->length;
1094 nd.mtime = d->mtime;
1095 if(dirfwstat(wfd, &nd) < 0)
1096 fprint(2, "warning: cannot set mtime on %s\n", local);
1104 samecontents(char *local, char *remote)
1107 int rfd, tfd, lfd, ret;
1110 /* quick check: sizes must match */
1112 if((d0 = dirstat(local)) == nil || (d1 = dirstat(remote)) == nil){
1117 if(d0->length != d1->length){
1124 if((rfd = open(remote, OREAD)) < 0)
1132 strcpy(tmp, "/tmp/replicaXXXXXXXX");
1133 tfd = opentemp(tmp);
1139 if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
1146 if(d0->qid.path != d1->qid.path
1147 || d0->qid.vers != d1->qid.vers
1148 || d0->mtime != d1->mtime
1149 || d0->length != d1->length){
1150 /* file changed underfoot; go around again */
1158 if(seek(tfd, 0, 0) != 0){
1166 if((lfd = open(local, OREAD)) < 0){
1171 ret = cmp1(lfd, tfd);
1178 * Applylog might try to overwrite itself.
1179 * To avoid problems with this, we copy ourselves
1180 * into /tmp and then re-exec.
1191 genopentemp(char *template, int mode, int perm)
1196 p = estrdup(template);
1198 for(i=0; i<10; i++){
1200 if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
1202 strcpy(p, template);
1205 sysfatal("could not create temporary file");
1207 strcpy(template, p);
1214 membogus(char **argv)
1217 char template[50], buf[1024];
1219 if(strncmp(argv[0], "/tmp/_applylog_", 1+3+1+1+8+1)==0) {
1225 if((fd = open(argv[0], OREAD)) < 0)
1228 strcpy(template, "/tmp/_applylog_XXXXXX");
1229 if((wfd = genopentemp(template, OWRITE, 0700)) < 0)
1232 while((n = read(fd, buf, sizeof buf)) > 0)
1233 if(write(wfd, buf, n) != n)
1243 exec(template, argv);
1244 fprint(2, "exec error %r\n");