iso9660fs: initialize buffer cache
[minix.git] / commands / remsync / remsync.c
blob5dabada9a8510d28d2ebc5091739ff1b08ff814a
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>
19 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
20 #define arraylimit(a) ((a) + arraysize(a))
22 #ifndef major
23 #define major(dev) ((int) ((dev) >> 8))
24 #define minor(dev) ((int) ((dev) & 0xFF))
25 #endif
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
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. */
42 #define NO_DEVICE (-1)
43 dev_t xdev= NO_DEVICE; /* The device that you should stay within. */
45 int excode= 0; /* Exit(excode); */
47 #define BASE_INDENT 2 /* State file basic indent. */
49 void report(const char *label)
51 fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno));
52 excode= 1;
55 void fatal(const char *label)
57 report(label);
58 exit(1);
61 void *allocate(void *mem, size_t size)
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);
68 return mem;
71 void deallocate(void *mem)
73 if (mem != nil) free(mem);
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.
79 int keep;
80 #define KEEP_STATE 0
81 #define KEEP_TRAVERSE 1
83 void forget(void *mem)
84 /* Some objects must be deleted in time, but not just yet. */
86 static void *death_row[2][50];
87 static void **dp[2]= { death_row[0], death_row[1] };
89 deallocate(*dp[keep]);
90 *dp[keep]++= mem;
91 if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep];
94 char *copystr(const char *s)
96 char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0]));
97 strcpy(c, s);
98 return c;
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;
107 void path_init(pathname_t *pp)
108 /* Initialize a pathname to the null string. */
110 pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0]));
111 pp->path[pp->idx= 0]= 0;
114 void path_add(pathname_t *pp, const char *name)
115 /* Add a component to a pathname. */
117 size_t lim;
118 char *p;
119 int slash;
121 lim= pp->idx + strlen(name) + 2;
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]));
128 p= pp->path + pp->idx;
129 slash= (pp->idx > 0);
130 if (pp->idx == 1 && p[-1] == '/') p--;
132 while (*name != 0) {
133 if (*name == '/') {
134 slash= 1;
135 } else {
136 if (slash) { *p++ = '/'; slash= 0; }
137 *p++= *name;
139 name++;
141 if (slash && p == pp->path) *p++= '/';
142 *p = 0;
143 pp->idx= p - pp->path;
146 void path_trunc(pathname_t *pp, size_t didx)
147 /* Delete part of a pathname to a remembered length. */
149 pp->path[pp->idx= didx]= 0;
152 #if kept_for_comments_only
154 const char *path_name(const pathname_t *pp)
155 /* Return the actual name as a char array. */
157 return pp->path;
160 size_t path_length(const pathname_t *pp)
161 /* The length of the pathname. */
163 return pp->idx;
166 void path_drop(pathname_t *pp)
167 /* Release the storage occupied by the pathname. */
169 free(pp->path);
171 #endif
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)
177 typedef struct namelist { /* Obviously a list of names. */
178 struct namelist *next;
179 char *name;
180 } namelist_t;
182 char *rdlink(const char *link, off_t size)
183 /* Look where "link" points. */
185 static char *path= nil;
186 static size_t len= 0;
187 size_t n;
189 if (len <= size) {
190 path= allocate(path, (len= size * 2) * sizeof(path[0]));
192 if ((n= readlink(link, path, len)) == -1) return nil;
193 path[n]= 0;
194 return path;
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);
202 /* static */ namelist_t *nl1, **mid; /* Need not be local */
203 namelist_t *nl2;
205 nl1= *(mid= &(*anl)->next);
206 do {
207 if ((nl1= nl1->next) == nil) break;
208 mid= &(*mid)->next;
209 } while ((nl1= nl1->next) != nil);
211 nl2= *mid;
212 *mid= nil;
214 if ((*anl)->next != nil) sort(anl);
215 if (nl2->next != nil) sort(&nl2);
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;
224 } else {
225 *anl= nl2;
226 nl2= *(anl= &nl2->next);
227 *anl= nl1;
228 if (nl2 == nil) break;
233 namelist_t *collect(const char *dir)
234 /* Return a sorted list of directory entries. Returns null with errno != 0
235 * on error.
238 namelist_t *names, **pn= &names;
239 DIR *dp;
240 struct dirent *entry;
242 if ((dp= opendir(dir)) == nil) return nil;
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;
251 *pn= allocate(nil, sizeof(**pn));
252 (*pn)->name= copystr(entry->d_name);
253 pn= &(*pn)->next;
255 closedir(dp);
256 *pn= nil;
257 errno= 0;
258 if (names != nil && names->next != nil) sort(&names);
259 return names;
262 char *pop_name(namelist_t **names)
263 /* Return one name of a name list. */
265 char *name;
266 namelist_t *junk;
268 junk= *names;
269 *names= junk->next;
270 name= junk->name;
271 deallocate(junk);
272 forget(name);
273 return name;
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;
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;
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.
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;
319 entry->linked= entry->lastlink= 0;
320 entry->fake_ino= 0;
321 entry->link= nil;
323 if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return;
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;
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;
342 entry->fake_ino= lp->fake_ino;
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;
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;
359 entry_t *traverse(void)
360 /* Get one name from the directory tree. */
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;
371 recurse:
372 keep= KEEP_TRAVERSE;
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;
393 if (++depth == deep) {
394 deep++;
395 entries= allocate(entries, deep * sizeof(entries[0]));
396 trunc= allocate(trunc, deep * sizeof(trunc[0]));
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. */
406 /* Go up one level. */
407 depth--;
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++;
419 entry.depth= depth;
420 entry.ignore= 0;
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.
427 entry.ignore= errno;
428 report(path_name(&path));
429 return &entry;
430 } else {
431 /* Entry strangely nonexistent; simply continue. */
432 goto recurse;
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;
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;
453 linked(&entry, &st);
455 if (S_ISDIR(st.st_mode)) {
456 /* A directory. */
457 entry.type= F_DIR;
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));
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));
489 } else {
490 /* Unknown type of file. */
491 entry.ignore= EINVAL;
493 return &entry;
496 void checkstate(void)
498 if (ferror(statefp)) fatal(state_file);
501 void indent(int depth)
502 /* Provide indentation to show directory depth. */
504 int n= BASE_INDENT * (depth - 1);
506 while (n >= 8) {
507 if (putc('\t', statefp) == EOF) checkstate();
508 n-= 8;
510 while (n > 0) {
511 if (putc(' ', statefp) == EOF) checkstate();
512 n--;
516 int print_name(FILE *fp, const char *name)
517 /* Encode a name. */
519 const char *p;
520 int c;
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;
530 return 1;
533 void mkstatefile(void)
534 /* Make a state file out of the directory tree. */
536 entry_t *entry;
538 while ((entry= traverse()) != nil) {
539 indent(entry->depth);
540 if (!print_name(statefp, entry->name)) checkstate();
542 if (entry->ignore) {
543 fprintf(statefp, "\tignore (%s)\n",
544 strerror(entry->ignore));
545 checkstate();
546 continue;
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;
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();
588 fflush(statefp);
589 checkstate();
592 char *read1line(FILE *fp)
593 /* Read one line from a file. Return null on EOF or error. */
595 static char *line;
596 static size_t len;
597 size_t idx;
598 int c;
600 if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0]));
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);
610 line[idx++]= c;
611 if (idx == len) {
612 line= allocate(line, (len*= 2) * sizeof(line[0]));
615 if (c == EOF) {
616 if (ferror(fp)) return nil;
617 if (idx == 0) return nil;
619 line[idx]= 0;
620 return line;
623 void getword(char **pline, char **parg, size_t *plen)
624 /* Get one word from a line, interpret octal escapes. */
626 char *line= *pline;
627 char *arg= *parg;
628 size_t len= *plen;
629 int i;
630 int c;
631 size_t idx;
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++;
644 arg[idx++]= c;
645 if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0]));
647 arg[idx]= 0;
648 *pline= line;
649 *parg= arg;
650 *plen= len;
653 void splitline(char *line, char ***pargv, size_t *pargc)
654 /* Split a line into an array of words. */
656 static char **argv;
657 static size_t *lenv;
658 static size_t len;
659 size_t idx;
661 idx= 0;
662 for (;;) {
663 while (*line == ' ' || *line == '\t') line++;
665 if (*line == 0) break;
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;
674 getword(&line, &argv[idx], &lenv[idx]);
675 idx++;
677 *pargv= argv;
678 *pargc= idx;
681 int getattributes(entry_t *entry, int argc, char **argv)
682 /* Convert state or difference file info into file attributes. */
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
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;
725 if (attr & (A_MODE | A_MODE1)) {
726 entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010);
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);
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);
739 if (attr & A_DEV) {
740 if (i + 1 > argc) return 0;
741 entry->rdev= strtoul(argv[i++], nil, 0x10);
743 if (attr & A_LINK) {
744 if (i + 1 > argc) return 0;
745 entry->link= argv[i++];
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;
758 fake_ino= strtoul(argv[i++], nil, 10);
760 plp= &links[fake_ino % arraysize(links)];
761 while ((lp= *plp) != nil && lp->fake_ino != fake_ino)
762 plp= &lp->next;
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;
776 if (i < argc) {
777 if (strcmp(argv[i++], "last") != 0) return 0;
779 /* Last hard link of a file. */
780 forget(lp->path);
781 *plp= lp->next;
782 deallocate(lp);
783 entry->lastlink= 1;
786 if (i != argc) return 0;
787 return 1;
790 void state_syntax(off_t line)
792 fprintf(stderr, "remsync: %s: syntax error on line %lu\n",
793 state_file, (unsigned long) line);
794 exit(1);
797 entry_t *readstate(void)
798 /* Read one entry from the state file. */
800 static entry_t entry;
801 static pathname_t path;
802 static size_t *trunc;
803 static size_t trunc_len;
804 static base_indent;
805 char *line;
806 char **argv;
807 size_t argc;
808 static off_t lineno;
809 int indent, depth;
811 recurse:
812 keep= KEEP_STATE;
814 if (feof(statefp) || (line= read1line(statefp)) == nil) {
815 checkstate();
816 return nil;
818 lineno++;
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++;
830 if (indent > 0 && base_indent == 0) base_indent= indent;
831 depth= (base_indent == 0 ? 0 : indent / base_indent) + 1;
833 if (entry.ignore && depth > entry.depth) {
834 /* If the old directory is ignored, then so are its entries. */
835 goto recurse;
837 entry.depth= depth;
839 splitline(line, &argv, &argc);
840 if (argc < 2) state_syntax(lineno);
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]));
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]));
857 path_trunc(&path, trunc[entry.depth - 1]);
858 path_add(&path, argv[0]);
859 trunc[entry.depth]= path_length(&path);
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;
868 if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno);
869 return &entry;
872 void checkdiff(void)
874 if (ferror(difffp)) fatal(diff_file);
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. */
881 int cmp;
883 /* Surplus entries? */
884 if (local == nil) return DELETE;
885 if (remote == nil) return ADD;
887 /* Extra directory entries? */
888 if (remote->depth > local->depth) return DELETE;
889 if (local->depth > remote->depth) return ADD;
891 /* Compare names. */
892 cmp= strcmp(remote->name, local->name);
893 if (cmp < 0) return DELETE;
894 if (cmp > 0) return ADD;
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;
902 /* Reasons for replacement? */
903 if (remote->type != local->type) return REPLACE;
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;
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;
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;
936 return EQUAL;
939 void delete(entry_t *old)
940 /* Emit an instruction to remove an entry. */
942 if (old->ignore) return;
943 if (uflag) return;
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);
952 void change_modes(entry_t *old, entry_t *new)
953 /* Emit an instruction to change the attributes of an entry. */
955 if (new->ignore) return;
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);
972 int cat(int f, off_t size)
973 /* Include the contents of a file in the differences file. */
975 ssize_t n;
976 unsigned char buf[1024 << sizeof(int)];
977 unsigned char *p;
978 int c;
980 if (Dflag) return 1; /* Debug: Don't need the file contents. */
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;
990 c= *p++;
991 if (putc(c, difffp) == EOF) checkdiff();
992 size--;
993 } while (--n != 0);
995 if (size > 0) {
996 int err= errno;
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;
1005 return n == 0;
1008 void add(entry_t *old, entry_t *new)
1009 /* Emit an instruction to add an entry. */
1011 pathname_t file;
1012 int f;
1014 if (new->ignore) return;
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);
1027 return;
1030 /* Add some other type of file. */
1031 fprintf(difffp, "add ");
1032 checkdiff();
1033 if (!print_name(difffp, new->path)) checkdiff();
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;
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);
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);
1103 break;
1105 checkdiff();
1108 void mkdifferences(void)
1110 entry_t *remote;
1111 entry_t *local;
1113 remote= readstate();
1114 local= traverse();
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);
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;
1154 fprintf(difffp, "end\n");
1155 fflush(difffp);
1156 checkdiff();
1159 void apply_remove(pathname_t *pp)
1160 /* Remove an obsolete file. */
1162 struct stat st;
1164 if (lstat(path_name(pp), &st) < 0) {
1165 if (errno != ENOENT) report(path_name(pp));
1166 return;
1169 if (S_ISDIR(st.st_mode)) {
1170 /* Recursively delete directories. */
1171 size_t len;
1172 namelist_t *entries;
1174 if ((entries= collect(path_name(pp))) == nil && errno != 0) {
1175 report(path_name(pp));
1176 return;
1178 len= path_length(pp);
1180 while (entries != nil) {
1181 path_add(pp, pop_name(&entries));
1182 apply_remove(pp);
1183 path_trunc(pp, len);
1185 if (rmdir(path_name(pp)) < 0) {
1186 report(path_name(pp));
1187 return;
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;
1196 if (vflag) fprintf(stderr, "rm %s\n", path_name(pp));
1200 void apply_mkold(const char *file, const char *err)
1201 /* Make a file very old. (An error occurred when it was added.) */
1203 struct utimbuf utb;
1205 utb.actime= utb.modtime= 0;
1206 if (utime(file, &utb) < 0) {
1207 report(file);
1208 return;
1210 fprintf(stderr, "made %s look old", file);
1211 fprintf(stderr, err == nil ? "\n" : " due to a remote problem: %s\n",
1212 err);
1215 void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk)
1216 /* Change mode and ownership. */
1218 struct stat st;
1220 if (lstat(file, &st) < 0) {
1221 report(file);
1222 return;
1224 if ((st.st_mode & 07777) != mode) {
1225 if (chmod(file, mode) < 0) {
1226 report(file);
1227 return;
1229 if (vflag && talk) {
1230 fprintf(stderr, "chmod %03o %s\n",
1231 (unsigned) mode, file);
1234 if (st.st_uid != uid || st.st_gid != gid) {
1235 if (chown(file, uid, gid) < 0) {
1236 if (errno != EPERM) report(file);
1237 return;
1239 if (vflag && talk) {
1240 fprintf(stderr, "chown %u:%u %s\n",
1241 (unsigned) uid, (unsigned) gid, file);
1246 void apply_add(pathname_t *pp, entry_t *entry)
1247 /* Add or replace a file. */
1249 const char *file;
1250 off_t size;
1251 int f;
1252 unsigned char buf[1024 << sizeof(int)];
1253 unsigned char *p;
1254 int c;
1255 int dirty;
1256 struct stat st;
1257 struct utimbuf utb;
1259 if (entry->ignore) return;
1261 if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE
1262 || !S_ISREG(st.st_mode))) {
1263 apply_remove(pp);
1266 file= path_name(pp);
1268 switch (entry->type) {
1269 case F_DIR:
1270 if (mkdir(file, entry->mode) < 0) {
1271 report(file);
1272 return;
1274 if (vflag) fprintf(stderr, "mkdir %s\n", file);
1275 break;
1276 case F_FILE:
1277 size= entry->size;
1279 f= -1;
1280 st.st_mode= 0;
1281 if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) {
1282 f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1283 entry->mode);
1284 if (f < 0) {
1285 (void) chmod(file, entry->mode | 0200);
1286 f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1287 entry->mode);
1289 if (f < 0) {
1290 (void) unlink(file);
1291 f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1292 entry->mode);
1294 if (f < 0) report(file);
1296 dirty= (f >= 0);
1297 p= buf;
1298 while (size > 0 && (c= getc(difffp)) != EOF) {
1299 size--;
1300 *p++= c;
1301 if (p == arraylimit(buf) || size == 0) {
1302 if (f >= 0 && write(f, buf, p - buf) < 0) {
1303 report(file);
1304 close(f);
1305 f= -1;
1307 p= buf;
1310 if (size > 0) {
1311 if (ferror(difffp)) report(diff_file);
1312 if (feof(difffp)) {
1313 fprintf(stderr, "remspec: %s: premature EOF\n",
1314 diff_file);
1316 if (dirty) apply_mkold(file, nil);
1317 exit(1);
1319 if (f < 0) {
1320 if (dirty) apply_mkold(file, nil);
1321 return;
1323 close(f);
1324 if (vflag) {
1325 fprintf(stderr, st.st_mode == 0 ? "add %s\n"
1326 : entry->mtime >= st.st_mtime
1327 ? "update %s\n" : "restore %s\n", file);
1329 utb.actime= time(nil);
1330 utb.modtime= entry->mtime;
1331 if (utime(file, &utb) < 0) report(file);
1332 break;
1333 case F_BLK:
1334 if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) {
1335 report(file);
1336 return;
1338 if (vflag) {
1339 fprintf(stderr, "mknod %s b %d %d\n", file,
1340 major(entry->rdev), minor(entry->rdev));
1342 break;
1343 case F_CHR:
1344 if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) {
1345 report(file);
1346 return;
1348 if (vflag) {
1349 fprintf(stderr, "mknod %s c %d %d\n", file,
1350 major(entry->rdev), minor(entry->rdev));
1352 break;
1353 case F_PIPE:
1354 if (mknod(file, S_IFIFO | entry->mode, 0) < 0) {
1355 report(file);
1356 return;
1358 if (vflag) fprintf(stderr, "mknod %s p\n", file);
1359 break;
1360 case F_LINK:
1361 if (symlink(entry->link, file) < 0) {
1362 report(file);
1363 return;
1365 if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file);
1366 return;
1368 apply_chmod(file, entry->mode, entry->uid, entry->gid, 0);
1371 void apply_link(const char *file, pathname_t *pp)
1372 /* Hard link *pp to file. */
1374 struct stat st1, st2;
1376 if (lstat(file, &st1) < 0) {
1377 report(file);
1378 return;
1380 if (lstat(path_name(pp), &st2) >= 0) {
1381 if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev)
1382 return;
1383 apply_remove(pp);
1384 if (lstat(path_name(pp), &st2) >= 0) return;
1386 if (link(file, path_name(pp)) < 0) {
1387 fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp),
1388 strerror(errno));
1389 excode= 1;
1390 return;
1392 if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp));
1395 void diff_syntax(const char *line)
1397 fprintf(stderr, "remsync: %s: syntax error on this line: %s\n",
1398 diff_file, line);
1399 exit(1);
1402 void apply_differences(void)
1403 /* Update a tree to a list of differences derived from a remote tree. */
1405 char *line;
1406 char **argv;
1407 size_t argc;
1408 pathname_t path, link;
1409 size_t trunc;
1411 path_init(&path);
1412 path_init(&link);
1413 path_add(&path, tree);
1414 path_add(&link, tree);
1415 trunc= path_length(&path);
1417 while (!feof(difffp) && (line= read1line(difffp)) != nil) {
1418 splitline(line, &argv, &argc);
1419 if (argc == 0) diff_syntax(line);
1421 path_trunc(&path, trunc);
1423 if (strcmp(argv[0], "add") == 0) {
1424 entry_t entry;
1426 if (argc < 3) diff_syntax(line);
1427 path_add(&path, argv[1]);
1428 entry.ignore= (strcmp(argv[2], "ignore") == 0);
1429 if (!entry.ignore && !getattributes(&entry,
1430 argc - 2, argv + 2))
1431 diff_syntax(line);
1432 apply_add(&path, &entry);
1433 } else
1434 if (strcmp(argv[0], "rm") == 0) {
1435 if (argc != 2) diff_syntax(line);
1436 path_add(&path, argv[1]);
1437 apply_remove(&path);
1438 } else
1439 if (strcmp(argv[0], "ln") == 0) {
1440 if (argc != 3) diff_syntax(line);
1441 path_trunc(&link, trunc);
1442 path_add(&link, argv[1]);
1443 path_add(&path, argv[2]);
1444 apply_link(path_name(&link), &path);
1445 } else
1446 if (strcmp(argv[0], "chmod") == 0) {
1447 if (argc != 5) diff_syntax(line);
1448 path_add(&path, argv[1]);
1449 apply_chmod(path_name(&path),
1450 strtoul(argv[2], nil, 010),
1451 strtoul(argv[3], nil, 10),
1452 strtoul(argv[4], nil, 10),
1454 } else
1455 if (strcmp(argv[0], "old") == 0) {
1456 if (argc != 3) diff_syntax(line);
1457 path_add(&path, argv[1]);
1458 apply_mkold(path_name(&path), argv[2]);
1459 } else
1460 if (strcmp(argv[0], "end") == 0) {
1461 if (argc != 1) diff_syntax(line);
1462 break;
1463 } else {
1464 diff_syntax(line);
1467 checkdiff();
1470 void usage(void)
1472 fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n");
1473 fprintf(stderr, " remsync -duxvD tree [state-file [diff-file]]\n");
1474 fprintf(stderr, " remsync [-xv] tree [diff-file]\n");
1475 exit(1);
1478 int main(int argc, char **argv)
1480 int i;
1482 for (i= 1; i < argc && argv[i][0] == '-'; i++) {
1483 char *p= argv[i] + 1;
1485 if (p[0] == '-' && p[1] == 0) { i++; break; }
1487 while (*p != 0) {
1488 switch (*p++) {
1489 case 's': sflag= 1; break;
1490 case 'd': dflag= 1; break;
1491 case 'u': uflag= 1; break;
1492 case 'x': xflag= 1; break;
1493 case 'D': Dflag= 1; break;
1494 case 'v': vflag= 1; break;
1495 default: usage();
1499 if (sflag && dflag) usage();
1500 if (sflag && uflag) usage();
1501 if (!sflag && !dflag && uflag) usage();
1502 if (!dflag && Dflag) usage();
1504 if (i == argc) usage();
1505 tree= argv[i++];
1507 if (sflag) {
1508 /* Make a state file. */
1509 state_file= i < argc ? argv[i++] : "-";
1510 if (i != argc) usage();
1512 statefp= stdout;
1513 if (strcmp(state_file, "-") != 0) {
1514 if ((statefp= fopen(state_file, "w")) == nil)
1515 fatal(state_file);
1517 mkstatefile();
1518 } else
1519 if (dflag) {
1520 /* Make a file of differences. */
1521 state_file= i < argc ? argv[i++] : "-";
1523 diff_file= i < argc ? argv[i++] : "-";
1524 if (i != argc) usage();
1526 statefp= stdin;
1527 if (strcmp(state_file, "-") != 0) {
1528 if ((statefp= fopen(state_file, "r")) == nil)
1529 fatal(state_file);
1532 difffp= stdout;
1533 if (strcmp(diff_file, "-") != 0) {
1534 if ((difffp= fopen(diff_file, "w")) == nil)
1535 fatal(diff_file);
1537 mkdifferences();
1538 } else {
1539 /* Apply a file of differences. */
1540 diff_file= i < argc ? argv[i++] : "-";
1541 if (i != argc) usage();
1543 difffp= stdin;
1544 if (strcmp(diff_file, "-") != 0) {
1545 if ((difffp= fopen(diff_file, "r")) == nil)
1546 fatal(diff_file);
1548 apply_differences();
1550 exit(excode);