1 /* remsync 1.5 - remotely synchronize file trees Author: Kees J. Bot 2 * 10 Jun 1994 3 */ 4 #define nil 0 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <stdarg.h> 10 #include <string.h> 11 #include <dirent.h> 12 #include <unistd.h> 13 #include <fcntl.h> 14 #include <errno.h> 15 #include <limits.h> 16 #include <time.h> 17 #include <utime.h> 18 19 #define arraysize(a) (sizeof(a) / sizeof((a)[0])) 20 #define arraylimit(a) ((a) + arraysize(a)) 21 22 #ifndef major 23 #define major(dev) ((int) ((dev) >> 8)) 24 #define minor(dev) ((int) ((dev) & 0xFF)) 25 #endif 26 27 #ifndef S_ISLNK 28 /* There were no symlinks in medieval times. */ 29 #define S_ISLNK(mode) (0) 30 #define lstat stat 31 #define symlink(path1, path2) (errno= ENOSYS, -1) 32 #define readlink(path, buf, len) (errno= ENOSYS, -1) 33 #endif 34 35 int sflag; /* Make state file. */ 36 int dflag; /* Make list of differences. */ 37 int uflag; /* Only update files with newer versions. */ 38 int xflag; /* Do not cross device boundaries. */ 39 int Dflag; /* Debug: Readable differences, no file contents. */ 40 int vflag; /* Verbose. */ 41 42 #define NO_DEVICE (-1) 43 dev_t xdev= NO_DEVICE; /* The device that you should stay within. */ 44 45 int excode= 0; /* Exit(excode); */ 46 47 #define BASE_INDENT 2 /* State file basic indent. */ 48 49 void report(const char *label) 50 { 51 fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno)); 52 excode= 1; 53 } 54 55 void fatal(const char *label) 56 { 57 report(label); 58 exit(1); 59 } 60 61 void *allocate(void *mem, size_t size) 62 { 63 if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) { 64 fprintf(stderr, "remsync: Out of memory: %s\n", 65 strerror(errno)); 66 exit(1); 67 } 68 return mem; 69 } 70 71 void deallocate(void *mem) 72 { 73 if (mem != nil) free(mem); 74 } 75 76 /* One needs to slowly forget two sets of objects: for the code that reads 77 * the state file, and for the code that traverses trees. 78 */ 79 int keep; 80 #define KEEP_STATE 0 81 #define KEEP_TRAVERSE 1 82 83 void forget(void *mem) 84 /* Some objects must be deleted in time, but not just yet. */ 85 { 86 static void *death_row[2][50]; 87 static void **dp[2]= { death_row[0], death_row[1] }; 88 89 deallocate(*dp[keep]); 90 *dp[keep]++= mem; 91 if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep]; 92 } 93 94 char *copystr(const char *s) 95 { 96 char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0])); 97 strcpy(c, s); 98 return c; 99 } 100 101 typedef struct pathname { 102 char *path; /* The actual pathname. */ 103 size_t idx; /* Index for the terminating null byte. */ 104 size_t lim; /* Actual length of the path array. */ 105 } pathname_t; 106 107 void path_init(pathname_t *pp) 108 /* Initialize a pathname to the null string. */ 109 { 110 pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0])); 111 pp->path[pp->idx= 0]= 0; 112 } 113 114 void path_add(pathname_t *pp, const char *name) 115 /* Add a component to a pathname. */ 116 { 117 size_t lim; 118 char *p; 119 int slash; 120 121 lim= pp->idx + strlen(name) + 2; 122 123 if (lim > pp->lim) { 124 pp->lim= lim + lim/2; /* add an extra 50% growing space. */ 125 pp->path= allocate(pp->path, pp->lim * sizeof(pp->path[0])); 126 } 127 128 p= pp->path + pp->idx; 129 slash= (pp->idx > 0); 130 if (pp->idx == 1 && p[-1] == '/') p--; 131 132 while (*name != 0) { 133 if (*name == '/') { 134 slash= 1; 135 } else { 136 if (slash) { *p++ = '/'; slash= 0; } 137 *p++= *name; 138 } 139 name++; 140 } 141 if (slash && p == pp->path) *p++= '/'; 142 *p = 0; 143 pp->idx= p - pp->path; 144 } 145 146 void path_trunc(pathname_t *pp, size_t didx) 147 /* Delete part of a pathname to a remembered length. */ 148 { 149 pp->path[pp->idx= didx]= 0; 150 } 151 152 #if kept_for_comments_only 153 154 const char *path_name(const pathname_t *pp) 155 /* Return the actual name as a char array. */ 156 { 157 return pp->path; 158 } 159 160 size_t path_length(const pathname_t *pp) 161 /* The length of the pathname. */ 162 { 163 return pp->idx; 164 } 165 166 void path_drop(pathname_t *pp) 167 /* Release the storage occupied by the pathname. */ 168 { 169 free(pp->path); 170 } 171 #endif 172 173 #define path_name(pp) ((const char *) (pp)->path) 174 #define path_length(pp) ((pp)->idx) 175 #define path_drop(pp) free((void *) (pp)->path) 176 177 typedef struct namelist { /* Obviously a list of names. */ 178 struct namelist *next; 179 char *name; 180 } namelist_t; 181 182 char *rdlink(const char *link, off_t size) 183 /* Look where "link" points. */ 184 { 185 static char *path= nil; 186 static size_t len= 0; 187 size_t n; 188 189 if (len <= size) { 190 path= allocate(path, (len= size * 2) * sizeof(path[0])); 191 } 192 if ((n= readlink(link, path, len)) == -1) return nil; 193 path[n]= 0; 194 return path; 195 } 196 197 void sort(namelist_t **anl) 198 /* A stable mergesort disguised as line noise. Must be called like this: 199 * if (L!=nil && L->next!=nil) sort(&L); 200 */ 201 { 202 /* static */ namelist_t *nl1, **mid; /* Need not be local */ 203 namelist_t *nl2; 204 205 nl1= *(mid= &(*anl)->next); 206 do { 207 if ((nl1= nl1->next) == nil) break; 208 mid= &(*mid)->next; 209 } while ((nl1= nl1->next) != nil); 210 211 nl2= *mid; 212 *mid= nil; 213 214 if ((*anl)->next != nil) sort(anl); 215 if (nl2->next != nil) sort(&nl2); 216 217 nl1= *anl; 218 for (;;) { 219 if (strcmp(nl1->name, nl2->name)<=0) { 220 if ((nl1= *(anl= &nl1->next)) == nil) { 221 *anl= nl2; 222 break; 223 } 224 } else { 225 *anl= nl2; 226 nl2= *(anl= &nl2->next); 227 *anl= nl1; 228 if (nl2 == nil) break; 229 } 230 } 231 } 232 233 namelist_t *collect(const char *dir) 234 /* Return a sorted list of directory entries. Returns null with errno != 0 235 * on error. 236 */ 237 { 238 namelist_t *names, **pn= &names; 239 DIR *dp; 240 struct dirent *entry; 241 242 if ((dp= opendir(dir)) == nil) return nil; 243 244 while ((entry= readdir(dp)) != nil) { 245 if (entry->d_name[0] == '.' 246 && (entry->d_name[1] == 0 247 || (entry->d_name[1] == '.' 248 && entry->d_name[2] == 0))) { 249 continue; 250 } 251 *pn= allocate(nil, sizeof(**pn)); 252 (*pn)->name= copystr(entry->d_name); 253 pn= &(*pn)->next; 254 } 255 closedir(dp); 256 *pn= nil; 257 errno= 0; 258 if (names != nil && names->next != nil) sort(&names); 259 return names; 260 } 261 262 char *pop_name(namelist_t **names) 263 /* Return one name of a name list. */ 264 { 265 char *name; 266 namelist_t *junk; 267 268 junk= *names; 269 *names= junk->next; 270 name= junk->name; 271 deallocate(junk); 272 forget(name); 273 return name; 274 } 275 276 typedef enum filetype { /* The files we know about. */ 277 F_DIR, 278 F_FILE, 279 F_BLK, 280 F_CHR, 281 F_PIPE, 282 F_LINK 283 } filetype_t; 284 285 typedef struct entry { /* One file. */ 286 int depth; /* Depth in directory tree. */ 287 const char *name; /* Name of entry. */ 288 const char *path; /* Path name. */ 289 int ignore; /* Ignore this entry (errno number.) */ 290 unsigned long fake_ino; /* Fake inode number for hard links. */ 291 int linked; /* Is the file hard linked? */ 292 int lastlink; /* Is it the last link? */ 293 char *link; /* Where a (sym)link points to. */ 294 filetype_t type; 295 mode_t mode; /* Not unlike those in struct stat. */ 296 uid_t uid; 297 gid_t gid; 298 off_t size; 299 time_t mtime; 300 dev_t rdev; 301 } entry_t; 302 303 void linked(entry_t *entry, struct stat *stp) 304 /* Return an "inode number" if a file could have links (link count > 1). 305 * Also return a path to the first link if you see the file again. 306 */ 307 { 308 static unsigned long new_fake_ino= 0; 309 static struct links { 310 struct links *next; 311 char *path; 312 ino_t ino; 313 dev_t dev; 314 nlink_t nlink; 315 unsigned long fake_ino; 316 } *links[1024]; 317 struct links **plp, *lp; 318 319 entry->linked= entry->lastlink= 0; 320 entry->fake_ino= 0; 321 entry->link= nil; 322 323 if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return; 324 325 plp= &links[stp->st_ino % arraysize(links)]; 326 while ((lp= *plp) != nil && (lp->ino != stp->st_ino 327 || lp->dev != stp->st_dev)) plp= &lp->next; 328 329 if (lp == nil) { 330 /* New file, store it with a new fake inode number. */ 331 *plp= lp= allocate(nil, sizeof(*lp)); 332 lp->next= nil; 333 lp->path= copystr(entry->path); 334 lp->ino= stp->st_ino; 335 lp->dev= stp->st_dev; 336 lp->nlink= stp->st_nlink; 337 lp->fake_ino= ++new_fake_ino; 338 } else { 339 entry->link= lp->path; 340 entry->linked= 1; 341 } 342 entry->fake_ino= lp->fake_ino; 343 344 if (--lp->nlink == 0) { 345 /* No need to remember this one, no more links coming. */ 346 *plp= lp->next; 347 forget(lp->path); 348 deallocate(lp); 349 entry->lastlink= 1; 350 } 351 } 352 353 char *tree; /* Tree to work on. */ 354 FILE *statefp; /* State file. */ 355 char *state_file; 356 FILE *difffp; /* File of differences. */ 357 char *diff_file; 358 359 entry_t *traverse(void) 360 /* Get one name from the directory tree. */ 361 { 362 static int depth; 363 static pathname_t path; 364 static entry_t entry; 365 static namelist_t **entries; 366 static size_t *trunc; 367 static size_t deep; 368 static namelist_t *newentries; 369 struct stat st; 370 371 recurse: 372 keep= KEEP_TRAVERSE; 373 374 if (deep == 0) { 375 /* Initialize for the root of the tree. */ 376 path_init(&path); 377 path_add(&path, tree); 378 entries= allocate(nil, 1 * sizeof(entries[0])); 379 entries[0]= allocate(nil, sizeof(*entries[0])); 380 entries[0]->next= nil; 381 entries[0]->name= copystr("/"); 382 trunc= allocate(nil, 1 * sizeof(trunc[0])); 383 trunc[0]= path_length(&path); 384 deep= 1; 385 } else 386 if (newentries != nil) { 387 /* Last entry was a directory, need to go down. */ 388 if (entry.ignore) { 389 /* Ouch, it is to be ignored! */ 390 while (newentries != nil) (void) pop_name(&newentries); 391 goto recurse; 392 } 393 if (++depth == deep) { 394 deep++; 395 entries= allocate(entries, deep * sizeof(entries[0])); 396 trunc= allocate(trunc, deep * sizeof(trunc[0])); 397 } 398 entries[depth]= newentries; 399 newentries= nil; 400 trunc[depth]= path_length(&path); 401 } else { 402 /* Pop up out of emptied directories. */ 403 while (entries[depth] == nil) { 404 if (depth == 0) return nil; /* Back at the root. */ 405 406 /* Go up one level. */ 407 depth--; 408 } 409 } 410 entry.name= pop_name(&entries[depth]); 411 path_trunc(&path, trunc[depth]); 412 path_add(&path, entry.name); 413 if (depth == 0) { 414 entry.path= "/"; 415 } else { 416 entry.path= path_name(&path) + trunc[0]; 417 if (entry.path[0] == '/') entry.path++; 418 } 419 entry.depth= depth; 420 entry.ignore= 0; 421 422 if (lstat(path_name(&path), &st) < 0) { 423 if (depth == 0 || errno != ENOENT) { 424 /* Something wrong with this entry, complain about 425 * it and ignore it further. 426 */ 427 entry.ignore= errno; 428 report(path_name(&path)); 429 return &entry; 430 } else { 431 /* Entry strangely nonexistent; simply continue. */ 432 goto recurse; 433 } 434 } 435 436 /* Don't cross mountpoints if -x is set. */ 437 if (xflag) { 438 if (xdev == NO_DEVICE) xdev= st.st_dev; 439 if (st.st_dev != xdev) { 440 /* Ignore the mountpoint. */ 441 entry.ignore= EXDEV; 442 return &entry; 443 } 444 } 445 446 entry.mode= st.st_mode & 07777; 447 entry.uid= st.st_uid; 448 entry.gid= st.st_gid; 449 entry.size= st.st_size; 450 entry.mtime= st.st_mtime; 451 entry.rdev= st.st_rdev; 452 453 linked(&entry, &st); 454 455 if (S_ISDIR(st.st_mode)) { 456 /* A directory. */ 457 entry.type= F_DIR; 458 459 /* Gather directory entries for the next traverse. */ 460 if ((newentries= collect(path_name(&path))) == nil 461 && errno != 0) { 462 entry.ignore= errno; 463 report(path_name(&path)); 464 } 465 } else 466 if (S_ISREG(st.st_mode)) { 467 /* A plain file. */ 468 entry.type= F_FILE; 469 } else 470 if (S_ISBLK(st.st_mode)) { 471 /* A block special file. */ 472 entry.type= F_BLK; 473 } else 474 if (S_ISCHR(st.st_mode)) { 475 /* A character special file. */ 476 entry.type= F_CHR; 477 } else 478 if (S_ISFIFO(st.st_mode)) { 479 /* A named pipe. */ 480 entry.type= F_PIPE; 481 } else 482 if (S_ISLNK(st.st_mode)) { 483 /* A symbolic link. */ 484 entry.type= F_LINK; 485 if ((entry.link= rdlink(path_name(&path), st.st_size)) == nil) { 486 entry.ignore= errno; 487 report(path_name(&path)); 488 } 489 } else { 490 /* Unknown type of file. */ 491 entry.ignore= EINVAL; 492 } 493 return &entry; 494 } 495 496 void checkstate(void) 497 { 498 if (ferror(statefp)) fatal(state_file); 499 } 500 501 void indent(int depth) 502 /* Provide indentation to show directory depth. */ 503 { 504 int n= BASE_INDENT * (depth - 1); 505 506 while (n >= 8) { 507 if (putc('\t', statefp) == EOF) checkstate(); 508 n-= 8; 509 } 510 while (n > 0) { 511 if (putc(' ', statefp) == EOF) checkstate(); 512 n--; 513 } 514 } 515 516 int print_name(FILE *fp, const char *name) 517 /* Encode a name. */ 518 { 519 const char *p; 520 int c; 521 522 for (p= name; (c= (unsigned char) *p) != 0; p++) { 523 if (c <= ' ' || c == '\\') { 524 fprintf(fp, "\\%03o", c); 525 if (ferror(fp)) return 0; 526 } else { 527 if (putc(c, fp) == EOF) return 0; 528 } 529 } 530 return 1; 531 } 532 533 void mkstatefile(void) 534 /* Make a state file out of the directory tree. */ 535 { 536 entry_t *entry; 537 538 while ((entry= traverse()) != nil) { 539 indent(entry->depth); 540 if (!print_name(statefp, entry->name)) checkstate(); 541 542 if (entry->ignore) { 543 fprintf(statefp, "\tignore (%s)\n", 544 strerror(entry->ignore)); 545 checkstate(); 546 continue; 547 } 548 549 switch (entry->type) { 550 case F_DIR: 551 fprintf(statefp, "\td%03o %u %u", 552 (unsigned) entry->mode, 553 (unsigned) entry->uid, (unsigned) entry->gid); 554 break; 555 case F_FILE: 556 fprintf(statefp, "\t%03o %u %u %lu %lu", 557 (unsigned) entry->mode, 558 (unsigned) entry->uid, (unsigned) entry->gid, 559 (unsigned long) entry->size, 560 (unsigned long) entry->mtime); 561 break; 562 case F_BLK: 563 case F_CHR: 564 fprintf(statefp, "\t%c%03o %u %u %x", 565 entry->type == F_BLK ? 'b' : 'c', 566 (unsigned) entry->mode, 567 (unsigned) entry->uid, (unsigned) entry->gid, 568 (unsigned) entry->rdev); 569 break; 570 case F_PIPE: 571 fprintf(statefp, "\tp%03o %u %u", 572 (unsigned) entry->mode, 573 (unsigned) entry->uid, (unsigned) entry->gid); 574 break; 575 case F_LINK: 576 fprintf(statefp, "\t-> "); 577 checkstate(); 578 (void) print_name(statefp, entry->link); 579 break; 580 } 581 checkstate(); 582 if (entry->fake_ino != 0) 583 fprintf(statefp, " %lu", entry->fake_ino); 584 if (entry->lastlink) 585 fprintf(statefp, " last"); 586 if (fputc('\n', statefp) == EOF) checkstate(); 587 } 588 fflush(statefp); 589 checkstate(); 590 } 591 592 char *read1line(FILE *fp) 593 /* Read one line from a file. Return null on EOF or error. */ 594 { 595 static char *line; 596 static size_t len; 597 size_t idx; 598 int c; 599 600 if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0])); 601 602 idx= 0; 603 while ((c= getc(fp)) != EOF && c != '\n') { 604 if (c < '\t') { 605 /* Control characters are not possible. */ 606 fprintf(stderr, 607 "remsync: control character in data file!\n"); 608 exit(1); 609 } 610 line[idx++]= c; 611 if (idx == len) { 612 line= allocate(line, (len*= 2) * sizeof(line[0])); 613 } 614 } 615 if (c == EOF) { 616 if (ferror(fp)) return nil; 617 if (idx == 0) return nil; 618 } 619 line[idx]= 0; 620 return line; 621 } 622 623 void getword(char **pline, char **parg, size_t *plen) 624 /* Get one word from a line, interpret octal escapes. */ 625 { 626 char *line= *pline; 627 char *arg= *parg; 628 size_t len= *plen; 629 int i; 630 int c; 631 size_t idx; 632 633 idx= 0; 634 while ((c= *line) != 0 && c != ' ' && c != '\t') { 635 line++; 636 if (c == '\\') { 637 c= 0; 638 for (i= 0; i < 3; i++) { 639 if ((unsigned) (*line - '0') >= 010) break; 640 c= (c << 3) | (*line - '0'); 641 line++; 642 } 643 } 644 arg[idx++]= c; 645 if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0])); 646 } 647 arg[idx]= 0; 648 *pline= line; 649 *parg= arg; 650 *plen= len; 651 } 652 653 void splitline(char *line, char ***pargv, size_t *pargc) 654 /* Split a line into an array of words. */ 655 { 656 static char **argv; 657 static size_t *lenv; 658 static size_t len; 659 size_t idx; 660 661 idx= 0; 662 for (;;) { 663 while (*line == ' ' || *line == '\t') line++; 664 665 if (*line == 0) break; 666 667 if (idx == len) { 668 len++; 669 argv= allocate(argv, len * sizeof(argv[0])); 670 lenv= allocate(lenv, len * sizeof(lenv[0])); 671 argv[idx]= allocate(nil, 16 * sizeof(argv[idx][0])); 672 lenv[idx]= 16; 673 } 674 getword(&line, &argv[idx], &lenv[idx]); 675 idx++; 676 } 677 *pargv= argv; 678 *pargc= idx; 679 } 680 681 int getattributes(entry_t *entry, int argc, char **argv) 682 /* Convert state or difference file info into file attributes. */ 683 { 684 int i; 685 int attr; 686 #define A_MODE1 0x01 /* Some of these attributes follow the name */ 687 #define A_MODE 0x02 688 #define A_OWNER 0x04 689 #define A_SIZETIME 0x08 690 #define A_DEV 0x10 691 #define A_LINK 0x20 692 693 switch (argv[0][0]) { 694 case 'd': 695 /* Directory. */ 696 entry->type= F_DIR; 697 attr= A_MODE1 | A_OWNER; 698 break; 699 case 'b': 700 /* Block device. */ 701 entry->type= F_BLK; 702 attr= A_MODE1 | A_OWNER | A_DEV; 703 break; 704 case 'c': 705 /* Character device. */ 706 entry->type= F_CHR; 707 attr= A_MODE1 | A_OWNER | A_DEV; 708 break; 709 case 'p': 710 /* Named pipe. */ 711 entry->type= F_PIPE; 712 attr= A_MODE1 | A_OWNER; 713 break; 714 case '-': 715 /* Symlink. */ 716 entry->type= F_LINK; 717 attr= A_LINK; 718 break; 719 default: 720 /* Normal file. */ 721 entry->type= F_FILE; 722 attr= A_MODE | A_OWNER | A_SIZETIME; 723 } 724 725 if (attr & (A_MODE | A_MODE1)) { 726 entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010); 727 } 728 i= 1; 729 if (attr & A_OWNER) { 730 if (i + 2 > argc) return 0; 731 entry->uid= strtoul(argv[i++], nil, 10); 732 entry->gid= strtoul(argv[i++], nil, 10); 733 } 734 if (attr & A_SIZETIME) { 735 if (i + 2 > argc) return 0; 736 entry->size= strtoul(argv[i++], nil, 10); 737 entry->mtime= strtoul(argv[i++], nil, 10); 738 } 739 if (attr & A_DEV) { 740 if (i + 1 > argc) return 0; 741 entry->rdev= strtoul(argv[i++], nil, 0x10); 742 } 743 if (attr & A_LINK) { 744 if (i + 1 > argc) return 0; 745 entry->link= argv[i++]; 746 } 747 entry->linked= entry->lastlink= 0; 748 if (i < argc) { 749 /* It has a fake inode number, so it is a hard link. */ 750 static struct links { /* List of hard links. */ 751 struct links *next; 752 unsigned long fake_ino; 753 char *path; 754 } *links[1024]; 755 struct links **plp, *lp; 756 unsigned long fake_ino; 757 758 fake_ino= strtoul(argv[i++], nil, 10); 759 760 plp= &links[fake_ino % arraysize(links)]; 761 while ((lp= *plp) != nil && lp->fake_ino != fake_ino) 762 plp= &lp->next; 763 764 if (lp == nil) { 765 /* New link. */ 766 *plp= lp= allocate(nil, sizeof(*lp)); 767 lp->next= nil; 768 lp->fake_ino= fake_ino; 769 lp->path= copystr(entry->path); 770 } else { 771 /* Linked to. */ 772 entry->link= lp->path; 773 entry->linked= 1; 774 } 775 776 if (i < argc) { 777 if (strcmp(argv[i++], "last") != 0) return 0; 778 779 /* Last hard link of a file. */ 780 forget(lp->path); 781 *plp= lp->next; 782 deallocate(lp); 783 entry->lastlink= 1; 784 } 785 } 786 if (i != argc) return 0; 787 return 1; 788 } 789 790 void state_syntax(off_t line) 791 { 792 fprintf(stderr, "remsync: %s: syntax error on line %lu\n", 793 state_file, (unsigned long) line); 794 exit(1); 795 } 796 797 entry_t *readstate(void) 798 /* Read one entry from the state file. */ 799 { 800 static entry_t entry; 801 static pathname_t path; 802 static size_t *trunc; 803 static size_t trunc_len; 804 static int base_indent; 805 char *line; 806 char **argv; 807 size_t argc; 808 static off_t lineno; 809 int indent, depth; 810 811 recurse: 812 keep= KEEP_STATE; 813 814 if (feof(statefp) || (line= read1line(statefp)) == nil) { 815 checkstate(); 816 return nil; 817 } 818 lineno++; 819 820 /* How far is this entry indented? */ 821 indent= 0; 822 while (*line != 0) { 823 if (*line == ' ') indent++; 824 else 825 if (*line == '\t') indent= (indent + 8) & ~7; 826 else 827 break; 828 line++; 829 } 830 if (indent > 0 && base_indent == 0) base_indent= indent; 831 depth= (base_indent == 0 ? 0 : indent / base_indent) + 1; 832 833 if (entry.ignore && depth > entry.depth) { 834 /* If the old directory is ignored, then so are its entries. */ 835 goto recurse; 836 } 837 entry.depth= depth; 838 839 splitline(line, &argv, &argc); 840 if (argc < 2) state_syntax(lineno); 841 842 if (trunc == nil) { 843 /* The root of the tree, initialize path. */ 844 if (argv[0][0] != '/') state_syntax(lineno); 845 path_init(&path); 846 path_add(&path, "/"); 847 trunc= allocate(nil, (trunc_len= 16) * sizeof(trunc[0])); 848 849 /* The root has depth 0. */ 850 entry.depth= 0; 851 trunc[0]= 0; 852 } else { 853 if (entry.depth > trunc_len) { 854 trunc= allocate(trunc, 855 (trunc_len*= 2) * sizeof(trunc[0])); 856 } 857 path_trunc(&path, trunc[entry.depth - 1]); 858 path_add(&path, argv[0]); 859 trunc[entry.depth]= path_length(&path); 860 } 861 862 entry.path= path_name(&path); 863 entry.name= argv[0]; 864 entry.link= nil; 865 if ((entry.ignore= strcmp(argv[1], "ignore") == 0)) { 866 return &entry; 867 } 868 if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno); 869 return &entry; 870 } 871 872 void checkdiff(void) 873 { 874 if (ferror(difffp)) fatal(diff_file); 875 } 876 877 enum { DELETE, REPLACE, COPY, SIMILAR, EQUAL, ADD } 878 compare(entry_t *remote, entry_t *local) 879 /* Compare the local and remote entries and tell what need to be done. */ 880 { 881 int cmp; 882 883 /* Surplus entries? */ 884 if (local == nil) return DELETE; 885 if (remote == nil) return ADD; 886 887 /* Extra directory entries? */ 888 if (remote->depth > local->depth) return DELETE; 889 if (local->depth > remote->depth) return ADD; 890 891 /* Compare names. */ 892 cmp= strcmp(remote->name, local->name); 893 if (cmp < 0) return DELETE; 894 if (cmp > 0) return ADD; 895 896 /* The files have the same name. Ignore one, ignore the other. */ 897 if (remote->ignore || local->ignore) { 898 remote->ignore= local->ignore= 1; 899 return EQUAL; 900 } 901 902 /* Reasons for replacement? */ 903 if (remote->type != local->type) return REPLACE; 904 905 /* Should be hard linked to the same file. */ 906 if (remote->linked || local->linked) { 907 if (!remote->linked || !local->linked) return REPLACE; 908 if (strcmp(remote->link, local->link) != 0) return REPLACE; 909 } 910 911 switch (remote->type) { 912 case F_FILE: 913 if (uflag) { 914 if (remote->mtime < local->mtime) return COPY; 915 } else { 916 if (remote->size != local->size 917 || remote->mtime != local->mtime) 918 return COPY; 919 } 920 goto check_modes; 921 case F_BLK: 922 case F_CHR: 923 if (remote->rdev != local->rdev) return REPLACE; 924 goto check_modes; 925 case F_DIR: 926 case F_PIPE: 927 check_modes: 928 if (remote->mode != local->mode 929 || remote->uid != local->uid 930 || remote->gid != local->gid) return SIMILAR; 931 break; 932 case F_LINK: 933 if (strcmp(remote->link, local->link) != 0) return REPLACE; 934 break; 935 } 936 return EQUAL; 937 } 938 939 void delete(entry_t *old) 940 /* Emit an instruction to remove an entry. */ 941 { 942 if (old->ignore) return; 943 if (uflag) return; 944 945 fprintf(difffp, "rm "); 946 checkdiff(); 947 if (!print_name(difffp, old->path)) checkdiff(); 948 if (putc('\n', difffp) == EOF) checkdiff(); 949 if (vflag) fprintf(stderr, "rm %s\n", old->path); 950 } 951 952 void change_modes(entry_t *old, entry_t *new) 953 /* Emit an instruction to change the attributes of an entry. */ 954 { 955 if (new->ignore) return; 956 957 fprintf(difffp, "chmod "); 958 checkdiff(); 959 if (!print_name(difffp, new->path)) checkdiff(); 960 fprintf(difffp, " %03o %u %u\n", 961 (unsigned) new->mode, 962 (unsigned) new->uid, (unsigned) new->gid); 963 checkdiff(); 964 if (vflag && old->mode != new->mode) { 965 fprintf(stderr, "chmod %s %03o %u %u\n", 966 new->path, 967 (unsigned) new->mode, 968 (unsigned) new->uid, (unsigned) new->gid); 969 } 970 } 971 972 int cat(int f, off_t size) 973 /* Include the contents of a file in the differences file. */ 974 { 975 ssize_t n; 976 unsigned char buf[1024 << sizeof(int)]; 977 unsigned char *p; 978 int c; 979 980 if (Dflag) return 1; /* Debug: Don't need the file contents. */ 981 982 while ((n= read(f, buf, sizeof(buf))) > 0) { 983 p= buf; 984 do { 985 if (size == 0) { 986 /* File suddenly larger. */ 987 errno= EINVAL; 988 return 0; 989 } 990 c= *p++; 991 if (putc(c, difffp) == EOF) checkdiff(); 992 size--; 993 } while (--n != 0); 994 } 995 if (size > 0) { 996 int err= errno; 997 998 /* File somehow shrunk, pad it out. */ 999 do { 1000 if (putc(0, difffp) == EOF) checkdiff(); 1001 } while (--size != 0); 1002 errno= n == 0 ? EINVAL : err; 1003 n= -1; 1004 } 1005 return n == 0; 1006 } 1007 1008 void add(entry_t *old, entry_t *new) 1009 /* Emit an instruction to add an entry. */ 1010 { 1011 pathname_t file; 1012 int f; 1013 1014 if (new->ignore) return; 1015 1016 if (new->linked) { 1017 /* This file is to be a hard link to an existing file. */ 1018 fprintf(difffp, "ln "); 1019 checkdiff(); 1020 if (!print_name(difffp, new->link)) checkdiff(); 1021 if (fputc(' ', difffp) == EOF) checkdiff(); 1022 if (!print_name(difffp, new->path)) checkdiff(); 1023 if (fputc('\n', difffp) == EOF) checkdiff(); 1024 if (vflag) { 1025 fprintf(stderr, "ln %s %s\n", new->link, new->path); 1026 } 1027 return; 1028 } 1029 1030 /* Add some other type of file. */ 1031 fprintf(difffp, "add "); 1032 checkdiff(); 1033 if (!print_name(difffp, new->path)) checkdiff(); 1034 1035 switch (new->type) { 1036 case F_DIR: 1037 fprintf(difffp, " d%03o %u %u\n", 1038 (unsigned) new->mode, 1039 (unsigned) new->uid, (unsigned) new->gid); 1040 if (vflag) fprintf(stderr, "mkdir %s\n", new->path); 1041 break; 1042 case F_FILE: 1043 path_init(&file); 1044 path_add(&file, tree); 1045 path_add(&file, new->path); 1046 if ((f= open(path_name(&file), O_RDONLY)) < 0) { 1047 report(path_name(&file)); 1048 path_drop(&file); 1049 fprintf(difffp, " ignore\n"); 1050 break; 1051 } 1052 fprintf(difffp, " %03o %u %u %lu %lu\n", 1053 (unsigned) new->mode, 1054 (unsigned) new->uid, (unsigned) new->gid, 1055 (unsigned long) new->size, 1056 (unsigned long) new->mtime); 1057 checkdiff(); 1058 if (!cat(f, new->size)) { 1059 int err= errno; 1060 report(path_name(&file)); 1061 fprintf(difffp, "old "); 1062 checkdiff(); 1063 print_name(difffp, err == EINVAL 1064 ? "File changed when copied" : strerror(err)); 1065 fputc('\n', difffp); 1066 checkdiff(); 1067 } else { 1068 if (vflag) { 1069 fprintf(stderr, "%s %s\n", 1070 old == nil ? "add" : 1071 old->mtime > new->mtime ? 1072 "restore" : "update", 1073 new->path); 1074 } 1075 } 1076 close(f); 1077 path_drop(&file); 1078 break; 1079 case F_BLK: 1080 case F_CHR: 1081 fprintf(difffp, " %c%03o %u %u %lx\n", 1082 new->type == F_BLK ? 'b' : 'c', 1083 (unsigned) new->mode, 1084 (unsigned) new->uid, (unsigned) new->gid, 1085 (unsigned long) new->rdev); 1086 if (vflag) fprintf(stderr, "mknod %s\n", new->path); 1087 break; 1088 case F_PIPE: 1089 fprintf(difffp, " p%03o %u %u\n", 1090 (unsigned) new->mode, 1091 (unsigned) new->uid, (unsigned) new->gid); 1092 if (vflag) fprintf(stderr, "mkfifo %s\n", new->path); 1093 break; 1094 case F_LINK: 1095 fprintf(difffp, " -> "); 1096 checkdiff(); 1097 (void) print_name(difffp, new->link); 1098 checkdiff(); 1099 fputc('\n', difffp); 1100 if (vflag) { 1101 fprintf(stderr, "ln -s %s %s\n", new->link, new->path); 1102 } 1103 break; 1104 } 1105 checkdiff(); 1106 } 1107 1108 void mkdifferences(void) 1109 { 1110 entry_t *remote; 1111 entry_t *local; 1112 1113 remote= readstate(); 1114 local= traverse(); 1115 1116 while (remote != nil || local != nil) { 1117 switch (compare(remote, local)) { 1118 case DELETE: 1119 /* Remove the remote file. */ 1120 delete(remote); 1121 remote->ignore= 1; 1122 remote= readstate(); 1123 break; 1124 case REPLACE: 1125 /* Replace the remote file with the local one. */ 1126 if (remote->type == F_FILE && local->type == F_FILE 1127 && !local->linked) { 1128 /* Don't overwrite, remove first. */ 1129 delete(remote); 1130 } 1131 /*FALL THROUGH*/ 1132 case COPY: 1133 /* Overwrite the remote file with the local one. */ 1134 add(remote, local); 1135 remote->ignore= 1; 1136 goto skip2; 1137 case SIMILAR: 1138 /* About the same, but the attributes need changing. */ 1139 change_modes(remote, local); 1140 goto skip2; 1141 case EQUAL: 1142 skip2: 1143 /* Skip two files. */ 1144 remote= readstate(); 1145 local= traverse(); 1146 break; 1147 case ADD: 1148 /* Add the local file. */ 1149 add(nil, local); 1150 local= traverse(); 1151 break; 1152 } 1153 } 1154 fprintf(difffp, "end\n"); 1155 fflush(difffp); 1156 checkdiff(); 1157 } 1158 1159 void apply_remove(pathname_t *pp) 1160 /* Remove an obsolete file. */ 1161 { 1162 struct stat st; 1163 1164 if (lstat(path_name(pp), &st) < 0) { 1165 if (errno != ENOENT) report(path_name(pp)); 1166 return; 1167 } 1168 1169 if (S_ISDIR(st.st_mode)) { 1170 /* Recursively delete directories. */ 1171 size_t len; 1172 namelist_t *entries; 1173 1174 if ((entries= collect(path_name(pp))) == nil && errno != 0) { 1175 report(path_name(pp)); 1176 return; 1177 } 1178 len= path_length(pp); 1179 1180 while (entries != nil) { 1181 path_add(pp, pop_name(&entries)); 1182 apply_remove(pp); 1183 path_trunc(pp, len); 1184 } 1185 if (rmdir(path_name(pp)) < 0) { 1186 report(path_name(pp)); 1187 return; 1188 } 1189 if (vflag) fprintf(stderr, "rmdir %s\n", path_name(pp)); 1190 } else { 1191 /* Some other type of file. */ 1192 if (unlink(path_name(pp)) < 0) { 1193 report(path_name(pp)); 1194 return; 1195 } 1196 if (vflag) fprintf(stderr, "rm %s\n", path_name(pp)); 1197 } 1198 } 1199 1200 void apply_mkold(const char *file, const char *err) 1201 /* Make a file very old. (An error occurred when it was added.) */ 1202 { 1203 struct utimbuf utb; 1204 1205 utb.actime= utb.modtime= 0; 1206 if (utime(file, &utb) < 0) { 1207 report(file); 1208 return; 1209 } 1210 fprintf(stderr, "made %s look old", file); 1211 if (err != nil) 1212 fprintf(stderr, " due to a remote problem: %s\n", err); 1213 else 1214 fprintf(stderr, "\n"); 1215 } 1216 1217 void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk) 1218 /* Change mode and ownership. */ 1219 { 1220 struct stat st; 1221 1222 if (lstat(file, &st) < 0) { 1223 report(file); 1224 return; 1225 } 1226 if ((st.st_mode & 07777) != mode) { 1227 if (chmod(file, mode) < 0) { 1228 report(file); 1229 return; 1230 } 1231 if (vflag && talk) { 1232 fprintf(stderr, "chmod %03o %s\n", 1233 (unsigned) mode, file); 1234 } 1235 } 1236 if (st.st_uid != uid || st.st_gid != gid) { 1237 if (chown(file, uid, gid) < 0) { 1238 if (errno != EPERM) report(file); 1239 return; 1240 } 1241 if (vflag && talk) { 1242 fprintf(stderr, "chown %u:%u %s\n", 1243 (unsigned) uid, (unsigned) gid, file); 1244 } 1245 } 1246 } 1247 1248 void apply_add(pathname_t *pp, entry_t *entry) 1249 /* Add or replace a file. */ 1250 { 1251 const char *file; 1252 off_t size; 1253 int f; 1254 unsigned char buf[1024 << sizeof(int)]; 1255 unsigned char *p; 1256 int c; 1257 int dirty; 1258 struct stat st; 1259 struct utimbuf utb; 1260 1261 if (entry->ignore) return; 1262 1263 if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE 1264 || !S_ISREG(st.st_mode))) { 1265 apply_remove(pp); 1266 } 1267 1268 file= path_name(pp); 1269 1270 switch (entry->type) { 1271 case F_DIR: 1272 if (mkdir(file, entry->mode) < 0) { 1273 report(file); 1274 return; 1275 } 1276 if (vflag) fprintf(stderr, "mkdir %s\n", file); 1277 break; 1278 case F_FILE: 1279 size= entry->size; 1280 1281 f= -1; 1282 st.st_mode= 0; 1283 if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) { 1284 f= open(file, O_WRONLY | O_CREAT | O_TRUNC, 1285 entry->mode); 1286 if (f < 0) { 1287 (void) chmod(file, entry->mode | 0200); 1288 f= open(file, O_WRONLY | O_CREAT | O_TRUNC, 1289 entry->mode); 1290 } 1291 if (f < 0) { 1292 (void) unlink(file); 1293 f= open(file, O_WRONLY | O_CREAT | O_TRUNC, 1294 entry->mode); 1295 } 1296 if (f < 0) report(file); 1297 } 1298 dirty= (f >= 0); 1299 p= buf; 1300 while (size > 0 && (c= getc(difffp)) != EOF) { 1301 size--; 1302 *p++= c; 1303 if (p == arraylimit(buf) || size == 0) { 1304 if (f >= 0 && write(f, buf, p - buf) < 0) { 1305 report(file); 1306 close(f); 1307 f= -1; 1308 } 1309 p= buf; 1310 } 1311 } 1312 if (size > 0) { 1313 if (ferror(difffp)) report(diff_file); 1314 if (feof(difffp)) { 1315 fprintf(stderr, "remspec: %s: premature EOF\n", 1316 diff_file); 1317 } 1318 if (dirty) apply_mkold(file, nil); 1319 exit(1); 1320 } 1321 if (f < 0) { 1322 if (dirty) apply_mkold(file, nil); 1323 return; 1324 } 1325 close(f); 1326 if (vflag) { 1327 fprintf(stderr, st.st_mode == 0 ? "add %s\n" 1328 : entry->mtime >= st.st_mtime 1329 ? "update %s\n" : "restore %s\n", file); 1330 } 1331 utb.actime= time(nil); 1332 utb.modtime= entry->mtime; 1333 if (utime(file, &utb) < 0) report(file); 1334 break; 1335 case F_BLK: 1336 if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) { 1337 report(file); 1338 return; 1339 } 1340 if (vflag) { 1341 fprintf(stderr, "mknod %s b %d %d\n", file, 1342 major(entry->rdev), minor(entry->rdev)); 1343 } 1344 break; 1345 case F_CHR: 1346 if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) { 1347 report(file); 1348 return; 1349 } 1350 if (vflag) { 1351 fprintf(stderr, "mknod %s c %d %d\n", file, 1352 major(entry->rdev), minor(entry->rdev)); 1353 } 1354 break; 1355 case F_PIPE: 1356 if (mknod(file, S_IFIFO | entry->mode, 0) < 0) { 1357 report(file); 1358 return; 1359 } 1360 if (vflag) fprintf(stderr, "mknod %s p\n", file); 1361 break; 1362 case F_LINK: 1363 if (symlink(entry->link, file) < 0) { 1364 report(file); 1365 return; 1366 } 1367 if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file); 1368 return; 1369 } 1370 apply_chmod(file, entry->mode, entry->uid, entry->gid, 0); 1371 } 1372 1373 void apply_link(const char *file, pathname_t *pp) 1374 /* Hard link *pp to file. */ 1375 { 1376 struct stat st1, st2; 1377 1378 if (lstat(file, &st1) < 0) { 1379 report(file); 1380 return; 1381 } 1382 if (lstat(path_name(pp), &st2) >= 0) { 1383 if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev) 1384 return; 1385 apply_remove(pp); 1386 if (lstat(path_name(pp), &st2) >= 0) return; 1387 } 1388 if (link(file, path_name(pp)) < 0) { 1389 fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp), 1390 strerror(errno)); 1391 excode= 1; 1392 return; 1393 } 1394 if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp)); 1395 } 1396 1397 void diff_syntax(const char *line) 1398 { 1399 fprintf(stderr, "remsync: %s: syntax error on this line: %s\n", 1400 diff_file, line); 1401 exit(1); 1402 } 1403 1404 void apply_differences(void) 1405 /* Update a tree to a list of differences derived from a remote tree. */ 1406 { 1407 char *line; 1408 char **argv; 1409 size_t argc; 1410 pathname_t path, link; 1411 size_t trunc; 1412 1413 path_init(&path); 1414 path_init(&link); 1415 path_add(&path, tree); 1416 path_add(&link, tree); 1417 trunc= path_length(&path); 1418 1419 while (!feof(difffp) && (line= read1line(difffp)) != nil) { 1420 splitline(line, &argv, &argc); 1421 if (argc == 0) diff_syntax(line); 1422 1423 path_trunc(&path, trunc); 1424 1425 if (strcmp(argv[0], "add") == 0) { 1426 entry_t entry; 1427 1428 if (argc < 3) diff_syntax(line); 1429 path_add(&path, argv[1]); 1430 entry.ignore= (strcmp(argv[2], "ignore") == 0); 1431 if (!entry.ignore && !getattributes(&entry, 1432 argc - 2, argv + 2)) 1433 diff_syntax(line); 1434 apply_add(&path, &entry); 1435 } else 1436 if (strcmp(argv[0], "rm") == 0) { 1437 if (argc != 2) diff_syntax(line); 1438 path_add(&path, argv[1]); 1439 apply_remove(&path); 1440 } else 1441 if (strcmp(argv[0], "ln") == 0) { 1442 if (argc != 3) diff_syntax(line); 1443 path_trunc(&link, trunc); 1444 path_add(&link, argv[1]); 1445 path_add(&path, argv[2]); 1446 apply_link(path_name(&link), &path); 1447 } else 1448 if (strcmp(argv[0], "chmod") == 0) { 1449 if (argc != 5) diff_syntax(line); 1450 path_add(&path, argv[1]); 1451 apply_chmod(path_name(&path), 1452 strtoul(argv[2], nil, 010), 1453 strtoul(argv[3], nil, 10), 1454 strtoul(argv[4], nil, 10), 1455 1); 1456 } else 1457 if (strcmp(argv[0], "old") == 0) { 1458 if (argc != 3) diff_syntax(line); 1459 path_add(&path, argv[1]); 1460 apply_mkold(path_name(&path), argv[2]); 1461 } else 1462 if (strcmp(argv[0], "end") == 0) { 1463 if (argc != 1) diff_syntax(line); 1464 break; 1465 } else { 1466 diff_syntax(line); 1467 } 1468 } 1469 checkdiff(); 1470 } 1471 1472 void usage(void) 1473 { 1474 fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n"); 1475 fprintf(stderr, " remsync -duxvD tree [state-file [diff-file]]\n"); 1476 fprintf(stderr, " remsync [-xv] tree [diff-file]\n"); 1477 exit(1); 1478 } 1479 1480 int main(int argc, char **argv) 1481 { 1482 int i; 1483 1484 for (i= 1; i < argc && argv[i][0] == '-'; i++) { 1485 char *p= argv[i] + 1; 1486 1487 if (p[0] == '-' && p[1] == 0) { i++; break; } 1488 1489 while (*p != 0) { 1490 switch (*p++) { 1491 case 's': sflag= 1; break; 1492 case 'd': dflag= 1; break; 1493 case 'u': uflag= 1; break; 1494 case 'x': xflag= 1; break; 1495 case 'D': Dflag= 1; break; 1496 case 'v': vflag= 1; break; 1497 default: usage(); 1498 } 1499 } 1500 } 1501 if (sflag && dflag) usage(); 1502 if (sflag && uflag) usage(); 1503 if (!sflag && !dflag && uflag) usage(); 1504 if (!dflag && Dflag) usage(); 1505 1506 if (i == argc) usage(); 1507 tree= argv[i++]; 1508 1509 if (sflag) { 1510 /* Make a state file. */ 1511 state_file= i < argc ? argv[i++] : "-"; 1512 if (i != argc) usage(); 1513 1514 statefp= stdout; 1515 if (strcmp(state_file, "-") != 0) { 1516 if ((statefp= fopen(state_file, "w")) == nil) 1517 fatal(state_file); 1518 } 1519 mkstatefile(); 1520 } else 1521 if (dflag) { 1522 /* Make a file of differences. */ 1523 state_file= i < argc ? argv[i++] : "-"; 1524 1525 diff_file= i < argc ? argv[i++] : "-"; 1526 if (i != argc) usage(); 1527 1528 statefp= stdin; 1529 if (strcmp(state_file, "-") != 0) { 1530 if ((statefp= fopen(state_file, "r")) == nil) 1531 fatal(state_file); 1532 } 1533 1534 difffp= stdout; 1535 if (strcmp(diff_file, "-") != 0) { 1536 if ((difffp= fopen(diff_file, "w")) == nil) 1537 fatal(diff_file); 1538 } 1539 mkdifferences(); 1540 } else { 1541 /* Apply a file of differences. */ 1542 diff_file= i < argc ? argv[i++] : "-"; 1543 if (i != argc) usage(); 1544 1545 difffp= stdin; 1546 if (strcmp(diff_file, "-") != 0) { 1547 if ((difffp= fopen(diff_file, "r")) == nil) 1548 fatal(diff_file); 1549 } 1550 apply_differences(); 1551 } 1552 exit(excode); 1553 } 1554