00001
00002
00003
00004 #define nil 0
00005 #include <sys/types.h>
00006 #include <sys/stat.h>
00007 #include <stdio.h>
00008 #include <stdlib.h>
00009 #include <stdarg.h>
00010 #include <string.h>
00011 #include <dirent.h>
00012 #include <unistd.h>
00013 #include <fcntl.h>
00014 #include <errno.h>
00015 #include <limits.h>
00016 #include <time.h>
00017 #include <utime.h>
00018
00019 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
00020 #define arraylimit(a) ((a) + arraysize(a))
00021
00022 #ifndef major
00023 #define major(dev) ((int) ((dev) >> 8))
00024 #define minor(dev) ((int) ((dev) & 0xFF))
00025 #endif
00026
00027 #ifndef S_ISLNK
00028
00029 #define S_ISLNK(mode) (0)
00030 #define lstat stat
00031 #define symlink(path1, path2) (errno= ENOSYS, -1)
00032 #define readlink(path, buf, len) (errno= ENOSYS, -1)
00033 #endif
00034
00035 int sflag;
00036 int dflag;
00037 int uflag;
00038 int xflag;
00039 int Dflag;
00040 int vflag;
00041
00042 #define NO_DEVICE (-1)
00043 dev_t xdev= NO_DEVICE;
00044
00045 int excode= 0;
00046
00047 #define BASE_INDENT 2
00048
00049 void report(const char *label)
00050 {
00051 fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno));
00052 excode= 1;
00053 }
00054
00055 void fatal(const char *label)
00056 {
00057 report(label);
00058 exit(1);
00059 }
00060
00061 void *allocate(void *mem, size_t size)
00062 {
00063 if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) {
00064 fprintf(stderr, "remsync: Out of memory: %s\n",
00065 strerror(errno));
00066 exit(1);
00067 }
00068 return mem;
00069 }
00070
00071 void deallocate(void *mem)
00072 {
00073 if (mem != nil) free(mem);
00074 }
00075
00076
00077
00078
00079 int keep;
00080 #define KEEP_STATE 0
00081 #define KEEP_TRAVERSE 1
00082
00083 void forget(void *mem)
00084
00085 {
00086 static void *death_row[2][50];
00087 static void **dp[2]= { death_row[0], death_row[1] };
00088
00089 deallocate(*dp[keep]);
00090 *dp[keep]++= mem;
00091 if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep];
00092 }
00093
00094 char *copystr(const char *s)
00095 {
00096 char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0]));
00097 strcpy(c, s);
00098 return c;
00099 }
00100
00101 typedef struct pathname {
00102 char *path;
00103 size_t idx;
00104 size_t lim;
00105 } pathname_t;
00106
00107 void path_init(pathname_t *pp)
00108
00109 {
00110 pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0]));
00111 pp->path[pp->idx= 0]= 0;
00112 }
00113
00114 void path_add(pathname_t *pp, const char *name)
00115
00116 {
00117 size_t lim;
00118 char *p;
00119 int slash;
00120
00121 lim= pp->idx + strlen(name) + 2;
00122
00123 if (lim > pp->lim) {
00124 pp->lim= lim + lim/2;
00125 pp->path= allocate(pp->path, pp->lim * sizeof(pp->path[0]));
00126 }
00127
00128 p= pp->path + pp->idx;
00129 slash= (pp->idx > 0);
00130 if (pp->idx == 1 && p[-1] == '/') p--;
00131
00132 while (*name != 0) {
00133 if (*name == '/') {
00134 slash= 1;
00135 } else {
00136 if (slash) { *p++ = '/'; slash= 0; }
00137 *p++= *name;
00138 }
00139 name++;
00140 }
00141 if (slash && p == pp->path) *p++= '/';
00142 *p = 0;
00143 pp->idx= p - pp->path;
00144 }
00145
00146 void path_trunc(pathname_t *pp, size_t didx)
00147
00148 {
00149 pp->path[pp->idx= didx]= 0;
00150 }
00151
00152 #if kept_for_comments_only
00153
00154 const char *path_name(const pathname_t *pp)
00155
00156 {
00157 return pp->path;
00158 }
00159
00160 size_t path_length(const pathname_t *pp)
00161
00162 {
00163 return pp->idx;
00164 }
00165
00166 void path_drop(pathname_t *pp)
00167
00168 {
00169 free(pp->path);
00170 }
00171 #endif
00172
00173 #define path_name(pp) ((const char *) (pp)->path)
00174 #define path_length(pp) ((pp)->idx)
00175 #define path_drop(pp) free((void *) (pp)->path)
00176
00177 typedef struct namelist {
00178 struct namelist *next;
00179 char *name;
00180 } namelist_t;
00181
00182 char *rdlink(const char *link, off_t size)
00183
00184 {
00185 static char *path= nil;
00186 static size_t len= 0;
00187 size_t n;
00188
00189 if (len <= size) {
00190 path= allocate(path, (len= size * 2) * sizeof(path[0]));
00191 }
00192 if ((n= readlink(link, path, len)) == -1) return nil;
00193 path[n]= 0;
00194 return path;
00195 }
00196
00197 void sort(namelist_t **anl)
00198
00199
00200
00201 {
00202 namelist_t *nl1, **mid;
00203 namelist_t *nl2;
00204
00205 nl1= *(mid= &(*anl)->next);
00206 do {
00207 if ((nl1= nl1->next) == nil) break;
00208 mid= &(*mid)->next;
00209 } while ((nl1= nl1->next) != nil);
00210
00211 nl2= *mid;
00212 *mid= nil;
00213
00214 if ((*anl)->next != nil) sort(anl);
00215 if (nl2->next != nil) sort(&nl2);
00216
00217 nl1= *anl;
00218 for (;;) {
00219 if (strcmp(nl1->name, nl2->name)<=0) {
00220 if ((nl1= *(anl= &nl1->next)) == nil) {
00221 *anl= nl2;
00222 break;
00223 }
00224 } else {
00225 *anl= nl2;
00226 nl2= *(anl= &nl2->next);
00227 *anl= nl1;
00228 if (nl2 == nil) break;
00229 }
00230 }
00231 }
00232
00233 namelist_t *collect(const char *dir)
00234
00235
00236
00237 {
00238 namelist_t *names, **pn= &names;
00239 DIR *dp;
00240 struct dirent *entry;
00241
00242 if ((dp= opendir(dir)) == nil) return nil;
00243
00244 while ((entry= readdir(dp)) != nil) {
00245 if (entry->d_name[0] == '.'
00246 && (entry->d_name[1] == 0
00247 || (entry->d_name[1] == '.'
00248 && entry->d_name[2] == 0))) {
00249 continue;
00250 }
00251 *pn= allocate(nil, sizeof(**pn));
00252 (*pn)->name= copystr(entry->d_name);
00253 pn= &(*pn)->next;
00254 }
00255 closedir(dp);
00256 *pn= nil;
00257 errno= 0;
00258 if (names != nil && names->next != nil) sort(&names);
00259 return names;
00260 }
00261
00262 char *pop_name(namelist_t **names)
00263
00264 {
00265 char *name;
00266 namelist_t *junk;
00267
00268 junk= *names;
00269 *names= junk->next;
00270 name= junk->name;
00271 deallocate(junk);
00272 forget(name);
00273 return name;
00274 }
00275
00276 typedef enum filetype {
00277 F_DIR,
00278 F_FILE,
00279 F_BLK,
00280 F_CHR,
00281 F_PIPE,
00282 F_LINK
00283 } filetype_t;
00284
00285 typedef struct entry {
00286 int depth;
00287 const char *name;
00288 const char *path;
00289 int ignore;
00290 unsigned long fake_ino;
00291 int linked;
00292 int lastlink;
00293 char *link;
00294 filetype_t type;
00295 mode_t mode;
00296 uid_t uid;
00297 gid_t gid;
00298 off_t size;
00299 time_t mtime;
00300 dev_t rdev;
00301 } entry_t;
00302
00303 void linked(entry_t *entry, struct stat *stp)
00304
00305
00306
00307 {
00308 static unsigned long new_fake_ino= 0;
00309 static struct links {
00310 struct links *next;
00311 char *path;
00312 ino_t ino;
00313 dev_t dev;
00314 nlink_t nlink;
00315 unsigned long fake_ino;
00316 } *links[1024];
00317 struct links **plp, *lp;
00318
00319 entry->linked= entry->lastlink= 0;
00320 entry->fake_ino= 0;
00321 entry->link= nil;
00322
00323 if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return;
00324
00325 plp= &links[stp->st_ino % arraysize(links)];
00326 while ((lp= *plp) != nil && (lp->ino != stp->st_ino
00327 || lp->dev != stp->st_dev)) plp= &lp->next;
00328
00329 if (lp == nil) {
00330
00331 *plp= lp= allocate(nil, sizeof(*lp));
00332 lp->next= nil;
00333 lp->path= copystr(entry->path);
00334 lp->ino= stp->st_ino;
00335 lp->dev= stp->st_dev;
00336 lp->nlink= stp->st_nlink;
00337 lp->fake_ino= ++new_fake_ino;
00338 } else {
00339 entry->link= lp->path;
00340 entry->linked= 1;
00341 }
00342 entry->fake_ino= lp->fake_ino;
00343
00344 if (--lp->nlink == 0) {
00345
00346 *plp= lp->next;
00347 forget(lp->path);
00348 deallocate(lp);
00349 entry->lastlink= 1;
00350 }
00351 }
00352
00353 char *tree;
00354 FILE *statefp;
00355 char *state_file;
00356 FILE *difffp;
00357 char *diff_file;
00358
00359 entry_t *traverse(void)
00360
00361 {
00362 static int depth;
00363 static pathname_t path;
00364 static entry_t entry;
00365 static namelist_t **entries;
00366 static size_t *trunc;
00367 static size_t deep;
00368 static namelist_t *newentries;
00369 struct stat st;
00370
00371 recurse:
00372 keep= KEEP_TRAVERSE;
00373
00374 if (deep == 0) {
00375
00376 path_init(&path);
00377 path_add(&path, tree);
00378 entries= allocate(nil, 1 * sizeof(entries[0]));
00379 entries[0]= allocate(nil, sizeof(*entries[0]));
00380 entries[0]->next= nil;
00381 entries[0]->name= copystr("/");
00382 trunc= allocate(nil, 1 * sizeof(trunc[0]));
00383 trunc[0]= path_length(&path);
00384 deep= 1;
00385 } else
00386 if (newentries != nil) {
00387
00388 if (entry.ignore) {
00389
00390 while (newentries != nil) (void) pop_name(&newentries);
00391 goto recurse;
00392 }
00393 if (++depth == deep) {
00394 deep++;
00395 entries= allocate(entries, deep * sizeof(entries[0]));
00396 trunc= allocate(trunc, deep * sizeof(trunc[0]));
00397 }
00398 entries[depth]= newentries;
00399 newentries= nil;
00400 trunc[depth]= path_length(&path);
00401 } else {
00402
00403 while (entries[depth] == nil) {
00404 if (depth == 0) return nil;
00405
00406
00407 depth--;
00408 }
00409 }
00410 entry.name= pop_name(&entries[depth]);
00411 path_trunc(&path, trunc[depth]);
00412 path_add(&path, entry.name);
00413 if (depth == 0) {
00414 entry.path= "/";
00415 } else {
00416 entry.path= path_name(&path) + trunc[0];
00417 if (entry.path[0] == '/') entry.path++;
00418 }
00419 entry.depth= depth;
00420 entry.ignore= 0;
00421
00422 if (lstat(path_name(&path), &st) < 0) {
00423 if (depth == 0 || errno != ENOENT) {
00424
00425
00426
00427 entry.ignore= errno;
00428 report(path_name(&path));
00429 return &entry;
00430 } else {
00431
00432 goto recurse;
00433 }
00434 }
00435
00436
00437 if (xflag) {
00438 if (xdev == NO_DEVICE) xdev= st.st_dev;
00439 if (st.st_dev != xdev) {
00440
00441 entry.ignore= EXDEV;
00442 return &entry;
00443 }
00444 }
00445
00446 entry.mode= st.st_mode & 07777;
00447 entry.uid= st.st_uid;
00448 entry.gid= st.st_gid;
00449 entry.size= st.st_size;
00450 entry.mtime= st.st_mtime;
00451 entry.rdev= st.st_rdev;
00452
00453 linked(&entry, &st);
00454
00455 if (S_ISDIR(st.st_mode)) {
00456
00457 entry.type= F_DIR;
00458
00459
00460 if ((newentries= collect(path_name(&path))) == nil
00461 && errno != 0) {
00462 entry.ignore= errno;
00463 report(path_name(&path));
00464 }
00465 } else
00466 if (S_ISREG(st.st_mode)) {
00467
00468 entry.type= F_FILE;
00469 } else
00470 if (S_ISBLK(st.st_mode)) {
00471
00472 entry.type= F_BLK;
00473 } else
00474 if (S_ISCHR(st.st_mode)) {
00475
00476 entry.type= F_CHR;
00477 } else
00478 if (S_ISFIFO(st.st_mode)) {
00479
00480 entry.type= F_PIPE;
00481 } else
00482 if (S_ISLNK(st.st_mode)) {
00483
00484 entry.type= F_LINK;
00485 if ((entry.link= rdlink(path_name(&path), st.st_size)) == nil) {
00486 entry.ignore= errno;
00487 report(path_name(&path));
00488 }
00489 } else {
00490
00491 entry.ignore= EINVAL;
00492 }
00493 return &entry;
00494 }
00495
00496 void checkstate(void)
00497 {
00498 if (ferror(statefp)) fatal(state_file);
00499 }
00500
00501 void indent(int depth)
00502
00503 {
00504 int n= BASE_INDENT * (depth - 1);
00505
00506 while (n >= 8) {
00507 if (putc('\t', statefp) == EOF) checkstate();
00508 n-= 8;
00509 }
00510 while (n > 0) {
00511 if (putc(' ', statefp) == EOF) checkstate();
00512 n--;
00513 }
00514 }
00515
00516 int print_name(FILE *fp, const char *name)
00517
00518 {
00519 const char *p;
00520 int c;
00521
00522 for (p= name; (c= (unsigned char) *p) != 0; p++) {
00523 if (c <= ' ' || c == '\\') {
00524 fprintf(fp, "\\%03o", c);
00525 if (ferror(fp)) return 0;
00526 } else {
00527 if (putc(c, fp) == EOF) return 0;
00528 }
00529 }
00530 return 1;
00531 }
00532
00533 void mkstatefile(void)
00534
00535 {
00536 entry_t *entry;
00537
00538 while ((entry= traverse()) != nil) {
00539 indent(entry->depth);
00540 if (!print_name(statefp, entry->name)) checkstate();
00541
00542 if (entry->ignore) {
00543 fprintf(statefp, "\tignore (%s)\n",
00544 strerror(entry->ignore));
00545 checkstate();
00546 continue;
00547 }
00548
00549 switch (entry->type) {
00550 case F_DIR:
00551 fprintf(statefp, "\td%03o %u %u",
00552 (unsigned) entry->mode,
00553 (unsigned) entry->uid, (unsigned) entry->gid);
00554 break;
00555 case F_FILE:
00556 fprintf(statefp, "\t%03o %u %u %lu %lu",
00557 (unsigned) entry->mode,
00558 (unsigned) entry->uid, (unsigned) entry->gid,
00559 (unsigned long) entry->size,
00560 (unsigned long) entry->mtime);
00561 break;
00562 case F_BLK:
00563 case F_CHR:
00564 fprintf(statefp, "\t%c%03o %u %u %x",
00565 entry->type == F_BLK ? 'b' : 'c',
00566 (unsigned) entry->mode,
00567 (unsigned) entry->uid, (unsigned) entry->gid,
00568 (unsigned) entry->rdev);
00569 break;
00570 case F_PIPE:
00571 fprintf(statefp, "\tp%03o %u %u",
00572 (unsigned) entry->mode,
00573 (unsigned) entry->uid, (unsigned) entry->gid);
00574 break;
00575 case F_LINK:
00576 fprintf(statefp, "\t-> ");
00577 checkstate();
00578 (void) print_name(statefp, entry->link);
00579 break;
00580 }
00581 checkstate();
00582 if (entry->fake_ino != 0)
00583 fprintf(statefp, " %lu", entry->fake_ino);
00584 if (entry->lastlink)
00585 fprintf(statefp, " last");
00586 if (fputc('\n', statefp) == EOF) checkstate();
00587 }
00588 fflush(statefp);
00589 checkstate();
00590 }
00591
00592 char *read1line(FILE *fp)
00593
00594 {
00595 static char *line;
00596 static size_t len;
00597 size_t idx;
00598 int c;
00599
00600 if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0]));
00601
00602 idx= 0;
00603 while ((c= getc(fp)) != EOF && c != '\n') {
00604 if (c < '\t') {
00605
00606 fprintf(stderr,
00607 "remsync: control character in data file!\n");
00608 exit(1);
00609 }
00610 line[idx++]= c;
00611 if (idx == len) {
00612 line= allocate(line, (len*= 2) * sizeof(line[0]));
00613 }
00614 }
00615 if (c == EOF) {
00616 if (ferror(fp)) return nil;
00617 if (idx == 0) return nil;
00618 }
00619 line[idx]= 0;
00620 return line;
00621 }
00622
00623 void getword(char **pline, char **parg, size_t *plen)
00624
00625 {
00626 char *line= *pline;
00627 char *arg= *parg;
00628 size_t len= *plen;
00629 int i;
00630 int c;
00631 size_t idx;
00632
00633 idx= 0;
00634 while ((c= *line) != 0 && c != ' ' && c != '\t') {
00635 line++;
00636 if (c == '\\') {
00637 c= 0;
00638 for (i= 0; i < 3; i++) {
00639 if ((unsigned) (*line - '0') >= 010) break;
00640 c= (c << 3) | (*line - '0');
00641 line++;
00642 }
00643 }
00644 arg[idx++]= c;
00645 if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0]));
00646 }
00647 arg[idx]= 0;
00648 *pline= line;
00649 *parg= arg;
00650 *plen= len;
00651 }
00652
00653 void splitline(char *line, char ***pargv, size_t *pargc)
00654
00655 {
00656 static char **argv;
00657 static size_t *lenv;
00658 static size_t len;
00659 size_t idx;
00660
00661 idx= 0;
00662 for (;;) {
00663 while (*line == ' ' || *line == '\t') line++;
00664
00665 if (*line == 0) break;
00666
00667 if (idx == len) {
00668 len++;
00669 argv= allocate(argv, len * sizeof(argv[0]));
00670 lenv= allocate(lenv, len * sizeof(lenv[0]));
00671 argv[idx]= allocate(nil, 16 * sizeof(argv[idx][0]));
00672 lenv[idx]= 16;
00673 }
00674 getword(&line, &argv[idx], &lenv[idx]);
00675 idx++;
00676 }
00677 *pargv= argv;
00678 *pargc= idx;
00679 }
00680
00681 int getattributes(entry_t *entry, int argc, char **argv)
00682
00683 {
00684 int i;
00685 int attr;
00686 #define A_MODE1 0x01
00687 #define A_MODE 0x02
00688 #define A_OWNER 0x04
00689 #define A_SIZETIME 0x08
00690 #define A_DEV 0x10
00691 #define A_LINK 0x20
00692
00693 switch (argv[0][0]) {
00694 case 'd':
00695
00696 entry->type= F_DIR;
00697 attr= A_MODE1 | A_OWNER;
00698 break;
00699 case 'b':
00700
00701 entry->type= F_BLK;
00702 attr= A_MODE1 | A_OWNER | A_DEV;
00703 break;
00704 case 'c':
00705
00706 entry->type= F_CHR;
00707 attr= A_MODE1 | A_OWNER | A_DEV;
00708 break;
00709 case 'p':
00710
00711 entry->type= F_PIPE;
00712 attr= A_MODE1 | A_OWNER;
00713 break;
00714 case '-':
00715
00716 entry->type= F_LINK;
00717 attr= A_LINK;
00718 break;
00719 default:
00720
00721 entry->type= F_FILE;
00722 attr= A_MODE | A_OWNER | A_SIZETIME;
00723 }
00724
00725 if (attr & (A_MODE | A_MODE1)) {
00726 entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010);
00727 }
00728 i= 1;
00729 if (attr & A_OWNER) {
00730 if (i + 2 > argc) return 0;
00731 entry->uid= strtoul(argv[i++], nil, 10);
00732 entry->gid= strtoul(argv[i++], nil, 10);
00733 }
00734 if (attr & A_SIZETIME) {
00735 if (i + 2 > argc) return 0;
00736 entry->size= strtoul(argv[i++], nil, 10);
00737 entry->mtime= strtoul(argv[i++], nil, 10);
00738 }
00739 if (attr & A_DEV) {
00740 if (i + 1 > argc) return 0;
00741 entry->rdev= strtoul(argv[i++], nil, 0x10);
00742 }
00743 if (attr & A_LINK) {
00744 if (i + 1 > argc) return 0;
00745 entry->link= argv[i++];
00746 }
00747 entry->linked= entry->lastlink= 0;
00748 if (i < argc) {
00749
00750 static struct links {
00751 struct links *next;
00752 unsigned long fake_ino;
00753 char *path;
00754 } *links[1024];
00755 struct links **plp, *lp;
00756 unsigned long fake_ino;
00757
00758 fake_ino= strtoul(argv[i++], nil, 10);
00759
00760 plp= &links[fake_ino % arraysize(links)];
00761 while ((lp= *plp) != nil && lp->fake_ino != fake_ino)
00762 plp= &lp->next;
00763
00764 if (lp == nil) {
00765
00766 *plp= lp= allocate(nil, sizeof(*lp));
00767 lp->next= nil;
00768 lp->fake_ino= fake_ino;
00769 lp->path= copystr(entry->path);
00770 } else {
00771
00772 entry->link= lp->path;
00773 entry->linked= 1;
00774 }
00775
00776 if (i < argc) {
00777 if (strcmp(argv[i++], "last") != 0) return 0;
00778
00779
00780 forget(lp->path);
00781 *plp= lp->next;
00782 deallocate(lp);
00783 entry->lastlink= 1;
00784 }
00785 }
00786 if (i != argc) return 0;
00787 return 1;
00788 }
00789
00790 void state_syntax(off_t line)
00791 {
00792 fprintf(stderr, "remsync: %s: syntax error on line %lu\n",
00793 state_file, (unsigned long) line);
00794 exit(1);
00795 }
00796
00797 entry_t *readstate(void)
00798
00799 {
00800 static entry_t entry;
00801 static pathname_t path;
00802 static size_t *trunc;
00803 static size_t trunc_len;
00804 static base_indent;
00805 char *line;
00806 char **argv;
00807 size_t argc;
00808 static off_t lineno;
00809 int indent, depth;
00810
00811 recurse:
00812 keep= KEEP_STATE;
00813
00814 if (feof(statefp) || (line= read1line(statefp)) == nil) {
00815 checkstate();
00816 return nil;
00817 }
00818 lineno++;
00819
00820
00821 indent= 0;
00822 while (*line != 0) {
00823 if (*line == ' ') indent++;
00824 else
00825 if (*line == '\t') indent= (indent + 8) & ~7;
00826 else
00827 break;
00828 line++;
00829 }
00830 if (indent > 0 && base_indent == 0) base_indent= indent;
00831 depth= (base_indent == 0 ? 0 : indent / base_indent) + 1;
00832
00833 if (entry.ignore && depth > entry.depth) {
00834
00835 goto recurse;
00836 }
00837 entry.depth= depth;
00838
00839 splitline(line, &argv, &argc);
00840 if (argc < 2) state_syntax(lineno);
00841
00842 if (trunc == nil) {
00843
00844 if (argv[0][0] != '/') state_syntax(lineno);
00845 path_init(&path);
00846 path_add(&path, "/");
00847 trunc= allocate(nil, (trunc_len= 16) * sizeof(trunc[0]));
00848
00849
00850 entry.depth= 0;
00851 trunc[0]= 0;
00852 } else {
00853 if (entry.depth > trunc_len) {
00854 trunc= allocate(trunc,
00855 (trunc_len*= 2) * sizeof(trunc[0]));
00856 }
00857 path_trunc(&path, trunc[entry.depth - 1]);
00858 path_add(&path, argv[0]);
00859 trunc[entry.depth]= path_length(&path);
00860 }
00861
00862 entry.path= path_name(&path);
00863 entry.name= argv[0];
00864 entry.link= nil;
00865 if ((entry.ignore= strcmp(argv[1], "ignore") == 0)) {
00866 return &entry;
00867 }
00868 if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno);
00869 return &entry;
00870 }
00871
00872 void checkdiff(void)
00873 {
00874 if (ferror(difffp)) fatal(diff_file);
00875 }
00876
00877 enum { DELETE, REPLACE, COPY, SIMILAR, EQUAL, ADD }
00878 compare(entry_t *remote, entry_t *local)
00879
00880 {
00881 int cmp;
00882
00883
00884 if (local == nil) return DELETE;
00885 if (remote == nil) return ADD;
00886
00887
00888 if (remote->depth > local->depth) return DELETE;
00889 if (local->depth > remote->depth) return ADD;
00890
00891
00892 cmp= strcmp(remote->name, local->name);
00893 if (cmp < 0) return DELETE;
00894 if (cmp > 0) return ADD;
00895
00896
00897 if (remote->ignore || local->ignore) {
00898 remote->ignore= local->ignore= 1;
00899 return EQUAL;
00900 }
00901
00902
00903 if (remote->type != local->type) return REPLACE;
00904
00905
00906 if (remote->linked || local->linked) {
00907 if (!remote->linked || !local->linked) return REPLACE;
00908 if (strcmp(remote->link, local->link) != 0) return REPLACE;
00909 }
00910
00911 switch (remote->type) {
00912 case F_FILE:
00913 if (uflag) {
00914 if (remote->mtime < local->mtime) return COPY;
00915 } else {
00916 if (remote->size != local->size
00917 || remote->mtime != local->mtime)
00918 return COPY;
00919 }
00920 goto check_modes;
00921 case F_BLK:
00922 case F_CHR:
00923 if (remote->rdev != local->rdev) return REPLACE;
00924 goto check_modes;
00925 case F_DIR:
00926 case F_PIPE:
00927 check_modes:
00928 if (remote->mode != local->mode
00929 || remote->uid != local->uid
00930 || remote->gid != local->gid) return SIMILAR;
00931 break;
00932 case F_LINK:
00933 if (strcmp(remote->link, local->link) != 0) return REPLACE;
00934 break;
00935 }
00936 return EQUAL;
00937 }
00938
00939 void delete(entry_t *old)
00940
00941 {
00942 if (old->ignore) return;
00943 if (uflag) return;
00944
00945 fprintf(difffp, "rm ");
00946 checkdiff();
00947 if (!print_name(difffp, old->path)) checkdiff();
00948 if (putc('\n', difffp) == EOF) checkdiff();
00949 if (vflag) fprintf(stderr, "rm %s\n", old->path);
00950 }
00951
00952 void change_modes(entry_t *old, entry_t *new)
00953
00954 {
00955 if (new->ignore) return;
00956
00957 fprintf(difffp, "chmod ");
00958 checkdiff();
00959 if (!print_name(difffp, new->path)) checkdiff();
00960 fprintf(difffp, " %03o %u %u\n",
00961 (unsigned) new->mode,
00962 (unsigned) new->uid, (unsigned) new->gid);
00963 checkdiff();
00964 if (vflag && old->mode != new->mode) {
00965 fprintf(stderr, "chmod %s %03o %u %u\n",
00966 new->path,
00967 (unsigned) new->mode,
00968 (unsigned) new->uid, (unsigned) new->gid);
00969 }
00970 }
00971
00972 int cat(int f, off_t size)
00973
00974 {
00975 ssize_t n;
00976 unsigned char buf[1024 << sizeof(int)];
00977 unsigned char *p;
00978 int c;
00979
00980 if (Dflag) return 1;
00981
00982 while ((n= read(f, buf, sizeof(buf))) > 0) {
00983 p= buf;
00984 do {
00985 if (size == 0) {
00986
00987 errno= EINVAL;
00988 return 0;
00989 }
00990 c= *p++;
00991 if (putc(c, difffp) == EOF) checkdiff();
00992 size--;
00993 } while (--n != 0);
00994 }
00995 if (size > 0) {
00996 int err= errno;
00997
00998
00999 do {
01000 if (putc(0, difffp) == EOF) checkdiff();
01001 } while (--size != 0);
01002 errno= n == 0 ? EINVAL : err;
01003 n= -1;
01004 }
01005 return n == 0;
01006 }
01007
01008 void add(entry_t *old, entry_t *new)
01009
01010 {
01011 pathname_t file;
01012 int f;
01013
01014 if (new->ignore) return;
01015
01016 if (new->linked) {
01017
01018 fprintf(difffp, "ln ");
01019 checkdiff();
01020 if (!print_name(difffp, new->link)) checkdiff();
01021 if (fputc(' ', difffp) == EOF) checkdiff();
01022 if (!print_name(difffp, new->path)) checkdiff();
01023 if (fputc('\n', difffp) == EOF) checkdiff();
01024 if (vflag) {
01025 fprintf(stderr, "ln %s %s\n", new->link, new->path);
01026 }
01027 return;
01028 }
01029
01030
01031 fprintf(difffp, "add ");
01032 checkdiff();
01033 if (!print_name(difffp, new->path)) checkdiff();
01034
01035 switch (new->type) {
01036 case F_DIR:
01037 fprintf(difffp, " d%03o %u %u\n",
01038 (unsigned) new->mode,
01039 (unsigned) new->uid, (unsigned) new->gid);
01040 if (vflag) fprintf(stderr, "mkdir %s\n", new->path);
01041 break;
01042 case F_FILE:
01043 path_init(&file);
01044 path_add(&file, tree);
01045 path_add(&file, new->path);
01046 if ((f= open(path_name(&file), O_RDONLY)) < 0) {
01047 report(path_name(&file));
01048 path_drop(&file);
01049 fprintf(difffp, " ignore\n");
01050 break;
01051 }
01052 fprintf(difffp, " %03o %u %u %lu %lu\n",
01053 (unsigned) new->mode,
01054 (unsigned) new->uid, (unsigned) new->gid,
01055 (unsigned long) new->size,
01056 (unsigned long) new->mtime);
01057 checkdiff();
01058 if (!cat(f, new->size)) {
01059 int err= errno;
01060 report(path_name(&file));
01061 fprintf(difffp, "old ");
01062 checkdiff();
01063 print_name(difffp, err == EINVAL
01064 ? "File changed when copied" : strerror(err));
01065 fputc('\n', difffp);
01066 checkdiff();
01067 } else {
01068 if (vflag) {
01069 fprintf(stderr, "%s %s\n",
01070 old == nil ? "add" :
01071 old->mtime > new->mtime ?
01072 "restore" : "update",
01073 new->path);
01074 }
01075 }
01076 close(f);
01077 path_drop(&file);
01078 break;
01079 case F_BLK:
01080 case F_CHR:
01081 fprintf(difffp, " %c%03o %u %u %lx\n",
01082 new->type == F_BLK ? 'b' : 'c',
01083 (unsigned) new->mode,
01084 (unsigned) new->uid, (unsigned) new->gid,
01085 (unsigned long) new->rdev);
01086 if (vflag) fprintf(stderr, "mknod %s\n", new->path);
01087 break;
01088 case F_PIPE:
01089 fprintf(difffp, " p%03o %u %u\n",
01090 (unsigned) new->mode,
01091 (unsigned) new->uid, (unsigned) new->gid);
01092 if (vflag) fprintf(stderr, "mkfifo %s\n", new->path);
01093 break;
01094 case F_LINK:
01095 fprintf(difffp, " -> ");
01096 checkdiff();
01097 (void) print_name(difffp, new->link);
01098 checkdiff();
01099 fputc('\n', difffp);
01100 if (vflag) {
01101 fprintf(stderr, "ln -s %s %s\n", new->link, new->path);
01102 }
01103 break;
01104 }
01105 checkdiff();
01106 }
01107
01108 void mkdifferences(void)
01109 {
01110 entry_t *remote;
01111 entry_t *local;
01112
01113 remote= readstate();
01114 local= traverse();
01115
01116 while (remote != nil || local != nil) {
01117 switch (compare(remote, local)) {
01118 case DELETE:
01119
01120 delete(remote);
01121 remote->ignore= 1;
01122 remote= readstate();
01123 break;
01124 case REPLACE:
01125
01126 if (remote->type == F_FILE && local->type == F_FILE
01127 && !local->linked) {
01128
01129 delete(remote);
01130 }
01131
01132 case COPY:
01133
01134 add(remote, local);
01135 remote->ignore= 1;
01136 goto skip2;
01137 case SIMILAR:
01138
01139 change_modes(remote, local);
01140 goto skip2;
01141 case EQUAL:
01142 skip2:
01143
01144 remote= readstate();
01145 local= traverse();
01146 break;
01147 case ADD:
01148
01149 add(nil, local);
01150 local= traverse();
01151 break;
01152 }
01153 }
01154 fprintf(difffp, "end\n");
01155 fflush(difffp);
01156 checkdiff();
01157 }
01158
01159 void apply_remove(pathname_t *pp)
01160
01161 {
01162 struct stat st;
01163
01164 if (lstat(path_name(pp), &st) < 0) {
01165 if (errno != ENOENT) report(path_name(pp));
01166 return;
01167 }
01168
01169 if (S_ISDIR(st.st_mode)) {
01170
01171 size_t len;
01172 namelist_t *entries;
01173
01174 if ((entries= collect(path_name(pp))) == nil && errno != 0) {
01175 report(path_name(pp));
01176 return;
01177 }
01178 len= path_length(pp);
01179
01180 while (entries != nil) {
01181 path_add(pp, pop_name(&entries));
01182 apply_remove(pp);
01183 path_trunc(pp, len);
01184 }
01185 if (rmdir(path_name(pp)) < 0) {
01186 report(path_name(pp));
01187 return;
01188 }
01189 if (vflag) fprintf(stderr, "rmdir %s\n", path_name(pp));
01190 } else {
01191
01192 if (unlink(path_name(pp)) < 0) {
01193 report(path_name(pp));
01194 return;
01195 }
01196 if (vflag) fprintf(stderr, "rm %s\n", path_name(pp));
01197 }
01198 }
01199
01200 void apply_mkold(const char *file, const char *err)
01201
01202 {
01203 struct utimbuf utb;
01204
01205 utb.actime= utb.modtime= 0;
01206 if (utime(file, &utb) < 0) {
01207 report(file);
01208 return;
01209 }
01210 fprintf(stderr, "made %s look old", file);
01211 fprintf(stderr, err == nil ? "\n" : " due to a remote problem: %s\n",
01212 err);
01213 }
01214
01215 void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk)
01216
01217 {
01218 struct stat st;
01219
01220 if (lstat(file, &st) < 0) {
01221 report(file);
01222 return;
01223 }
01224 if ((st.st_mode & 07777) != mode) {
01225 if (chmod(file, mode) < 0) {
01226 report(file);
01227 return;
01228 }
01229 if (vflag && talk) {
01230 fprintf(stderr, "chmod %03o %s\n",
01231 (unsigned) mode, file);
01232 }
01233 }
01234 if (st.st_uid != uid || st.st_gid != gid) {
01235 if (chown(file, uid, gid) < 0) {
01236 if (errno != EPERM) report(file);
01237 return;
01238 }
01239 if (vflag && talk) {
01240 fprintf(stderr, "chown %u:%u %s\n",
01241 (unsigned) uid, (unsigned) gid, file);
01242 }
01243 }
01244 }
01245
01246 void apply_add(pathname_t *pp, entry_t *entry)
01247
01248 {
01249 const char *file;
01250 off_t size;
01251 int f;
01252 unsigned char buf[1024 << sizeof(int)];
01253 unsigned char *p;
01254 int c;
01255 int dirty;
01256 struct stat st;
01257 struct utimbuf utb;
01258
01259 if (entry->ignore) return;
01260
01261 if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE
01262 || !S_ISREG(st.st_mode))) {
01263 apply_remove(pp);
01264 }
01265
01266 file= path_name(pp);
01267
01268 switch (entry->type) {
01269 case F_DIR:
01270 if (mkdir(file, entry->mode) < 0) {
01271 report(file);
01272 return;
01273 }
01274 if (vflag) fprintf(stderr, "mkdir %s\n", file);
01275 break;
01276 case F_FILE:
01277 size= entry->size;
01278
01279 f= -1;
01280 st.st_mode= 0;
01281 if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) {
01282 f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
01283 entry->mode);
01284 if (f < 0) {
01285 (void) chmod(file, entry->mode | 0200);
01286 f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
01287 entry->mode);
01288 }
01289 if (f < 0) {
01290 (void) unlink(file);
01291 f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
01292 entry->mode);
01293 }
01294 if (f < 0) report(file);
01295 }
01296 dirty= (f >= 0);
01297 p= buf;
01298 while (size > 0 && (c= getc(difffp)) != EOF) {
01299 size--;
01300 *p++= c;
01301 if (p == arraylimit(buf) || size == 0) {
01302 if (f >= 0 && write(f, buf, p - buf) < 0) {
01303 report(file);
01304 close(f);
01305 f= -1;
01306 }
01307 p= buf;
01308 }
01309 }
01310 if (size > 0) {
01311 if (ferror(difffp)) report(diff_file);
01312 if (feof(difffp)) {
01313 fprintf(stderr, "remspec: %s: premature EOF\n",
01314 diff_file);
01315 }
01316 if (dirty) apply_mkold(file, nil);
01317 exit(1);
01318 }
01319 if (f < 0) {
01320 if (dirty) apply_mkold(file, nil);
01321 return;
01322 }
01323 close(f);
01324 if (vflag) {
01325 fprintf(stderr, st.st_mode == 0 ? "add %s\n"
01326 : entry->mtime >= st.st_mtime
01327 ? "update %s\n" : "restore %s\n", file);
01328 }
01329 utb.actime= time(nil);
01330 utb.modtime= entry->mtime;
01331 if (utime(file, &utb) < 0) report(file);
01332 break;
01333 case F_BLK:
01334 if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) {
01335 report(file);
01336 return;
01337 }
01338 if (vflag) {
01339 fprintf(stderr, "mknod %s b %d %d\n", file,
01340 major(entry->rdev), minor(entry->rdev));
01341 }
01342 break;
01343 case F_CHR:
01344 if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) {
01345 report(file);
01346 return;
01347 }
01348 if (vflag) {
01349 fprintf(stderr, "mknod %s c %d %d\n", file,
01350 major(entry->rdev), minor(entry->rdev));
01351 }
01352 break;
01353 case F_PIPE:
01354 if (mknod(file, S_IFIFO | entry->mode, 0) < 0) {
01355 report(file);
01356 return;
01357 }
01358 if (vflag) fprintf(stderr, "mknod %s p\n", file);
01359 break;
01360 case F_LINK:
01361 if (symlink(entry->link, file) < 0) {
01362 report(file);
01363 return;
01364 }
01365 if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file);
01366 return;
01367 }
01368 apply_chmod(file, entry->mode, entry->uid, entry->gid, 0);
01369 }
01370
01371 void apply_link(const char *file, pathname_t *pp)
01372
01373 {
01374 struct stat st1, st2;
01375
01376 if (lstat(file, &st1) < 0) {
01377 report(file);
01378 return;
01379 }
01380 if (lstat(path_name(pp), &st2) >= 0) {
01381 if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev)
01382 return;
01383 apply_remove(pp);
01384 if (lstat(path_name(pp), &st2) >= 0) return;
01385 }
01386 if (link(file, path_name(pp)) < 0) {
01387 fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp),
01388 strerror(errno));
01389 excode= 1;
01390 return;
01391 }
01392 if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp));
01393 }
01394
01395 void diff_syntax(const char *line)
01396 {
01397 fprintf(stderr, "remsync: %s: syntax error on this line: %s\n",
01398 diff_file, line);
01399 exit(1);
01400 }
01401
01402 void apply_differences(void)
01403
01404 {
01405 char *line;
01406 char **argv;
01407 size_t argc;
01408 pathname_t path, link;
01409 size_t trunc;
01410
01411 path_init(&path);
01412 path_init(&link);
01413 path_add(&path, tree);
01414 path_add(&link, tree);
01415 trunc= path_length(&path);
01416
01417 while (!feof(difffp) && (line= read1line(difffp)) != nil) {
01418 splitline(line, &argv, &argc);
01419 if (argc == 0) diff_syntax(line);
01420
01421 path_trunc(&path, trunc);
01422
01423 if (strcmp(argv[0], "add") == 0) {
01424 entry_t entry;
01425
01426 if (argc < 3) diff_syntax(line);
01427 path_add(&path, argv[1]);
01428 entry.ignore= (strcmp(argv[2], "ignore") == 0);
01429 if (!entry.ignore && !getattributes(&entry,
01430 argc - 2, argv + 2))
01431 diff_syntax(line);
01432 apply_add(&path, &entry);
01433 } else
01434 if (strcmp(argv[0], "rm") == 0) {
01435 if (argc != 2) diff_syntax(line);
01436 path_add(&path, argv[1]);
01437 apply_remove(&path);
01438 } else
01439 if (strcmp(argv[0], "ln") == 0) {
01440 if (argc != 3) diff_syntax(line);
01441 path_trunc(&link, trunc);
01442 path_add(&link, argv[1]);
01443 path_add(&path, argv[2]);
01444 apply_link(path_name(&link), &path);
01445 } else
01446 if (strcmp(argv[0], "chmod") == 0) {
01447 if (argc != 5) diff_syntax(line);
01448 path_add(&path, argv[1]);
01449 apply_chmod(path_name(&path),
01450 strtoul(argv[2], nil, 010),
01451 strtoul(argv[3], nil, 10),
01452 strtoul(argv[4], nil, 10),
01453 1);
01454 } else
01455 if (strcmp(argv[0], "old") == 0) {
01456 if (argc != 3) diff_syntax(line);
01457 path_add(&path, argv[1]);
01458 apply_mkold(path_name(&path), argv[2]);
01459 } else
01460 if (strcmp(argv[0], "end") == 0) {
01461 if (argc != 1) diff_syntax(line);
01462 break;
01463 } else {
01464 diff_syntax(line);
01465 }
01466 }
01467 checkdiff();
01468 }
01469
01470 void usage(void)
01471 {
01472 fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n");
01473 fprintf(stderr, " remsync -duxvD tree [state-file [diff-file]]\n");
01474 fprintf(stderr, " remsync [-xv] tree [diff-file]\n");
01475 exit(1);
01476 }
01477
01478 int main(int argc, char **argv)
01479 {
01480 int i;
01481
01482 for (i= 1; i < argc && argv[i][0] == '-'; i++) {
01483 char *p= argv[i] + 1;
01484
01485 if (p[0] == '-' && p[1] == 0) { i++; break; }
01486
01487 while (*p != 0) {
01488 switch (*p++) {
01489 case 's': sflag= 1; break;
01490 case 'd': dflag= 1; break;
01491 case 'u': uflag= 1; break;
01492 case 'x': xflag= 1; break;
01493 case 'D': Dflag= 1; break;
01494 case 'v': vflag= 1; break;
01495 default: usage();
01496 }
01497 }
01498 }
01499 if (sflag && dflag) usage();
01500 if (sflag && uflag) usage();
01501 if (!sflag && !dflag && uflag) usage();
01502 if (!dflag && Dflag) usage();
01503
01504 if (i == argc) usage();
01505 tree= argv[i++];
01506
01507 if (sflag) {
01508
01509 state_file= i < argc ? argv[i++] : "-";
01510 if (i != argc) usage();
01511
01512 statefp= stdout;
01513 if (strcmp(state_file, "-") != 0) {
01514 if ((statefp= fopen(state_file, "w")) == nil)
01515 fatal(state_file);
01516 }
01517 mkstatefile();
01518 } else
01519 if (dflag) {
01520
01521 state_file= i < argc ? argv[i++] : "-";
01522
01523 diff_file= i < argc ? argv[i++] : "-";
01524 if (i != argc) usage();
01525
01526 statefp= stdin;
01527 if (strcmp(state_file, "-") != 0) {
01528 if ((statefp= fopen(state_file, "r")) == nil)
01529 fatal(state_file);
01530 }
01531
01532 difffp= stdout;
01533 if (strcmp(diff_file, "-") != 0) {
01534 if ((difffp= fopen(diff_file, "w")) == nil)
01535 fatal(diff_file);
01536 }
01537 mkdifferences();
01538 } else {
01539
01540 diff_file= i < argc ? argv[i++] : "-";
01541 if (i != argc) usage();
01542
01543 difffp= stdin;
01544 if (strcmp(diff_file, "-") != 0) {
01545 if ((difffp= fopen(diff_file, "r")) == nil)
01546 fatal(diff_file);
01547 }
01548 apply_differences();
01549 }
01550 exit(excode);
01551 }