1 #include "all.h" 2 3 int localdirstat(char*, Dir*); 4 int ismatch(char*); 5 void conflict(char*, char*, ...); 6 void error(char*, ...); 7 int isdir(char*); 8 9 int errors; 10 int nconf; 11 int donothing; 12 char **conf; 13 int verbose; 14 char **match; 15 int nmatch; 16 int resolve; 17 int notempspool; 18 char *lroot; 19 char *rroot; 20 Db *clientdb; 21 int skip; 22 int douid; 23 char *mkname(char*, int, char*, char*); 24 char localbuf[10240]; 25 char remotebuf[10240]; 26 int copyfile(char*, char*, Dir*, int, int*); 27 ulong maxnow; 28 int maxn; 29 char *timefile; 30 int timefd; 31 32 Db *copyerr; 33 34 void 35 readtimefile(void) 36 { 37 int n; 38 char buf[24]; 39 40 if((timefd = open(timefile, ORDWR)) < 0 41 && (timefd = create(timefile, ORDWR|OEXCL, 0666)) < 0) 42 return; 43 44 n = readn(timefd, buf, sizeof buf); 45 if(n < sizeof buf) 46 return; 47 48 maxnow = atoi(buf); 49 maxn = atoi(buf+12); 50 } 51 52 void 53 writetimefile(void) 54 { 55 char buf[24+1]; 56 57 snprint(buf, sizeof buf, "%11lud %11d ", maxnow, maxn); 58 pwrite(timefd, buf, 24, 0); 59 } 60 61 static void membogus(char**); 62 63 void 64 addce(char *local) 65 { 66 char e[ERRMAX]; 67 Dir d; 68 69 memset(&d, 0, sizeof d); 70 rerrstr(e, sizeof e); 71 d.name = atom(e); 72 d.uid = ""; 73 d.gid = ""; 74 insertdb(copyerr, atom(local), &d); 75 } 76 77 void 78 delce(char *local) 79 { 80 removedb(copyerr, local); 81 } 82 83 void 84 chat(char *f, ...) 85 { 86 Fmt fmt; 87 char buf[256]; 88 va_list arg; 89 90 if(!verbose) 91 return; 92 93 fmtfdinit(&fmt, 1, buf, sizeof buf); 94 va_start(arg, f); 95 fmtvprint(&fmt, f, arg); 96 va_end(arg); 97 fmtfdflush(&fmt); 98 } 99 100 void 101 usage(void) 102 { 103 fprint(2, "usage: replica/applylog [-cnsuv] [-T timefile] clientdb clientroot serverroot [path ...]\n"); 104 exits("usage"); 105 } 106 107 int 108 notexists(char *path) 109 { 110 char buf[ERRMAX]; 111 112 if(access(path, AEXIST) >= 0) 113 return 0; 114 115 rerrstr(buf, sizeof buf); 116 if(strstr(buf, "entry not found") || strstr(buf, "not exist")) 117 return 1; 118 119 /* some other error, like network hangup */ 120 return 0; 121 } 122 123 void 124 main(int argc, char **argv) 125 { 126 char *f[10], *local, *name, *remote, *s, *t, verb; 127 int fd, havedb, havelocal, k, n, nf, skip; 128 ulong now; 129 Biobuf bin; 130 Dir dbd, ld, nd, rd; 131 Avlwalk *w; 132 Entry *e; 133 134 membogus(argv); 135 quotefmtinstall(); 136 ARGBEGIN{ 137 case 's': 138 case 'c': 139 resolve = ARGC(); 140 break; 141 case 'n': 142 donothing = 1; 143 verbose = 1; 144 break; 145 case 'T': 146 timefile = EARGF(usage()); 147 break; 148 case 't': 149 notempspool = 1; 150 break; 151 case 'u': 152 douid = 1; 153 break; 154 case 'v': 155 verbose = 1; 156 break; 157 default: 158 usage(); 159 }ARGEND 160 161 if(argc < 3) 162 usage(); 163 164 if(timefile) 165 readtimefile(); 166 167 lroot = argv[1]; 168 if(!isdir(lroot)) 169 sysfatal("bad local root directory"); 170 rroot = argv[2]; 171 if(!isdir(rroot)) 172 sysfatal("bad remote root directory"); 173 174 if((clientdb = opendb(argv[0])) == nil) 175 sysfatal("opendb %q: %r", argv[2]); 176 match = argv+3; 177 nmatch = argc-3; 178 179 copyerr = opendb(nil); 180 181 skip = 0; 182 Binit(&bin, 0, OREAD); 183 for(; s=Brdstr(&bin, '\n', 1); free(s)){ 184 t = estrdup(s); 185 nf = tokenize(s, f, nelem(f)); 186 if(nf != 10 || strlen(f[2]) != 1){ 187 skip = 1; 188 fprint(2, "warning: skipping bad log entry <%s>\n", t); 189 free(t); 190 continue; 191 } 192 free(t); 193 now = strtoul(f[0], 0, 0); 194 n = atoi(f[1]); 195 verb = f[2][0]; 196 name = f[3]; 197 if(now < maxnow || (now==maxnow && n <= maxn)) 198 continue; 199 if(!ismatch(name)){ 200 skip = 1; 201 continue; 202 } 203 local = mkname(localbuf, sizeof localbuf, lroot, name); 204 if(strcmp(f[4], "-") == 0) 205 f[4] = f[3]; 206 remote = mkname(remotebuf, sizeof remotebuf, rroot, f[4]); 207 rd.name = f[4]; 208 rd.mode = strtoul(f[5], 0, 8); 209 rd.uid = f[6]; 210 rd.gid = f[7]; 211 rd.mtime = strtoul(f[8], 0, 10); 212 rd.length = strtoll(f[9], 0, 10); 213 havedb = finddb(clientdb, name, &dbd)>=0; 214 havelocal = localdirstat(local, &ld)>=0; 215 216 switch(verb){ 217 case 'd': /* delete file */ 218 delce(local); 219 if(!havelocal) /* doesn't exist; who cares? */ 220 break; 221 if(!havedb){ 222 if(resolve == 's') 223 goto DoRemove; 224 else if(resolve == 'c') 225 goto DoRemoveDb; 226 conflict(name, "locally created; will not remove"); 227 skip = 1; 228 continue; 229 } 230 assert(havelocal && havedb); 231 if(dbd.mtime > rd.mtime) /* we have a newer file than what was deleted */ 232 break; 233 if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* locally modified since we downloaded it */ 234 if(resolve == 's') 235 goto DoRemove; 236 else if(resolve == 'c') 237 break; 238 conflict(name, "locally modified; will not remove"); 239 skip = 1; 240 continue; 241 } 242 DoRemove: 243 chat("d %q\n", name); 244 if(donothing) 245 break; 246 if(remove(local) < 0){ 247 error("removing %q", name); 248 skip = 1; 249 continue; 250 } 251 DoRemoveDb: 252 removedb(clientdb, name); 253 break; 254 255 case 'a': /* add file */ 256 if(!havedb){ 257 if(!havelocal) 258 goto DoCreate; 259 if((ld.mode&DMDIR) && (rd.mode&DMDIR)) 260 break; 261 if(resolve == 's') 262 goto DoCreate; 263 else if(resolve == 'c') 264 goto DoCreateDb; 265 conflict(name, "locally created; will not overwrite"); 266 skip = 1; 267 continue; 268 } 269 assert(havedb); 270 if(dbd.mtime >= rd.mtime) /* already created this file; ignore */ 271 break; 272 if(havelocal){ 273 if(dbd.mtime==ld.mtime && dbd.length==ld.length) 274 goto DoCreate; 275 if(resolve=='s') 276 goto DoCreate; 277 else if(resolve == 'c') 278 break; 279 conflict(name, "locally modified; will not overwrite"); 280 skip = 1; 281 continue; 282 } 283 DoCreate: 284 if(notexists(remote)){ 285 addce(local); 286 /* no skip=1 */ 287 break;; 288 } 289 chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); 290 if(donothing) 291 break; 292 if(rd.mode&DMDIR){ 293 if((fd = create(local, OREAD, DMDIR)) < 0){ 294 error("mkdir %q: %r", name); 295 skip = 1; 296 continue; 297 } 298 nulldir(&nd); 299 nd.mode = rd.mode; 300 if(dirfwstat(fd, &nd) < 0) 301 fprint(2, "warning: cannot set mode on %q\n", local); 302 nulldir(&nd); 303 nd.gid = rd.gid; 304 if(dirfwstat(fd, &nd) < 0) 305 fprint(2, "warning: cannot set gid on %q\n", local); 306 if(douid){ 307 nulldir(&nd); 308 nd.uid = rd.uid; 309 if(dirfwstat(fd, &nd) < 0) 310 fprint(2, "warning: cannot set uid on %q\n", local); 311 } 312 close(fd); 313 rd.mtime = now; 314 }else{ 315 if(copyfile(local, remote, &rd, 1, &k) < 0){ 316 if(k) 317 addce(local); 318 skip = 1; 319 continue; 320 } 321 } 322 DoCreateDb: 323 insertdb(clientdb, name, &rd); 324 break; 325 326 case 'c': /* change contents */ 327 if(!havedb){ 328 if(notexists(remote)){ 329 addce(local); 330 /* no skip=1 */ 331 break; 332 } 333 if(resolve == 's') 334 goto DoCopy; 335 else if(resolve=='c') 336 goto DoCopyDb; 337 if(havelocal) 338 conflict(name, "locally created; will not update"); 339 else 340 conflict(name, "not replicated; will not update"); 341 skip = 1; 342 continue; 343 } 344 if(dbd.mtime >= rd.mtime) /* already have/had this version; ignore */ 345 break; 346 if(!havelocal){ 347 if(notexists(remote)){ 348 addce(local); 349 /* no skip=1 */ 350 break; 351 } 352 if(resolve == 's') 353 goto DoCopy; 354 else if(resolve == 'c') 355 break; 356 conflict(name, "locally removed; will not update"); 357 skip = 1; 358 continue; 359 } 360 assert(havedb && havelocal); 361 if(dbd.mtime != ld.mtime || dbd.length != ld.length){ 362 if(notexists(remote)){ 363 addce(local); 364 /* no skip=1 */ 365 break; 366 } 367 if(resolve == 's') 368 goto DoCopy; 369 else if(resolve == 'c') 370 break; 371 conflict(name, "locally modified; will not update"); 372 skip = 1; 373 continue; 374 } 375 DoCopy: 376 if(notexists(remote)){ 377 addce(local); 378 /* no skip=1 */ 379 break; 380 } 381 chat("c %q\n", name); 382 if(donothing) 383 break; 384 if(copyfile(local, remote, &rd, 0, &k) < 0){ 385 if(k) 386 addce(local); 387 skip = 1; 388 continue; 389 } 390 DoCopyDb: 391 if(!havedb){ 392 if(havelocal) 393 dbd = ld; 394 else 395 dbd = rd; 396 } 397 dbd.mtime = rd.mtime; 398 dbd.length = rd.length; 399 insertdb(clientdb, name, &dbd); 400 break; 401 402 case 'm': /* change metadata */ 403 if(!havedb){ 404 if(notexists(remote)){ 405 addce(local); 406 /* no skip=1 */ 407 break; 408 } 409 if(resolve == 's') 410 goto DoCreate; 411 else if(resolve == 'c') 412 goto DoMetaDb; 413 if(havelocal) 414 conflict(name, "locally created; will not update metadata"); 415 else 416 conflict(name, "not replicated; will not update metadata"); 417 skip = 1; 418 continue; 419 } 420 if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime) /* have newer version; ignore */ 421 break; 422 if((dbd.mode&DMDIR) && dbd.mtime > now) 423 break; 424 if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode) /* nothing to do */ 425 goto DoMetaDb; 426 if(!(dbd.mode&DMDIR) && dbd.mtime < rd.mtime){ /* this check might be overkill */ 427 if(notexists(remote)){ 428 addce(local); 429 /* no skip=1 */ 430 break; 431 } 432 if(resolve == 's') 433 goto DoMeta; 434 else if(resolve == 'c') 435 break; 436 conflict(name, "contents out of date; will not update metadata to %s %s %luo", 437 rd.uid, rd.gid, rd.mode); 438 skip = 1; 439 continue; 440 } 441 if(!havelocal){ 442 if(notexists(remote)){ 443 addce(local); 444 /* no skip=1 */ 445 break; 446 } 447 if(resolve == 's') 448 goto DoCreate; 449 else if(resolve == 'c') 450 break; 451 conflict(name, "locally removed; will not update metadata"); 452 skip = 1; 453 continue; 454 } 455 if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* this check might be overkill */ 456 if(notexists(remote)){ 457 addce(local); 458 /* no skip=1 */ 459 break; 460 } 461 if(resolve == 's') 462 goto DoMeta; 463 else if(resolve == 'c') 464 break; 465 conflict(name, "contents locally modified; will not update metadata to %s %s %luo", 466 rd.uid, rd.gid, rd.mode); 467 skip = 1; 468 continue; 469 } 470 if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){ 471 if(notexists(remote)){ 472 addce(local); 473 /* no skip=1 */ 474 break; 475 } 476 if(resolve == 's') 477 goto DoMeta; 478 else if(resolve == 'c') 479 break; 480 conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode); 481 skip = 1; 482 continue; 483 } 484 DoMeta: 485 if(notexists(remote)){ 486 addce(local); 487 /* no skip=1 */ 488 break; 489 } 490 chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime); 491 if(donothing) 492 break; 493 nulldir(&nd); 494 nd.gid = rd.gid; 495 nd.mode = rd.mode; 496 if(douid) 497 nd.uid = rd.uid; 498 if(dirwstat(local, &nd) < 0){ 499 error("dirwstat %q: %r", name); 500 skip = 1; 501 continue; 502 } 503 DoMetaDb: 504 if(dbd.mode&DMDIR) 505 dbd.mtime = now; 506 dbd.gid = rd.gid; 507 dbd.mode = rd.mode; 508 if(douid) 509 dbd.uid = rd.uid; 510 insertdb(clientdb, name, &dbd); 511 break; 512 } 513 if(!skip && !donothing){ 514 maxnow = now; 515 maxn = n; 516 } 517 } 518 519 w = avlwalk(copyerr->avl); 520 while(e = (Entry*)avlnext(w)) 521 error("copying %q: %s\n", e->name, e->d.name); 522 523 if(timefile) 524 writetimefile(); 525 if(nconf) 526 exits("conflicts"); 527 528 if(errors) 529 exits("errors"); 530 exits(nil); 531 } 532 533 534 char* 535 mkname(char *buf, int nbuf, char *a, char *b) 536 { 537 if(strlen(a)+strlen(b)+2 > nbuf) 538 sysfatal("name too long"); 539 540 strcpy(buf, a); 541 if(a[strlen(a)-1] != '/') 542 strcat(buf, "/"); 543 strcat(buf, b); 544 return buf; 545 } 546 547 int 548 isdir(char *s) 549 { 550 ulong m; 551 Dir *d; 552 553 if((d = dirstat(s)) == nil) 554 return 0; 555 m = d->mode; 556 free(d); 557 return (m&DMDIR) != 0; 558 } 559 560 void 561 conflict(char *name, char *f, ...) 562 { 563 char *s; 564 va_list arg; 565 566 va_start(arg, f); 567 s = vsmprint(f, arg); 568 va_end(arg); 569 570 fprint(2, "%s: %s\n", name, s); 571 free(s); 572 573 nconf++; 574 // if(nconf%16 == 0) 575 // conf = erealloc(conf, (nconf+16)*sizeof(conf[0])); 576 // conf[nconf++] = estrdup(name); 577 } 578 579 void 580 error(char *f, ...) 581 { 582 char *s; 583 va_list arg; 584 585 va_start(arg, f); 586 s = vsmprint(f, arg); 587 va_end(arg); 588 fprint(2, "error: %s\n", s); 589 free(s); 590 errors = 1; 591 } 592 593 int 594 ismatch(char *s) 595 { 596 int i, len; 597 598 if(nmatch == 0) 599 return 1; 600 for(i=0; i<nmatch; i++){ 601 if(strcmp(s, match[i]) == 0) 602 return 1; 603 len = strlen(match[i]); 604 if(strncmp(s, match[i], len) == 0 && s[len]=='/') 605 return 1; 606 } 607 return 0; 608 } 609 610 int 611 localdirstat(char *name, Dir *d) 612 { 613 static Dir *d2; 614 615 free(d2); 616 if((d2 = dirstat(name)) == nil) 617 return -1; 618 *d = *d2; 619 return 0; 620 } 621 622 enum { DEFB = 8192 }; 623 624 static int 625 copy1(int fdf, int fdt, char *from, char *to) 626 { 627 char buf[DEFB]; 628 long n, n1, rcount; 629 int rv; 630 char err[ERRMAX]; 631 632 /* clear any residual error */ 633 err[0] = '\0'; 634 errstr(err, ERRMAX); 635 rv = 0; 636 for(rcount=0;; rcount++) { 637 n = read(fdf, buf, DEFB); 638 if(n <= 0) 639 break; 640 n1 = write(fdt, buf, n); 641 if(n1 != n) { 642 fprint(2, "error writing %q: %r\n", to); 643 rv = -1; 644 break; 645 } 646 } 647 if(n < 0) { 648 fprint(2, "error reading %q: %r\n", from); 649 rv = -1; 650 } 651 return rv; 652 } 653 654 static int 655 opentemp(char *template) 656 { 657 int fd, i; 658 char *p; 659 660 p = estrdup(template); 661 fd = -1; 662 for(i=0; i<10; i++){ 663 mktemp(p); 664 if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0) 665 break; 666 strcpy(p, template); 667 } 668 if(fd < 0) 669 return -1; 670 strcpy(template, p); 671 free(p); 672 return fd; 673 } 674 675 int 676 copyfile(char *local, char *remote, Dir *d, int dowstat, int *printerror) 677 { 678 Dir *d0, *d1; 679 Dir nd; 680 int rfd, tfd, wfd, didcreate; 681 char tmp[32]; 682 683 Again: 684 *printerror = 0; 685 if((rfd = open(remote, OREAD)) < 0) 686 return -1; 687 688 d0 = dirfstat(rfd); 689 if(d0 == nil){ 690 close(rfd); 691 return -1; 692 } 693 *printerror = 1; 694 if(notempspool){ 695 tfd = rfd; 696 goto DoCopy; 697 } 698 strcpy(tmp, "/tmp/replicaXXXXXXXX"); 699 tfd = opentemp(tmp); 700 if(tfd < 0){ 701 close(rfd); 702 free(d0); 703 return -1; 704 } 705 if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){ 706 close(rfd); 707 close(tfd); 708 free(d0); 709 return -1; 710 } 711 close(rfd); 712 if(d0->qid.path != d1->qid.path 713 || d0->qid.vers != d1->qid.vers 714 || d0->mtime != d1->mtime 715 || d0->length != d1->length){ 716 /* file changed underfoot; go around again */ 717 close(tfd); 718 free(d0); 719 free(d1); 720 goto Again; 721 } 722 free(d1); 723 if(seek(tfd, 0, 0) != 0){ 724 close(tfd); 725 free(d0); 726 return -1; 727 } 728 DoCopy: 729 didcreate = 0; 730 if((wfd = open(local, OTRUNC|OWRITE)) < 0){ 731 if((wfd = create(local, OWRITE, 0)) < 0){ 732 close(tfd); 733 free(d0); 734 return -1; 735 } 736 didcreate = 1; 737 } 738 if(copy1(tfd, wfd, tmp, local) < 0){ 739 close(tfd); 740 close(wfd); 741 free(d0); 742 return -1; 743 } 744 close(tfd); 745 if(didcreate || dowstat){ 746 nulldir(&nd); 747 nd.mode = d->mode; 748 if(dirfwstat(wfd, &nd) < 0) 749 fprint(2, "warning: cannot set mode on %s\n", local); 750 nulldir(&nd); 751 nd.gid = d->gid; 752 if(dirfwstat(wfd, &nd) < 0) 753 fprint(2, "warning: cannot set gid on %s\n", local); 754 if(douid){ 755 nulldir(&nd); 756 nd.uid = d->uid; 757 if(dirfwstat(wfd, &nd) < 0) 758 fprint(2, "warning: cannot set uid on %s\n", local); 759 } 760 } 761 d->mtime = d0->mtime; 762 d->length = d0->length; 763 nulldir(&nd); 764 nd.mtime = d->mtime; 765 if(dirfwstat(wfd, &nd) < 0) 766 fprint(2, "warning: cannot set mtime on %s\n", local); 767 close(wfd); 768 free(d0); 769 return 0; 770 } 771 772 /* 773 * Applylog might try to overwrite itself. 774 * To avoid problems with this, we copy ourselves 775 * into /tmp and then re-exec. 776 */ 777 char *rmargv0; 778 779 static void 780 rmself(void) 781 { 782 remove(rmargv0); 783 } 784 785 static int 786 genopentemp(char *template, int mode, int perm) 787 { 788 int fd, i; 789 char *p; 790 791 p = estrdup(template); 792 fd = -1; 793 for(i=0; i<10; i++){ 794 mktemp(p); 795 if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0) 796 break; 797 strcpy(p, template); 798 } 799 if(fd < 0) 800 sysfatal("could not create temporary file"); 801 802 strcpy(template, p); 803 free(p); 804 805 return fd; 806 } 807 808 static void 809 membogus(char **argv) 810 { 811 int n, fd, wfd; 812 char template[50], buf[1024]; 813 814 if(strncmp(argv[0], "/tmp/_inst_", 1+3+1+6)==0) { 815 rmargv0 = argv[0]; 816 atexit(rmself); 817 return; 818 } 819 820 if((fd = open(argv[0], OREAD)) < 0) 821 return; 822 823 strcpy(template, "/tmp/_applylog_XXXXXX"); 824 if((wfd = genopentemp(template, OWRITE, 0700)) < 0) 825 return; 826 827 while((n = read(fd, buf, sizeof buf)) > 0) 828 if(write(wfd, buf, n) != n) 829 goto Error; 830 831 if(n != 0) 832 goto Error; 833 834 close(fd); 835 close(wfd); 836 837 argv[0] = template; 838 exec(template, argv); 839 fprint(2, "exec error %r\n"); 840 841 Error: 842 close(fd); 843 close(wfd); 844 remove(template); 845 return; 846 } 847