1 /* synctree 4.16 - Synchronise file tree. Author: Kees J. Bot
4 * synctree [-iuf] [[user1@machine1:]dir1 [[user2@]machine2:]dir2
6 * Dir2 will then be synchronized to dir1 with respect to the flags.
8 * -i Be interactive on files other than directories too.
9 * -u Only install files that are newer, i.e. that need an update.
10 * -f Force. Don't ask for confirmation, all answers are 'yes'.
12 * Hitting return lets synctree use its proposed answer. Hitting CTRL-D is
13 * like typing return to all questions that follow.
15 * If either of the directories to be synchronized contains the file ".backup"
16 * then it is a backup directory. The file ".backup" in this directory is
17 * an array of mode information indexed on inode number.
19 * 89/04/05, Kees J. Bot - Birth of tree synchronizing program.
20 * 92/02/02 - General overhaul, rcp(1) like syntax.
24 #include <sys/types.h>
39 #define USE_SHADOWING 0
47 /* There were no symlinks in medieval times. */
48 #define S_ISLNK(mode) (0)
50 #define symlink(path1, path2) (errno= ENOSYS, -1)
51 #define readlink(path, buf, len) (errno= ENOSYS, -1)
54 #define NUMBYTES 4 /* Any number fits in this many bytes. */
55 #define CHUNK 4096 /* Transfer files in this size chunks. */
57 static int install
= 0; /* Install files, do not delete, update if newer. */
58 static int interact
= 0; /* Ask permission to install too. */
59 static int force
= 0; /* Force trees to be completely equal. */
60 static int backup
= 0; /* This tree is for backup. */
62 static char SYNCNAME
[] = "synctree";
63 static char SLAVENAME
[] = "==SLAVE==";
64 static char MASTERNAME
[]= "==MASTER==";
67 static char BACKUP
[] = ".backup"; /* Backup filemodes. */
68 static int filemodes
; /* Filemodes fildes. */
70 static int chan
[2]= { 0, 1 }; /* In and output channel to opposite side. */
72 #define BUCKSIZE (1+NUMBYTES+CHUNK)
73 static char bucket
[BUCKSIZE
]; /* Put a lot of things here before sending. */
74 static char *buckp
= bucket
; /* Fill pointer. */
75 static int buckn
= 0; /* # bytes in bucket. */
77 enum orders
{ /* What back breaking labour should the slave perform? */
78 ENTER
= 128, /* Make ready to process contents of directory. */
79 ADVANCE
, /* Determine next pathname and report it back. */
80 CAT
, /* Send contents of file. */
81 MORE
, /* Send more file contents. */
82 ORDER_CANCEL
, /* Current pathname is not installed, remove as link. */
83 DIE
, /* Die with exit(0); */
84 DIE_BAD
, /* exit(1); */
85 POSITIVE
, /* Ask a yes/no question expecting yes. */
86 NEGATIVE
, /* Same expecting no. */
87 PASS_YES
, /* Pass this to the master will you. */
88 PASS_NO
/* Same here. */
93 "ENTER", "ADVANCE", "CAT", "MORE", "CANCEL", "DIE", "DIE_BAD",
94 "POSITIVE", "NEGATIVE", "PASS_YES", "PASS_NO"
99 PATH
= 128, /* Report pathname, and stat(2) info. */
100 LINK
, /* Report linkname, pathname, and stat(2) info. */
101 DATA
, /* Contents of file follow. */
102 NODATA
, /* Can't read file. */
103 DONE
, /* Nothing more to advance to. */
104 SYMLINK
, /* Report symlinkname, pathname, and stat(2) info. */
105 YES
, NO
/* Result of an ASK. */
110 "PATH", "LINK", "DATA", "NODATA", "DONE", "SYMLINK", "YES", "NO"
113 #define DPRINTF(format, arg) fprintf(stderr, format, arg0, arg)
115 #define DPRINTF(format, arg)
119 unsigned short md_mode
;
120 unsigned short md_uid
;
121 unsigned short md_gid
;
122 unsigned short md_rdev
;
123 unsigned short md_devsiz
;
126 static char *arg0
; /* basename(argv[0]) */
127 static int ex
= 0; /* exit status. */
129 static void because()
131 fprintf(stderr
, ": %s\n", strerror(errno
));
135 static void perr(char *label
)
137 fprintf(stderr
, "%s: %s: %s\n", arg0
, label
, strerror(errno
));
141 static void perrx(char *label
)
148 /* Support for per achitecture hidden files. */
149 static int transparent
= 0;
151 static void isvisible(char *name
)
153 char *p
= name
+ strlen(name
);
155 while (p
> name
&& *--p
== '/') {}
157 if (p
> name
&& *p
== '@' && p
[-1] != '/') transparent
= 1;
160 #define transparent 0
161 #define isvisible(name) ((void) 0)
164 static void isbackup(int slave
)
166 if ((filemodes
= open(BACKUP
, slave
? O_RDONLY
: O_RDWR
)) >= 0)
169 if (errno
!= ENOENT
) perrx(BACKUP
);
173 static char path
[PATH_MAX
+1]; /* Holds pathname of file being worked on. */
174 static char lnkpth
[PATH_MAX
+1]; /* (Sym)link to path. */
175 static char *linkpath
; /* What path is, or should be linked to. */
176 static struct stat st
; /* Corresponding stat(2) info. */
177 static char Spath
[PATH_MAX
+1]; /* Slave is looking at this. */
178 static char Slnkpth
[PATH_MAX
+1];/* (Sym)link to Spath. */
179 static char *Slinkpath
=nil
; /* Either nil or Slnkpth. */
180 static struct stat Sst
; /* Slave's stat(2). */
182 static char *addpath(char *p
, char *n
)
183 /* Add a name to the path, return pointer to end. */
185 if (p
- path
+ 1 + strlen(n
) > PATH_MAX
) {
187 fprintf(stderr
, "%s: %s/%s is too long.\n", arg0
, path
, n
);
188 fprintf(stderr
, "%s: Unable to continue.\n", arg0
);
191 if (p
== path
+1 && path
[0] == '.') p
= path
;
193 if (p
> path
) *p
++ = '/';
195 while (*n
!= 0) *p
++ = *n
++;
200 struct entry
{ /* A directory entry. */
201 struct entry
*next
; /* Next entry in same directory */
202 struct entry
*dir
; /* It is part of this directory */
203 struct entry
*con
; /* If a dir, its contents */
204 char *name
; /* Name of this dir entry */
207 static struct entry
*E
= nil
; /* File being processed. */
209 static void setpath(struct entry
*e
)
210 /* Set path leading to e. */
218 pend
= addpath(pend
, e
->name
);
222 static void sort(struct entry
**ae
)
223 /* This is either a stable mergesort, or thermal noise, I'm no longer sure.
224 * It must be called like this: if (L!=nil && L->next!=nil) sort(&L);
227 /* static */ struct entry
*e1
, **mid
; /* Need not be local */
230 e1
= *(mid
= &(*ae
)->next
);
232 if ((e1
= e1
->next
) == nil
) break;
234 } while ((e1
= e1
->next
) != nil
);
239 if ((*ae
)->next
!= nil
) sort(ae
);
240 if (e2
->next
!= nil
) sort(&e2
);
244 if (strcmp(e1
->name
, e2
->name
)<=0) {
245 if ((e1
= *(ae
= &e1
->next
)) == nil
) {
251 e2
= *(ae
= &e2
->next
);
253 if (e2
== nil
) break;
259 /* Collect directory entries of E. */
261 struct entry
**last
= &E
->con
, *new;
265 if ((d
= opendir(path
)) == nil
) {
266 fprintf(stderr
, "%s: Can't read dir %s\n", arg0
, path
);
270 while ((e
= readdir(d
)) != nil
) {
271 if (e
->d_name
[0] == '.' && (e
->d_name
[1] == 0
272 || (e
->d_name
[1] == '.' && e
->d_name
[2] == 0)
275 new= (struct entry
*) malloc(sizeof(*new));
280 new->name
= (char *) malloc(strlen(e
->d_name
) + 1);
281 strcpy(new->name
, e
->d_name
);
286 if (E
->con
!= nil
&& E
->con
->next
!= nil
) sort(&E
->con
);
289 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
290 #define arraylimit(a) ((a) + arraysize(a))
292 static char *link_islink(struct stat
*stp
, const char *file
)
294 /* Tell if a file, which stat(2) information in '*stp', has been seen
295 * earlier by this function under a different name. If not return a
296 * null pointer with errno set to ENOENT, otherwise return the name of
297 * the link. Return a null pointer with an error code in errno for any
298 * error, using E2BIG for a too long file name.
300 * Use link_islink(nil, nil) to reset all bookkeeping.
302 * Call for a file twice to delete it from the store.
305 typedef struct link
{ /* In-memory link store. */
306 struct link
*next
; /* Hash chain on inode number. */
307 ino_t ino
; /* File's inode number. */
308 off_t off
; /* Offset to more info in temp file. */
310 typedef struct dlink
{ /* On-disk link store. */
311 dev_t dev
; /* Device number. */
312 char file
[PATH_MAX
]; /* Name of earlier seen link. */
314 static link_t
*links
[256]; /* Hash list of known links. */
315 static int tfd
= -1; /* Temp file for file name storage. */
316 static dlink_t dlink
;
322 /* Reset everything. */
323 for (plp
= links
; plp
< arraylimit(links
); plp
++) {
324 while ((lp
= *plp
) != nil
) {
329 if (tfd
!= -1) close(tfd
);
334 /* The file must be a non-directory with more than one link. */
335 if (S_ISDIR(stp
->st_mode
) || stp
->st_nlink
<= 1) {
340 plp
= &links
[stp
->st_ino
% arraysize(links
)];
342 while ((lp
= *plp
) != nil
) {
343 if (lp
->ino
== stp
->st_ino
) {
344 /* May have seen this link before. Get it and check. */
345 if (lseek(tfd
, lp
->off
, SEEK_SET
) == -1) return nil
;
346 if (read(tfd
, &dlink
, sizeof(dlink
)) < 0) return nil
;
348 /* Only need to check the device number. */
349 if (dlink
.dev
== stp
->st_dev
) {
350 if (strcmp(file
, dlink
.file
) == 0) {
351 /* Called twice. Forget about this link. */
358 /* Return the name of the earlier link. */
365 /* First time I see this link. Add it to the store. */
371 tfd
= open(tmp
, O_RDWR
|O_CREAT
|O_EXCL
, 0600);
373 if (errno
!= EEXIST
) return nil
;
380 if ((len
= strlen(file
)) >= PATH_MAX
) {
385 dlink
.dev
= stp
->st_dev
;
386 strcpy(dlink
.file
, file
);
387 len
+= offsetof(dlink_t
, file
) + 1;
388 if ((off
= lseek(tfd
, 0, SEEK_END
)) == -1) return nil
;
389 if (write(tfd
, &dlink
, len
) != len
) return nil
;
391 if ((lp
= malloc(sizeof(*lp
))) == nil
) return nil
;
393 lp
->ino
= stp
->st_ino
;
400 #define cancellink() ((void) islink())
402 static char *islink()
403 /* Returns the name of the file path is linked to. If no such link can be
404 * found, then path is added to the list and nil is returned. If all the
405 * links of a file have been seen, then it is removed from the list.
406 * Directories are not seen as linkable.
411 name
= link_islink(&st
, path
);
412 if (name
== nil
&& errno
!= ENOENT
) perrx(path
);
416 static void setstat(ino_t ino
, struct stat
*stp
)
417 /* Set backup status info, we know that backup is true. */
421 md
.md_mode
= stp
->st_mode
;
422 md
.md_uid
= stp
->st_uid
;
423 md
.md_gid
= stp
->st_gid
;
424 md
.md_rdev
= stp
->st_rdev
;
425 md
.md_devsiz
= stp
->st_size
/ 1024;
427 if (lseek(filemodes
, (off_t
) sizeof(md
) * (off_t
) ino
, 0) == -1
428 || write(filemodes
, (char *) &md
, sizeof(md
)) != sizeof(md
)
432 static int getstat(char *name
, struct stat
*stp
)
433 /* Get status information of file name, skipping some files. Backup info
434 * is inserted as needed.
439 if (strcmp(name
, BACKUP
) == 0) return -1;
441 if (lstat(name
, stp
) < 0) return -1;
443 if (stp
->st_mode
== S_IFREG
&& stp
->st_mtime
== 0) return -1;
449 (off_t
) sizeof(md
) * (off_t
) stp
->st_ino
, 0) == -1
450 || read(filemodes
, (char *) &md
, sizeof(md
)) != sizeof(md
)
454 setstat(stp
->st_ino
, stp
);
456 stp
->st_mode
= md
.md_mode
;
457 stp
->st_uid
= md
.md_uid
;
458 stp
->st_gid
= md
.md_gid
;
459 stp
->st_rdev
= md
.md_rdev
;
460 if (S_ISBLK(stp
->st_mode
))
461 stp
->st_size
= (off_t
) md
.md_devsiz
* 1024;
468 /* Determine next pathname, return true on success. */
471 if (E
==nil
) { /* First call, enter root dir. */
472 E
= (struct entry
*) malloc(sizeof(*E
));
476 E
->name
= (char *) malloc(3);
477 strcpy(E
->name
, transparent
? ".@" : ".");
479 if (E
->con
!= nil
) /* Dir's files must be processed. */
483 /* Remove E from it's parents list, then
484 * try next entry, if none, go to parent dir.
486 struct entry
*junk
= E
, *parent
= E
->dir
;
488 if (parent
!= nil
) parent
->con
= E
->next
;
495 if ((E
= parent
) == nil
) return 0;
499 if (getstat(path
, &st
) == 0) {
500 if (S_ISLNK(st
.st_mode
)) {
503 if ((n
= readlink(path
, lnkpth
, PATH_MAX
)) >= 0)
512 if (errno
!= 0 && errno
!= ENOENT
) perr(path
);
516 DPRINTF("%s: path = %s\n", path
);
520 static enum orders
request()
521 /* Slave reads command sent by master. */
523 static char buf
[64], *bp
;
529 if ((n
= read(chan
[0], buf
, (int) sizeof(buf
))) <= 0) {
530 if (n
< 0) perrx("request()");
531 /* Master died, try to report it then follow. */
533 "%s: Master died prematurely.\n", arg0
);
540 if (req
>= (int) ENTER
) break;
542 /* Master using slave to print to stdout: */
545 DPRINTF("%s: request() == %s\n", ORDERS
[req
- (int) ENTER
]);
547 return (enum orders
) req
;
554 DPRINTF("%s: reporting now!\n", 0);
560 if (r
> (512 << sizeof(char*))) r
= (512 << sizeof(char*));
562 if ((r
= write(chan
[1], buckp
, r
)) < 0) perrx("report()");
571 static void inform(enum answers a
)
572 /* Slave replies to master. */
574 DPRINTF("%s: inform(%s)\n", ANSWERS
[(int) a
- (int) PATH
]);
580 #define wwrite(buf, n) (memcpy(buckp, (buf), (n)), buckp+= (n), buckn+= (n))
582 static void sendnum(long n
)
583 /* Send number from least to most significant byte. */
585 #if BYTE_ORDER == LITTLE_ENDIAN
586 wwrite((char *) &n
, sizeof(n
));
590 buf
[0]= (char) (n
>> 0);
591 buf
[1]= (char) (n
>> 8);
592 buf
[2]= (char) (n
>> 16);
593 buf
[3]= (char) (n
>> 24);
594 wwrite(buf
, sizeof(buf
));
598 static void send(char *buf
, int n
)
599 /* Slave sends size and contents of buf. */
602 if (n
> 0) wwrite(buf
, (size_t) n
);
605 static void sendstat(struct stat
*stp
)
607 sendnum((long) stp
->st_mode
);
608 sendnum((long) stp
->st_uid
);
609 sendnum((long) stp
->st_gid
);
610 sendnum((long) stp
->st_rdev
);
611 sendnum((long) stp
->st_size
);
612 sendnum((long) stp
->st_mtime
);
618 /* Carry out orders from the master, such as transmitting path names.
619 * Note that the slave uses path, not Spath, the master uses Spath.
624 enum { run
, done
, die
} state
= run
;
632 if (!advance() || state
== done
) {
638 send(linkpath
, strlen(linkpath
) + 1);
640 if (S_ISLNK(st
.st_mode
)) {
642 send(lnkpth
, strlen(lnkpth
) + 1);
646 send(path
, strlen(path
) + 1);
651 if ((f
= open(path
, O_RDONLY
))<0) {
652 fprintf(stderr
, "%s: Can't open %s",
660 n
= read(f
, buf
, sizeof(buf
));
661 if (n
< 0) perr(path
);
677 inform(ask('y') ? YES
: NO
);
680 inform(ask('n') ? YES
: NO
);
689 fprintf(stderr
, "%s: strange request\n", arg0
);
693 } while (state
!= die
);
696 static int execute(char **argv
)
697 /* Execute a command and return its success or failure. */
701 if ((pid
= fork())<0) {
706 execvp(argv
[0], argv
);
709 while ((r
= wait(&status
)) != pid
) {
718 static int removedir(char *dir
)
719 /* Remove a directory and its contents. */
721 static char *argv
[] = { "rm", "-r", nil
, nil
};
723 printf("(rm -r %s)\n", dir
);
726 return execute(argv
);
729 static void order(enum orders o
)
730 /* Master tells slave what to do. */
734 DPRINTF("%s: order(%s)\n", ORDERS
[o
- (int) ENTER
]);
736 if (write(chan
[1], &c
, 1) != 1) perrx("order()");
739 static void rread(char *buf
, int n
)
740 /* Master gets buf of size n from slave, doing multiple reads if needed. */
746 switch (buckn
= read(chan
[0], bucket
, BUCKSIZE
)) {
748 perrx("reply channel from slave");
751 "%s: slave died prematurely.\n",
757 r
= n
< buckn
? n
: buckn
;
758 memcpy(buf
, buckp
, r
);
766 static enum answers
answer()
767 /* Master reads slave's reply. */
775 DPRINTF("%s: answer() == %s\n", ANSWERS
[a
- (int) PATH
]);
777 return (enum answers
) a
;
781 /* Read number as pack of bytes from least to most significant. The data
782 * is on the wire in little-endian format. (Mostly run on PC's).
785 #if BYTE_ORDER == LITTLE_ENDIAN
788 rread((char *) &n
, (int) sizeof(n
));
791 unsigned char buf
[NUMBYTES
];
793 rread(buf
, sizeof(buf
));
795 | ((unsigned) buf
[1] << 8)
796 | ((unsigned long) buf
[2] << 16)
797 | ((unsigned long) buf
[3] << 24);
801 static int receive(char *buf
, int max
)
802 /* Master get's data from slave, by first reading size, then data. */
808 fprintf(stderr
, "%s: panic: Can't read %d bytes\n", arg0
, n
);
811 if (n
> 0) rread(buf
, n
);
815 static void recstat(struct stat
*stp
)
817 stp
->st_mode
= recnum();
818 stp
->st_uid
= recnum();
819 stp
->st_gid
= recnum();
820 stp
->st_rdev
= recnum();
821 stp
->st_size
= recnum();
822 stp
->st_mtime
= recnum();
830 if (tty
< 0) tty
= isatty(0);
832 if (feof(stdin
) || (c
= getchar()) == EOF
) {
834 if (tty
) putchar('\n');
837 if (!tty
) putchar(c
);
842 static int ask(int def
)
843 /* Ask for a yes or no, anything else means choose def. */
848 /* I'm running remote, tell the slave to ask. */
850 order(def
== 'y' ? POSITIVE
: NEGATIVE
);
851 return answer() == YES
;
854 printf("? (%c) ", def
);
857 do c
= key(); while (c
== ' ' || c
== '\t');
861 while (c
!= '\n') c
= key();
863 if (y
!= 'y' && y
!= 'Y' && y
!= 'n' && y
!= 'N') y
= def
;
865 return y
== 'y' || y
== 'Y';
868 static void setmodes(int silent
)
876 if (backup
&& silent
) {
877 setstat(st
.st_ino
, &Sst
);
881 if (S_ISLNK(st
.st_mode
)) return;
883 if (errno
== 0 && st
.st_mode
!= Sst
.st_mode
) {
884 if (!backup
) chmod(Spath
, Sst
.st_mode
& 07777);
888 && (st
.st_uid
!= Sst
.st_uid
|| st
.st_gid
!= Sst
.st_gid
)
889 && (backup
|| geteuid() == 0)
892 if (!backup
) chown(Spath
, Sst
.st_uid
, Sst
.st_gid
);
896 if (backup
&& !silent
) setstat(st
.st_ino
, &Sst
);
898 if (errno
== 0 && S_ISREG(Sst
.st_mode
) && st
.st_mtime
!= Sst
.st_mtime
) {
900 tms
.modtime
= Sst
.st_mtime
;
906 fprintf(stderr
, "%s: Can't set modes of %s", arg0
, Spath
);
909 if (change
&& !silent
) {
910 printf("Mode changed of %s\n", Spath
);
914 static void makeold()
916 static struct utimbuf tms
= { 0, 0 };
918 if (utime(Spath
, &tms
) < 0) {
919 if (errno
!= ENOENT
) {
921 "%s: can't make %s look old", arg0
, Spath
);
925 fprintf(stderr
, "%s: made %s look old.\n", arg0
, Spath
);
931 static void bail_out(int sig
)
933 signal(sig
, SIG_IGN
);
935 fprintf(stderr
, "%s: Exiting after signal %d\n", arg0
, sig
);
938 fprintf(stderr
, "%s: was installing %s\n", arg0
, Spath
);
946 static int makenode(char *name
, int mode
, dev_t addr
, off_t size
)
951 r
= mknod(name
, mode
, addr
);
953 if ((r
= creat(name
, 0644)) >= 0) close(r
);
958 static void add(int update
)
959 /* Add Spath to the filesystem. */
963 int forced_update
= force
&& update
;
965 if (Slinkpath
!= nil
&& !S_ISLNK(Sst
.st_mode
)) {
966 if (interact
&& !update
) {
967 printf("Link %s to %s", Spath
, Slinkpath
);
968 if (!ask('n')) return;
970 if (link(Slinkpath
, Spath
) >= 0) {
971 printf("Linked %s to %s\n", Spath
, Slinkpath
);
975 "%s: Can't link %s to %s",
976 arg0
, Slinkpath
, Spath
);
978 /* Try to install instead. */
981 switch (Sst
.st_mode
& S_IFMT
) {
984 printf("Add dir %s", Spath
);
985 if (!ask('n')) return;
987 if (mkdir(Spath
, backup
? 0755 : Sst
.st_mode
) < 0) {
991 printf("Directory %s created.\n", Spath
);
997 if (interact
&& !update
) {
998 printf("Create special file %s", Spath
);
999 if (!ask('n')) { order(ORDER_CANCEL
); return; }
1001 if (makenode(Spath
, Sst
.st_mode
, Sst
.st_rdev
, Sst
.st_size
)<0) {
1003 "%s: Can't create special file %s",
1008 printf("Special file %s created\n", Spath
);
1012 if (interact
&& !update
) {
1013 printf("Install %s -> %s", Spath
, Slnkpth
);
1014 if (!ask('n')) { order(ORDER_CANCEL
); return; }
1016 if (symlink(Slnkpth
, Spath
) < 0) {
1017 fprintf(stderr
, "%s: Can't create symlink %s",
1022 printf("%s %s -> %s\n",
1023 forced_update
? "Updated: " : "Installed:",
1028 if (interact
&& !update
) {
1029 printf("Install %s", Spath
);
1030 if (!ask('n')) { order(ORDER_CANCEL
); return; }
1033 if (answer() != DATA
) return;
1036 if ((f
= creat(Spath
, backup
? 0644 : Sst
.st_mode
&07777)) < 0) {
1038 fprintf(stderr
, "%s: Can't create %s", arg0
, Spath
);
1042 while ((n
= receive(buf
, sizeof(buf
)))>0) {
1043 if (f
>= 0 && write(f
, buf
, n
) != n
) {
1044 fprintf(stderr
, "%s: Write error on %s",
1051 fprintf(stderr
, "%s: Slave read err on %s\n",
1054 if (f
>= 0) close(f
);
1055 if (n
< 0 || f
< 0) { makeold(); busy
= 0; return; }
1059 Sst
.st_mtime
< st
.st_mtime
? "Restored: " :
1067 "%s: Won't add file with strange mode %05o: %s\n",
1068 arg0
, Sst
.st_mode
, Spath
);
1069 order(ORDER_CANCEL
);
1075 static int delete(int update
)
1078 int forced_update
= force
&& update
;
1080 if (S_ISDIR(st
.st_mode
)) {
1081 if (install
) return 0;
1083 printf("Delete dir %s", path
);
1084 if (!ask('n')) return 0;
1086 if (!removedir(path
)) { ex
= 1; return 0; }
1087 if (!forced_update
) printf("Directory %s deleted.\n", path
);
1091 if (install
&& !update
) return 0;
1094 printf("Delete %s", path
);
1095 if (!ask((interact
&& !update
) ? 'n' : 'y')) return 0;
1098 if (unlink(path
)<0) {
1099 fprintf(stderr
, "Can't delete %s", path
);
1104 if (!forced_update
) printf("Deleted: %s\n", path
);
1108 static int different()
1109 /* Return true iff path and Spath are different. */
1111 if (! ( (linkpath
== nil
&& Slinkpath
== nil
)
1112 || (linkpath
!= nil
&& Slinkpath
!= nil
1113 && strcmp(linkpath
, Slinkpath
) == 0)
1115 linkpath
= Slinkpath
;
1119 if ((st
.st_mode
& S_IFMT
) != (Sst
.st_mode
& S_IFMT
)) return 1;
1121 switch (st
.st_mode
& S_IFMT
) {
1126 return st
.st_rdev
!= Sst
.st_rdev
;
1128 if (install
) return Sst
.st_mtime
> st
.st_mtime
;
1129 return st
.st_size
!= Sst
.st_size
1130 || st
.st_mtime
!= Sst
.st_mtime
;
1131 case S_IFIFO
: return 0;
1133 case S_IFLNK
: return strcmp(lnkpth
, Slnkpth
) != 0;
1139 static void compare()
1140 /* See if path and Spath are same. */
1144 printf("%sing %s (delete + add)\n",
1145 Sst
.st_mtime
< st
.st_mtime
? "Restor" : "Updat",
1148 if (delete(1)) add(1);
1150 if (!install
) setmodes(0);
1152 if (S_ISDIR(st
.st_mode
)) {
1159 static int done
= 0, Sdone
= 0;
1161 static enum action
{ ADD
, COMPARE
, DELETE
} action()
1162 /* Look at path's of master and slave, compare them alphabetically to see
1163 * who is ahead of who, then tell what is to be done.
1169 if (done
) return ADD
; /* Slave still has names. */
1170 if (Sdone
) return DELETE
; /* Master has too many names. */
1172 /* Compare paths. Let "a/a" come before "a.a". */
1175 while (*Sp
== *p
&& *Sp
!= 0) { Sp
++; p
++; }
1176 if (*Sp
== '/') return ADD
;
1177 if (*p
== '/') return DELETE
;
1178 return (c
= strcmp(Sp
, p
)) == 0 ? COMPARE
: c
< 0 ? ADD
: DELETE
;
1181 static void master()
1182 /* Synchronise file tree to that of its slave. */
1184 enum action a
= COMPARE
; /* Trick first advances. */
1186 umask(backup
? 0022 : 0000);
1188 signal(SIGPIPE
, SIG_IGN
);
1189 signal(SIGHUP
, bail_out
);
1190 signal(SIGINT
, bail_out
);
1191 signal(SIGTERM
, bail_out
);
1193 while (!done
|| !Sdone
) {
1194 if (!Sdone
&& (a
== ADD
|| a
== COMPARE
)) {
1195 /* Slave advances. */
1200 receive(Spath
, sizeof(Spath
));
1204 receive(Slnkpth
, sizeof(Slnkpth
));
1206 receive(Spath
, sizeof(Spath
));
1211 receive(Slnkpth
, sizeof(Slnkpth
));
1212 receive(Spath
, sizeof(Spath
));
1220 "%s: Strange answer from slave.\n",
1225 if (!done
&& (a
== COMPARE
|| a
== DELETE
)) {
1226 /* Master advances. */
1227 if (!advance()) done
= 1;
1230 if (done
&& Sdone
) break;
1232 switch (a
= action()) {
1233 case ADD
: /* Spath exists, path doesn't, add? */
1236 case COMPARE
: /* They both exist, are they the same? */
1239 case DELETE
: /* path exists, Spath doesn't, delete? */
1242 fflush(stdout
); /* Don't keep user in suspense. */
1244 order(ex
== 0 ? DIE
: DIE_BAD
);
1247 static void mediator()
1248 /* Sits at the local machine and passes orders from master to slave, both
1249 * on remote machines. Only diagnostics and questions are handled.
1255 switch (req
= request()) {
1263 order(ask('y') ? PASS_YES
: PASS_NO
);
1266 order(ask('n') ? PASS_YES
: PASS_NO
);
1274 #define P_EXIT 1 /* Make sure process doesn't return. */
1275 #define P_SHADOW 2 /* Always use exec on 68000. */
1277 static void startprocess(void (*proc
)(), char *machine
, char *path
,
1280 char *argv
[10], **argp
= argv
;
1281 char flags
[10], *pfl
= flags
;
1283 if (machine
!= nil
) {
1284 char *u
= machine
, *m
;
1287 if ((m
= strchr(machine
, '@')) != nil
) {
1295 /* Without this check it would run like a pig on an non MMU 68000: */
1296 if (!(USE_SHADOWING
&& p_flags
& P_SHADOW
)) {
1297 if (chdir(path
) < 0) {
1298 if (proc
!= master
|| errno
!= ENOENT
1299 || mkdir(path
, 0700) < 0)
1301 if (chdir(path
) < 0) perrx(path
);
1302 printf("Destination directory %s created\n", path
);
1305 isbackup(proc
== slave
);
1307 if (p_flags
& P_EXIT
) exit(ex
);
1312 if (interact
) *pfl
++ = 'i';
1313 if (install
) *pfl
++ = 'u';
1314 if (force
) *pfl
++ = 'f';
1317 *argp
++ = proc
== slave
? SLAVENAME
: MASTERNAME
;
1321 fprintf(stderr
, "execlp(");
1322 for (argp
= argv
; *argp
!= nil
; argp
++) fprintf(stderr
, "%s, ", *argp
);
1323 fprintf(stderr
, "nil);\n");
1325 execvp(argv
[0], argv
);
1329 void splitcolon(char *path
, char **amach
, char **adir
)
1340 if (*dir
== 0 || *dir
== '/') {
1352 "Usage: %s [-iuf] [[user@]machine:]dir1 [[user@]machine:]dir2\n",
1357 int main(int argc
, char **argv
)
1359 char *s_mach
, *s_dir
;
1360 char *m_mach
, *m_dir
;
1361 int m2s
[2], s2m
[2], m2m
[2];
1362 int s_pid
= 0, m_pid
= 0;
1365 if ((arg0
= strrchr(argv
[0], '/')) == nil
) arg0
= argv
[0]; else arg0
++;
1367 while (argc
>1 && argv
[1][0] == '-') {
1372 case 'i': interact
= 1; break;
1373 case 'u': install
= 1; break;
1374 case 'f': force
= 1; break;
1382 if (argc
!= 3) Usage();
1384 if (strcmp(argv
[1], SLAVENAME
) == 0) {
1386 splitcolon(argv
[2], &s_mach
, &s_dir
);
1387 startprocess(slave
, s_mach
, s_dir
, P_EXIT
);
1389 if (strcmp(argv
[1], MASTERNAME
) == 0) {
1391 splitcolon(argv
[2], &m_mach
, &m_dir
);
1392 startprocess(master
, m_mach
, m_dir
, P_EXIT
);
1395 splitcolon(argv
[1], &s_mach
, &s_dir
);
1396 splitcolon(argv
[2], &m_mach
, &m_dir
);
1398 /* How difficult can plumbing be? */
1399 if (pipe(m2s
) < 0 || pipe(s2m
) < 0) perrx("pipe()");
1401 if (m_mach
== nil
) {
1402 /* synctree [machine:]dir1 dir2 */
1403 switch (s_pid
= fork()) {
1407 dup2(m2s
[0], 0); close(m2s
[0]); close(m2s
[1]);
1408 dup2(s2m
[1], 1); close(s2m
[0]); close(s2m
[1]);
1410 startprocess(slave
, s_mach
, s_dir
, P_EXIT
|P_SHADOW
);
1412 chan
[0]= s2m
[0]; close(s2m
[1]);
1413 chan
[1]= m2s
[1]; close(m2s
[0]);
1414 startprocess(master
, m_mach
, m_dir
, 0);
1416 if (s_mach
== nil
) {
1417 /* synctree dir1 machine:dir2 */
1418 switch (m_pid
= fork()) {
1422 dup2(s2m
[0], 0); close(s2m
[0]); close(s2m
[1]);
1423 dup2(m2s
[1], 1); close(m2s
[0]); close(m2s
[1]);
1425 startprocess(master
, m_mach
, m_dir
, P_EXIT
|P_SHADOW
);
1427 chan
[0]= m2s
[0]; close(m2s
[1]);
1428 chan
[1]= s2m
[1]; close(s2m
[0]);
1429 startprocess(slave
, s_mach
, s_dir
, 0);
1431 /* synctree machine1:dir1 machine2:dir2 */
1432 if (pipe(m2m
) < 0) perrx("pipe");
1434 switch (s_pid
= fork()) {
1438 dup2(m2s
[0], 0); close(m2s
[0]); close(m2s
[1]);
1439 dup2(s2m
[1], 1); close(s2m
[0]); close(s2m
[1]);
1440 close(m2m
[0]); close(m2m
[1]);
1442 startprocess(slave
, s_mach
, s_dir
, P_EXIT
|P_SHADOW
);
1445 switch (m_pid
= fork()) {
1449 dup2(s2m
[0], 0); close(s2m
[0]); close(s2m
[1]);
1450 close(m2s
[0]); close(m2s
[1]);
1451 dup2(m2m
[1], 1); close(m2m
[0]); close(m2m
[1]);
1453 startprocess(master
, m_mach
, m_dir
, P_EXIT
|P_SHADOW
);
1455 close(s2m
[0]); close(s2m
[1]);
1456 chan
[0]= m2m
[0]; close(m2m
[1]);
1457 chan
[1]= m2s
[1]; close(m2s
[0]);
1463 alarm(15); /* Don't wait(2) forever. */
1465 while (s_pid
!= 0 || m_pid
!= 0) {
1466 if ((r
= wait((int *) nil
)) < 0) perrx("wait()");
1467 if (r
== s_pid
) s_pid
= 0;
1468 if (r
== m_pid
) m_pid
= 0;