1 /* remsync 1.5 - remotely synchronize file trees Author: Kees J. Bot
19 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
20 #define arraylimit(a) ((a) + arraysize(a))
23 #define major(dev) ((int) ((dev) >> 8))
24 #define minor(dev) ((int) ((dev) & 0xFF))
28 /* There were no symlinks in medieval times. */
29 #define S_ISLNK(mode) (0)
31 #define symlink(path1, path2) (errno= ENOSYS, -1)
32 #define readlink(path, buf, len) (errno= ENOSYS, -1)
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
));
55 void fatal(const char *label
)
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",
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.
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
]);
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]));
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. */
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. */
121 lim
= pp
->idx
+ strlen(name
) + 2;
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
--;
136 if (slash
) { *p
++ = '/'; slash
= 0; }
141 if (slash
&& p
== pp
->path
) *p
++= '/';
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. */
160 size_t path_length(const pathname_t
*pp
)
161 /* The length of the pathname. */
166 void path_drop(pathname_t
*pp
)
167 /* Release the storage occupied by the pathname. */
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
;
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;
190 path
= allocate(path
, (len
= size
* 2) * sizeof(path
[0]));
192 if ((n
= readlink(link
, path
, len
)) == -1) return nil
;
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 */
205 nl1
= *(mid
= &(*anl
)->next
);
207 if ((nl1
= nl1
->next
) == nil
) break;
209 } while ((nl1
= nl1
->next
) != nil
);
214 if ((*anl
)->next
!= nil
) sort(anl
);
215 if (nl2
->next
!= nil
) sort(&nl2
);
219 if (strcmp(nl1
->name
, nl2
->name
)<=0) {
220 if ((nl1
= *(anl
= &nl1
->next
)) == nil
) {
226 nl2
= *(anl
= &nl2
->next
);
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
238 namelist_t
*names
, **pn
= &names
;
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))) {
251 *pn
= allocate(nil
, sizeof(**pn
));
252 (*pn
)->name
= copystr(entry
->d_name
);
258 if (names
!= nil
&& names
->next
!= nil
) sort(&names
);
262 char *pop_name(namelist_t
**names
)
263 /* Return one name of a name list. */
276 typedef enum filetype
{ /* The files we know about. */
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. */
295 mode_t mode
; /* Not unlike those in struct stat. */
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
{
315 unsigned long fake_ino
;
317 struct links
**plp
, *lp
;
319 entry
->linked
= entry
->lastlink
= 0;
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
;
330 /* New file, store it with a new fake inode number. */
331 *plp
= lp
= allocate(nil
, sizeof(*lp
));
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
;
339 entry
->link
= lp
->path
;
342 entry
->fake_ino
= lp
->fake_ino
;
344 if (--lp
->nlink
== 0) {
345 /* No need to remember this one, no more links coming. */
353 char *tree
; /* Tree to work on. */
354 FILE *statefp
; /* State file. */
356 FILE *difffp
; /* File of differences. */
359 entry_t
*traverse(void)
360 /* Get one name from the directory tree. */
363 static pathname_t path
;
364 static entry_t entry
;
365 static namelist_t
**entries
;
366 static size_t *trunc
;
368 static namelist_t
*newentries
;
375 /* Initialize for the root of the tree. */
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
);
386 if (newentries
!= nil
) {
387 /* Last entry was a directory, need to go down. */
389 /* Ouch, it is to be ignored! */
390 while (newentries
!= nil
) (void) pop_name(&newentries
);
393 if (++depth
== deep
) {
395 entries
= allocate(entries
, deep
* sizeof(entries
[0]));
396 trunc
= allocate(trunc
, deep
* sizeof(trunc
[0]));
398 entries
[depth
]= newentries
;
400 trunc
[depth
]= path_length(&path
);
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. */
410 entry
.name
= pop_name(&entries
[depth
]);
411 path_trunc(&path
, trunc
[depth
]);
412 path_add(&path
, entry
.name
);
416 entry
.path
= path_name(&path
) + trunc
[0];
417 if (entry
.path
[0] == '/') entry
.path
++;
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.
428 report(path_name(&path
));
431 /* Entry strangely nonexistent; simply continue. */
436 /* Don't cross mountpoints if -x is set. */
438 if (xdev
== NO_DEVICE
) xdev
= st
.st_dev
;
439 if (st
.st_dev
!= xdev
) {
440 /* Ignore the mountpoint. */
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
;
455 if (S_ISDIR(st
.st_mode
)) {
459 /* Gather directory entries for the next traverse. */
460 if ((newentries
= collect(path_name(&path
))) == nil
463 report(path_name(&path
));
466 if (S_ISREG(st
.st_mode
)) {
470 if (S_ISBLK(st
.st_mode
)) {
471 /* A block special file. */
474 if (S_ISCHR(st
.st_mode
)) {
475 /* A character special file. */
478 if (S_ISFIFO(st
.st_mode
)) {
482 if (S_ISLNK(st
.st_mode
)) {
483 /* A symbolic link. */
485 if ((entry
.link
= rdlink(path_name(&path
), st
.st_size
)) == nil
) {
487 report(path_name(&path
));
490 /* Unknown type of file. */
491 entry
.ignore
= EINVAL
;
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);
507 if (putc('\t', statefp
) == EOF
) checkstate();
511 if (putc(' ', statefp
) == EOF
) checkstate();
516 int print_name(FILE *fp
, const char *name
)
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;
527 if (putc(c
, fp
) == EOF
) return 0;
533 void mkstatefile(void)
534 /* Make a state file out of the directory tree. */
538 while ((entry
= traverse()) != nil
) {
539 indent(entry
->depth
);
540 if (!print_name(statefp
, entry
->name
)) checkstate();
543 fprintf(statefp
, "\tignore (%s)\n",
544 strerror(entry
->ignore
));
549 switch (entry
->type
) {
551 fprintf(statefp
, "\td%03o %u %u",
552 (unsigned) entry
->mode
,
553 (unsigned) entry
->uid
, (unsigned) entry
->gid
);
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
);
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
);
571 fprintf(statefp
, "\tp%03o %u %u",
572 (unsigned) entry
->mode
,
573 (unsigned) entry
->uid
, (unsigned) entry
->gid
);
576 fprintf(statefp
, "\t-> ");
578 (void) print_name(statefp
, entry
->link
);
582 if (entry
->fake_ino
!= 0)
583 fprintf(statefp
, " %lu", entry
->fake_ino
);
585 fprintf(statefp
, " last");
586 if (fputc('\n', statefp
) == EOF
) checkstate();
592 char *read1line(FILE *fp
)
593 /* Read one line from a file. Return null on EOF or error. */
600 if (len
== 0) line
= allocate(nil
, (len
= 16) * sizeof(line
[0]));
603 while ((c
= getc(fp
)) != EOF
&& c
!= '\n') {
605 /* Control characters are not possible. */
607 "remsync: control character in data file!\n");
612 line
= allocate(line
, (len
*= 2) * sizeof(line
[0]));
616 if (ferror(fp
)) return nil
;
617 if (idx
== 0) return nil
;
623 void getword(char **pline
, char **parg
, size_t *plen
)
624 /* Get one word from a line, interpret octal escapes. */
634 while ((c
= *line
) != 0 && c
!= ' ' && c
!= '\t') {
638 for (i
= 0; i
< 3; i
++) {
639 if ((unsigned) (*line
- '0') >= 010) break;
640 c
= (c
<< 3) | (*line
- '0');
645 if (idx
== len
) arg
= allocate(arg
, (len
*= 2) * sizeof(arg
[0]));
653 void splitline(char *line
, char ***pargv
, size_t *pargc
)
654 /* Split a line into an array of words. */
663 while (*line
== ' ' || *line
== '\t') line
++;
665 if (*line
== 0) break;
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]));
674 getword(&line
, &argv
[idx
], &lenv
[idx
]);
681 int getattributes(entry_t
*entry
, int argc
, char **argv
)
682 /* Convert state or difference file info into file attributes. */
686 #define A_MODE1 0x01 /* Some of these attributes follow the name */
689 #define A_SIZETIME 0x08
693 switch (argv
[0][0]) {
697 attr
= A_MODE1
| A_OWNER
;
702 attr
= A_MODE1
| A_OWNER
| A_DEV
;
705 /* Character device. */
707 attr
= A_MODE1
| A_OWNER
| A_DEV
;
712 attr
= A_MODE1
| A_OWNER
;
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);
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);
740 if (i
+ 1 > argc
) return 0;
741 entry
->rdev
= strtoul(argv
[i
++], nil
, 0x10);
744 if (i
+ 1 > argc
) return 0;
745 entry
->link
= argv
[i
++];
747 entry
->linked
= entry
->lastlink
= 0;
749 /* It has a fake inode number, so it is a hard link. */
750 static struct links
{ /* List of hard links. */
752 unsigned long fake_ino
;
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
)
766 *plp
= lp
= allocate(nil
, sizeof(*lp
));
768 lp
->fake_ino
= fake_ino
;
769 lp
->path
= copystr(entry
->path
);
772 entry
->link
= lp
->path
;
777 if (strcmp(argv
[i
++], "last") != 0) return 0;
779 /* Last hard link of a file. */
786 if (i
!= argc
) return 0;
790 void state_syntax(off_t line
)
792 fprintf(stderr
, "remsync: %s: syntax error on line %lu\n",
793 state_file
, (unsigned long) line
);
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 int base_indent
;
814 if (feof(statefp
) || (line
= read1line(statefp
)) == nil
) {
820 /* How far is this entry indented? */
823 if (*line
== ' ') indent
++;
825 if (*line
== '\t') indent
= (indent
+ 8) & ~7;
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. */
839 splitline(line
, &argv
, &argc
);
840 if (argc
< 2) state_syntax(lineno
);
843 /* The root of the tree, initialize path. */
844 if (argv
[0][0] != '/') state_syntax(lineno
);
846 path_add(&path
, "/");
847 trunc
= allocate(nil
, (trunc_len
= 16) * sizeof(trunc
[0]));
849 /* The root has depth 0. */
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
);
865 if ((entry
.ignore
= strcmp(argv
[1], "ignore") == 0)) {
868 if (!getattributes(&entry
, argc
- 1, argv
+ 1)) state_syntax(lineno
);
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. */
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
;
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;
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
) {
914 if (remote
->mtime
< local
->mtime
) return COPY
;
916 if (remote
->size
!= local
->size
917 || remote
->mtime
!= local
->mtime
)
923 if (remote
->rdev
!= local
->rdev
) return REPLACE
;
928 if (remote
->mode
!= local
->mode
929 || remote
->uid
!= local
->uid
930 || remote
->gid
!= local
->gid
) return SIMILAR
;
933 if (strcmp(remote
->link
, local
->link
) != 0) return REPLACE
;
939 void delete(entry_t
*old
)
940 /* Emit an instruction to remove an entry. */
942 if (old
->ignore
) return;
945 fprintf(difffp
, "rm ");
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 ");
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
);
964 if (vflag
&& old
->mode
!= new->mode
) {
965 fprintf(stderr
, "chmod %s %03o %u %u\n",
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. */
976 unsigned char buf
[1024 << sizeof(int)];
980 if (Dflag
) return 1; /* Debug: Don't need the file contents. */
982 while ((n
= read(f
, buf
, sizeof(buf
))) > 0) {
986 /* File suddenly larger. */
991 if (putc(c
, difffp
) == EOF
) checkdiff();
998 /* File somehow shrunk, pad it out. */
1000 if (putc(0, difffp
) == EOF
) checkdiff();
1001 } while (--size
!= 0);
1002 errno
= n
== 0 ? EINVAL
: err
;
1008 void add(entry_t
*old
, entry_t
*new)
1009 /* Emit an instruction to add an entry. */
1014 if (new->ignore
) return;
1017 /* This file is to be a hard link to an existing file. */
1018 fprintf(difffp
, "ln ");
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();
1025 fprintf(stderr
, "ln %s %s\n", new->link
, new->path
);
1030 /* Add some other type of file. */
1031 fprintf(difffp
, "add ");
1033 if (!print_name(difffp
, new->path
)) checkdiff();
1035 switch (new->type
) {
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
);
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
));
1049 fprintf(difffp
, " ignore\n");
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
);
1058 if (!cat(f
, new->size
)) {
1060 report(path_name(&file
));
1061 fprintf(difffp
, "old ");
1063 print_name(difffp
, err
== EINVAL
1064 ? "File changed when copied" : strerror(err
));
1065 fputc('\n', difffp
);
1069 fprintf(stderr
, "%s %s\n",
1070 old
== nil
? "add" :
1071 old
->mtime
> new->mtime
?
1072 "restore" : "update",
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
);
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
);
1095 fprintf(difffp
, " -> ");
1097 (void) print_name(difffp
, new->link
);
1099 fputc('\n', difffp
);
1101 fprintf(stderr
, "ln -s %s %s\n", new->link
, new->path
);
1108 void mkdifferences(void)
1113 remote
= readstate();
1116 while (remote
!= nil
|| local
!= nil
) {
1117 switch (compare(remote
, local
)) {
1119 /* Remove the remote file. */
1122 remote
= readstate();
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. */
1133 /* Overwrite the remote file with the local one. */
1138 /* About the same, but the attributes need changing. */
1139 change_modes(remote
, local
);
1143 /* Skip two files. */
1144 remote
= readstate();
1148 /* Add the local file. */
1154 fprintf(difffp
, "end\n");
1159 void apply_remove(pathname_t
*pp
)
1160 /* Remove an obsolete file. */
1164 if (lstat(path_name(pp
), &st
) < 0) {
1165 if (errno
!= ENOENT
) report(path_name(pp
));
1169 if (S_ISDIR(st
.st_mode
)) {
1170 /* Recursively delete directories. */
1172 namelist_t
*entries
;
1174 if ((entries
= collect(path_name(pp
))) == nil
&& errno
!= 0) {
1175 report(path_name(pp
));
1178 len
= path_length(pp
);
1180 while (entries
!= nil
) {
1181 path_add(pp
, pop_name(&entries
));
1183 path_trunc(pp
, len
);
1185 if (rmdir(path_name(pp
)) < 0) {
1186 report(path_name(pp
));
1189 if (vflag
) fprintf(stderr
, "rmdir %s\n", path_name(pp
));
1191 /* Some other type of file. */
1192 if (unlink(path_name(pp
)) < 0) {
1193 report(path_name(pp
));
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.) */
1205 utb
.actime
= utb
.modtime
= 0;
1206 if (utime(file
, &utb
) < 0) {
1210 fprintf(stderr
, "made %s look old", file
);
1211 fprintf(stderr
, err
== nil
? "\n" : " due to a remote problem: %s\n",
1215 void apply_chmod(const char *file
, mode_t mode
, uid_t uid
, gid_t gid
, int talk
)
1216 /* Change mode and ownership. */
1220 if (lstat(file
, &st
) < 0) {
1224 if ((st
.st_mode
& 07777) != mode
) {
1225 if (chmod(file
, mode
) < 0) {
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
);
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. */
1252 unsigned char buf
[1024 << sizeof(int)];
1259 if (entry
->ignore
) return;
1261 if (lstat(path_name(pp
), &st
) >= 0 && (entry
->type
!= F_FILE
1262 || !S_ISREG(st
.st_mode
))) {
1266 file
= path_name(pp
);
1268 switch (entry
->type
) {
1270 if (mkdir(file
, entry
->mode
) < 0) {
1274 if (vflag
) fprintf(stderr
, "mkdir %s\n", file
);
1281 if (lstat(file
, &st
) < 0 || S_ISREG(st
.st_mode
)) {
1282 f
= open(file
, O_WRONLY
| O_CREAT
| O_TRUNC
,
1285 (void) chmod(file
, entry
->mode
| 0200);
1286 f
= open(file
, O_WRONLY
| O_CREAT
| O_TRUNC
,
1290 (void) unlink(file
);
1291 f
= open(file
, O_WRONLY
| O_CREAT
| O_TRUNC
,
1294 if (f
< 0) report(file
);
1298 while (size
> 0 && (c
= getc(difffp
)) != EOF
) {
1301 if (p
== arraylimit(buf
) || size
== 0) {
1302 if (f
>= 0 && write(f
, buf
, p
- buf
) < 0) {
1311 if (ferror(difffp
)) report(diff_file
);
1313 fprintf(stderr
, "remspec: %s: premature EOF\n",
1316 if (dirty
) apply_mkold(file
, nil
);
1320 if (dirty
) apply_mkold(file
, nil
);
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
);
1334 if (mknod(file
, S_IFBLK
| entry
->mode
, entry
->rdev
) < 0) {
1339 fprintf(stderr
, "mknod %s b %d %d\n", file
,
1340 major(entry
->rdev
), minor(entry
->rdev
));
1344 if (mknod(file
, S_IFCHR
| entry
->mode
, entry
->rdev
) < 0) {
1349 fprintf(stderr
, "mknod %s c %d %d\n", file
,
1350 major(entry
->rdev
), minor(entry
->rdev
));
1354 if (mknod(file
, S_IFIFO
| entry
->mode
, 0) < 0) {
1358 if (vflag
) fprintf(stderr
, "mknod %s p\n", file
);
1361 if (symlink(entry
->link
, file
) < 0) {
1365 if (vflag
) fprintf(stderr
, "ln -s %s %s\n", entry
->link
, file
);
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) {
1380 if (lstat(path_name(pp
), &st2
) >= 0) {
1381 if (st1
.st_ino
== st2
.st_ino
&& st1
.st_dev
== st2
.st_dev
)
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
),
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",
1402 void apply_differences(void)
1403 /* Update a tree to a list of differences derived from a remote tree. */
1408 pathname_t path
, 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) {
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))
1432 apply_add(&path
, &entry
);
1434 if (strcmp(argv
[0], "rm") == 0) {
1435 if (argc
!= 2) diff_syntax(line
);
1436 path_add(&path
, argv
[1]);
1437 apply_remove(&path
);
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
);
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),
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]);
1460 if (strcmp(argv
[0], "end") == 0) {
1461 if (argc
!= 1) diff_syntax(line
);
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");
1478 int main(int argc
, char **argv
)
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; }
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;
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();
1508 /* Make a state file. */
1509 state_file
= i
< argc
? argv
[i
++] : "-";
1510 if (i
!= argc
) usage();
1513 if (strcmp(state_file
, "-") != 0) {
1514 if ((statefp
= fopen(state_file
, "w")) == nil
)
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();
1527 if (strcmp(state_file
, "-") != 0) {
1528 if ((statefp
= fopen(state_file
, "r")) == nil
)
1533 if (strcmp(diff_file
, "-") != 0) {
1534 if ((difffp
= fopen(diff_file
, "w")) == nil
)
1539 /* Apply a file of differences. */
1540 diff_file
= i
< argc
? argv
[i
++] : "-";
1541 if (i
!= argc
) usage();
1544 if (strcmp(diff_file
, "-") != 0) {
1545 if ((difffp
= fopen(diff_file
, "r")) == nil
)
1548 apply_differences();