mkfs, mkproto: minor improvements
[minix.git] / commands / synctree / synctree.c
blob908a5b0e744a57229a8c8eb1dc9e852effdd7830
1 /* synctree 4.16 - Synchronise file tree. Author: Kees J. Bot
2 * 5 Apr 1989
3 * SYNOPSYS
4 * synctree [-iuf] [[user1@machine1:]dir1 [[user2@]machine2:]dir2
6 * Dir2 will then be synchronized to dir1 with respect to the flags.
7 * The flags mean:
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.
23 #define nil 0
24 #include <sys/types.h>
25 #include <stddef.h>
26 #include <stdio.h>
27 #include <sys/stat.h>
28 #include <utime.h>
29 #include <string.h>
30 #include <signal.h>
31 #include <dirent.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <time.h>
37 #include <sys/wait.h>
39 #define USE_SHADOWING 0
41 #ifndef PATH_MAX
43 #define PATH_MAX 1024
44 #endif
46 #ifndef S_ISLNK
47 /* There were no symlinks in medieval times. */
48 #define S_ISLNK(mode) (0)
49 #define lstat stat
50 #define symlink(path1, path2) (errno= ENOSYS, -1)
51 #define readlink(path, buf, len) (errno= ENOSYS, -1)
52 #endif
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. */
91 #ifdef DEBUG
92 char *ORDERS[]= {
93 "ENTER", "ADVANCE", "CAT", "MORE", "CANCEL", "DIE", "DIE_BAD",
94 "POSITIVE", "NEGATIVE", "PASS_YES", "PASS_NO"
96 #endif
98 enum answers {
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. */
108 #ifdef DEBUG
109 char *ANSWERS[]= {
110 "PATH", "LINK", "DATA", "NODATA", "DONE", "SYMLINK", "YES", "NO"
113 #define DPRINTF(format, arg) fprintf(stderr, format, arg0, arg)
114 #else
115 #define DPRINTF(format, arg)
116 #endif
118 struct mode {
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));
132 ex= 1;
135 static void perr(label) char *label;
137 fprintf(stderr, "%s: %s: %s\n", arg0, label, strerror(errno));
138 ex= 1;
141 static void perrx(label) char *label;
143 perr(label);
144 exit(1);
147 #if S_HIDDEN
148 /* Support for per achitecture hidden files. */
149 static int transparent= 0;
151 static void isvisible(name) char *name;
153 char *p= name + strlen(name);
155 while (p > name && *--p == '/') {}
157 if (p > name && *p == '@' && p[-1] != '/') transparent= 1;
159 #else
160 #define transparent 0
161 #define isvisible(name) ((void) 0)
162 #endif
164 static void isbackup(slave) int slave;
166 if ((filemodes= open(BACKUP, slave ? O_RDONLY : O_RDWR)) >= 0)
167 backup= 1;
168 else {
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(p, n) char *p, *n;
183 /* Add a name to the path, return pointer to end. */
185 if (p - path + 1 + strlen(n) > PATH_MAX) {
186 *p= 0;
187 fprintf(stderr, "%s: %s/%s is too long.\n", arg0, path, n);
188 fprintf(stderr, "%s: Unable to continue.\n", arg0);
189 exit(1);
191 if (p == path+1 && path[0] == '.') p= path;
193 if (p > path) *p++ = '/';
195 while (*n != 0) *p++ = *n++;
196 *p= 0;
197 return p;
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(e) struct entry *e;
210 /* Set path leading to e. */
212 static char *pend;
214 if (e == nil)
215 pend= path;
216 else {
217 setpath(e->dir);
218 pend= addpath(pend, e->name);
222 static void sort(ae) 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 */
228 struct entry *e2;
230 e1= *(mid= &(*ae)->next);
231 do {
232 if ((e1= e1->next) == nil) break;
233 mid= &(*mid)->next;
234 } while ((e1= e1->next) != nil);
236 e2= *mid;
237 *mid= nil;
239 if ((*ae)->next != nil) sort(ae);
240 if (e2->next != nil) sort(&e2);
242 e1= *ae;
243 for (;;) {
244 if (strcmp(e1->name, e2->name)<=0) {
245 if ((e1= *(ae= &e1->next)) == nil) {
246 *ae= e2;
247 break;
249 } else {
250 *ae= e2;
251 e2= *(ae= &e2->next);
252 *ae= e1;
253 if (e2 == nil) break;
258 static void enter()
259 /* Collect directory entries of E. */
261 struct entry **last= &E->con, *new;
262 struct dirent *e;
263 DIR *d;
265 if ((d= opendir(path)) == nil) {
266 fprintf(stderr, "%s: Can't read dir %s\n", arg0, path);
267 return;
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)
273 )) continue;
275 new= (struct entry *) malloc(sizeof(*new));
277 new->next= nil;
278 new->dir= E;
279 new->con= nil;
280 new->name= (char *) malloc(strlen(e->d_name) + 1);
281 strcpy(new->name, e->d_name);
282 *last= new;
283 last= &new->next;
285 closedir(d);
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. */
309 } link_t;
310 typedef struct dlink { /* On-disk link store. */
311 dev_t dev; /* Device number. */
312 char file[PATH_MAX]; /* Name of earlier seen link. */
313 } dlink_t;
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;
317 link_t *lp, **plp;
318 size_t len;
319 off_t off;
321 if (file == nil) {
322 /* Reset everything. */
323 for (plp= links; plp < arraylimit(links); plp++) {
324 while ((lp= *plp) != nil) {
325 *plp= lp->next;
326 free(lp);
329 if (tfd != -1) close(tfd);
330 tfd= -1;
331 return nil;
334 /* The file must be a non-directory with more than one link. */
335 if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) {
336 errno= ENOENT;
337 return nil;
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. */
352 *plp= lp->next;
353 free(lp);
354 errno= ENOENT;
355 return nil;
358 /* Return the name of the earlier link. */
359 return dlink.file;
362 plp= &lp->next;
365 /* First time I see this link. Add it to the store. */
366 if (tfd == -1) {
367 for (;;) {
368 char *tmp;
370 tmp= tmpnam(nil);
371 tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600);
372 if (tfd < 0) {
373 if (errno != EEXIST) return nil;
374 } else {
375 (void) unlink(tmp);
376 break;
380 if ((len= strlen(file)) >= PATH_MAX) {
381 errno= E2BIG;
382 return nil;
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;
392 lp->next= nil;
393 lp->ino= stp->st_ino;
394 lp->off= off;
395 *plp= lp;
396 errno= ENOENT;
397 return nil;
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.
409 char *name;
411 name= link_islink(&st, path);
412 if (name == nil && errno != ENOENT) perrx(path);
413 return name;
416 static void setstat(ino, stp) ino_t ino; struct stat *stp;
417 /* Set backup status info, we know that backup is true. */
419 struct mode md;
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)
429 ) perrx(BACKUP);
432 static int getstat(name, stp) char *name; struct stat *stp;
433 /* Get status information of file name, skipping some files. Backup info
434 * is inserted as needed.
437 errno= 0;
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;
445 if (backup) {
446 struct mode md;
448 if (lseek(filemodes,
449 (off_t) sizeof(md) * (off_t) stp->st_ino, 0) == -1
450 || read(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
451 || md.md_mode == 0
453 errno= 0;
454 setstat(stp->st_ino, stp);
455 } else {
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;
464 return 0;
467 static int advance()
468 /* Determine next pathname, return true on success. */
470 for (;;) {
471 if (E==nil) { /* First call, enter root dir. */
472 E= (struct entry *) malloc(sizeof(*E));
473 E->dir= nil;
474 E->con= nil;
475 E->next= nil;
476 E->name= (char *) malloc(3);
477 strcpy(E->name, transparent ? ".@" : ".");
478 } else
479 if (E->con != nil) /* Dir's files must be processed. */
480 E= E->con;
481 else {
482 for (;;) {
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;
489 E= E->next;
490 free(junk->name);
491 free(junk);
493 if (E != nil) break;
495 if ((E= parent) == nil) return 0;
498 setpath(E);
499 if (getstat(path, &st) == 0) {
500 if (S_ISLNK(st.st_mode)) {
501 int n;
503 if ((n= readlink(path, lnkpth, PATH_MAX)) >= 0)
505 lnkpth[n]= 0;
506 break;
508 } else {
509 break;
512 if (errno != 0 && errno != ENOENT) perr(path);
515 linkpath= islink();
516 DPRINTF("%s: path = %s\n", path);
517 return 1;
520 static enum orders request()
521 /* Slave reads command sent by master. */
523 static char buf[64], *bp;
524 static int n= 0;
525 int req;
527 for (;;) {
528 if (n == 0) {
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. */
532 fprintf(stderr,
533 "%s: Master died prematurely.\n", arg0);
534 exit(1);
536 bp= buf;
538 req= *bp++ & 0xFF;
539 n--;
540 if (req >= (int) ENTER) break;
542 /* Master using slave to print to stdout: */
543 putchar(req);
545 DPRINTF("%s: request() == %s\n", ORDERS[req - (int) ENTER]);
547 return (enum orders) req;
550 static void report()
552 int r;
554 DPRINTF("%s: reporting now!\n", 0);
556 buckp= bucket;
558 while (buckn > 0) {
559 r = buckn;
560 if (r > (512 << sizeof(char*))) r= (512 << sizeof(char*));
562 if ((r= write(chan[1], buckp, r)) < 0) perrx("report()");
564 buckp += r;
565 buckn -= r;
567 buckp= bucket;
568 buckn= 0;
571 static void inform(a) enum answers a;
572 /* Slave replies to master. */
574 DPRINTF("%s: inform(%s)\n", ANSWERS[(int) a - (int) PATH]);
576 *buckp++ = (int) a;
577 buckn++;
580 #define wwrite(buf, n) (memcpy(buckp, (buf), (n)), buckp+= (n), buckn+= (n))
582 static void sendnum(n) long n;
583 /* Send number from least to most significant byte. */
585 #if BYTE_ORDER == LITTLE_ENDIAN
586 wwrite((char *) &n, sizeof(n));
587 #else
588 char buf[NUMBYTES];
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));
595 #endif
598 static void send(buf, n) char *buf; int n;
599 /* Slave sends size and contents of buf. */
601 sendnum((long) n);
602 if (n > 0) wwrite(buf, (size_t) n);
605 static void sendstat(stp) 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);
615 static int ask();
617 static void slave()
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.
622 int f, n;
623 char buf[CHUNK];
624 enum { run, done, die } state= run;
626 do {
627 switch (request()) {
628 case ENTER:
629 enter();
630 break;
631 case ADVANCE:
632 if (!advance() || state == done) {
633 inform(DONE);
634 state= done;
635 } else {
636 if (linkpath!=nil) {
637 inform(LINK);
638 send(linkpath, strlen(linkpath) + 1);
639 } else
640 if (S_ISLNK(st.st_mode)) {
641 inform(SYMLINK);
642 send(lnkpth, strlen(lnkpth) + 1);
643 } else {
644 inform(PATH);
646 send(path, strlen(path) + 1);
647 sendstat(&st);
649 break;
650 case CAT:
651 if ((f= open(path, O_RDONLY))<0) {
652 fprintf(stderr, "%s: Can't open %s",
653 arg0, path);
654 because();
655 inform(NODATA);
656 break;
658 inform(DATA);
659 do {
660 n= read(f, buf, sizeof(buf));
661 if (n < 0) perr(path);
662 send(buf, n);
663 if (n > 0) report();
664 } while (n > 0);
665 close(f);
666 break;
667 case ORDER_CANCEL:
668 cancellink();
669 break;
670 case DIE_BAD:
671 ex= 1;
672 /*FALL THROUGH*/
673 case DIE:
674 state= die;
675 break;
676 case POSITIVE:
677 inform(ask('y') ? YES : NO);
678 break;
679 case NEGATIVE:
680 inform(ask('n') ? YES : NO);
681 break;
682 case PASS_YES:
683 inform(YES);
684 break;
685 case PASS_NO:
686 inform(NO);
687 break;
688 default:
689 fprintf(stderr, "%s: strange request\n", arg0);
690 exit(1);
692 report();
693 } while (state != die);
696 static int execute(argv) char **argv;
697 /* Execute a command and return its success or failure. */
699 int pid, r, status;
701 if ((pid= fork())<0) {
702 perr("fork()");
703 return 0;
705 if (pid == 0) {
706 execvp(argv[0], argv);
707 perrx(argv[0]);
709 while ((r= wait(&status)) != pid) {
710 if (r < 0) {
711 perr(argv[0]);
712 return 0;
715 return status == 0;
718 static int removedir(dir) char *dir;
719 /* Remove a directory and its contents. */
721 static char *argv[] = { "rm", "-r", nil, nil };
723 printf("(rm -r %s)\n", dir);
725 argv[2]= dir;
726 return execute(argv);
729 static void order(o) enum orders o;
730 /* Master tells slave what to do. */
732 char c= (char) o;
734 DPRINTF("%s: order(%s)\n", ORDERS[o - (int) ENTER]);
736 if (write(chan[1], &c, 1) != 1) perrx("order()");
739 static void rread(buf, n) char *buf; int n;
740 /* Master gets buf of size n from slave, doing multiple reads if needed. */
742 int r;
744 while (n > 0) {
745 if (buckn == 0) {
746 switch (buckn= read(chan[0], bucket, BUCKSIZE)) {
747 case -1:
748 perrx("reply channel from slave");
749 case 0:
750 fprintf(stderr,
751 "%s: slave died prematurely.\n",
752 arg0);
753 exit(1);
755 buckp= bucket;
757 r= n < buckn ? n : buckn;
758 memcpy(buf, buckp, r);
759 buckp+= r;
760 buckn-= r;
761 buf+= r;
762 n-= r;
766 static enum answers answer()
767 /* Master reads slave's reply. */
769 char c;
770 int a;
772 rread(&c, 1);
773 a= c & 0xFF;
775 DPRINTF("%s: answer() == %s\n", ANSWERS[a - (int) PATH]);
777 return (enum answers) a;
780 static long recnum()
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
786 long n;
788 rread((char *) &n, (int) sizeof(n));
789 return n;
790 #else
791 unsigned char buf[NUMBYTES];
793 rread(buf, sizeof(buf));
794 return buf[0]
795 | ((unsigned) buf[1] << 8)
796 | ((unsigned long) buf[2] << 16)
797 | ((unsigned long) buf[3] << 24);
798 #endif
801 static int receive(buf, max) char *buf; int max;
802 /* Master get's data from slave, by first reading size, then data. */
804 int n;
806 n= recnum();
807 if (n > max) {
808 fprintf(stderr, "%s: panic: Can't read %d bytes\n", arg0, n);
809 exit(1);
811 if (n > 0) rread(buf, n);
812 return n;
815 static void recstat(stp) 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();
825 static int key()
827 int c;
828 static int tty= -1;
830 if (tty < 0) tty= isatty(0);
832 if (feof(stdin) || (c= getchar()) == EOF) {
833 c= '\n';
834 if (tty) putchar('\n');
837 if (!tty) putchar(c);
839 return c;
842 static int ask(def) int def;
843 /* Ask for a yes or no, anything else means choose def. */
845 int y, c;
847 if (chan[0] == 0) {
848 /* I'm running remote, tell the slave to ask. */
849 fflush(stdout);
850 order(def == 'y' ? POSITIVE : NEGATIVE);
851 return answer() == YES;
854 printf("? (%c) ", def);
855 fflush(stdout);
857 do c= key(); while (c == ' ' || c == '\t');
859 y= c;
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(silent) int silent;
870 struct stat st;
871 int change= 0;
872 struct utimbuf tms;
874 errno= 0;
875 getstat(Spath, &st);
876 if (backup && silent) {
877 setstat(st.st_ino, &Sst);
878 getstat(Spath, &st);
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);
885 change= 1;
887 if (errno == 0
888 && (st.st_uid != Sst.st_uid || st.st_gid != Sst.st_gid)
889 && (backup || geteuid() == 0)
891 errno= 0;
892 if (!backup) chown(Spath, Sst.st_uid, Sst.st_gid);
893 change= 1;
896 if (backup && !silent) setstat(st.st_ino, &Sst);
898 if (errno == 0 && S_ISREG(Sst.st_mode) && st.st_mtime != Sst.st_mtime) {
899 time(&tms.actime);
900 tms.modtime= Sst.st_mtime;
901 errno= 0;
902 utime(Spath, &tms);
903 change= 1;
905 if (errno != 0) {
906 fprintf(stderr, "%s: Can't set modes of %s", arg0, Spath);
907 because();
908 } else
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) {
920 fprintf(stderr,
921 "%s: can't make %s look old", arg0, Spath);
922 because();
924 } else {
925 fprintf(stderr, "%s: made %s look old.\n", arg0, Spath);
929 static int busy= 0;
931 static void bail_out(sig) int sig;
933 signal(sig, SIG_IGN);
935 fprintf(stderr, "%s: Exiting after signal %d\n", arg0, sig);
937 if (busy) {
938 fprintf(stderr, "%s: was installing %s\n", arg0, Spath);
939 makeold();
941 order(DIE_BAD);
943 exit(sig);
946 static int makenode(name, mode, addr, size)
947 char *name; int mode; dev_t addr; off_t size;
949 int r;
951 if (!backup) {
952 r= mknod(name, mode, addr);
953 } else {
954 if ((r= creat(name, 0644)) >= 0) close(r);
956 return r;
959 static void add(update) int update;
960 /* Add Spath to the filesystem. */
962 int f, n;
963 char buf[CHUNK];
964 int forced_update= force && update;
966 if (Slinkpath != nil && !S_ISLNK(Sst.st_mode)) {
967 if (interact && !update) {
968 printf("Link %s to %s", Spath, Slinkpath);
969 if (!ask('n')) return;
971 if (link(Slinkpath, Spath) >= 0) {
972 printf("Linked %s to %s\n", Spath, Slinkpath);
973 return;
974 } else {
975 fprintf(stderr,
976 "%s: Can't link %s to %s",
977 arg0, Slinkpath, Spath);
978 because();
979 /* Try to install instead. */
982 switch (Sst.st_mode & S_IFMT) {
983 case S_IFDIR:
984 if (!force) {
985 printf("Add dir %s", Spath);
986 if (!ask('n')) return;
988 if (mkdir(Spath, backup ? 0755 : Sst.st_mode) < 0) {
989 perr(Spath);
990 return;
992 printf("Directory %s created.\n", Spath);
993 order(ENTER);
994 break;
995 case S_IFBLK:
996 case S_IFCHR:
997 case S_IFIFO:
998 if (interact && !update) {
999 printf("Create special file %s", Spath);
1000 if (!ask('n')) { order(ORDER_CANCEL); return; }
1002 if (makenode(Spath, Sst.st_mode, Sst.st_rdev, Sst.st_size)<0) {
1003 fprintf(stderr,
1004 "%s: Can't create special file %s",
1005 arg0, Spath);
1006 because();
1007 return;
1009 printf("Special file %s created\n", Spath);
1010 break;
1011 #ifdef S_IFLNK
1012 case S_IFLNK:
1013 if (interact && !update) {
1014 printf("Install %s -> %s", Spath, Slnkpth);
1015 if (!ask('n')) { order(ORDER_CANCEL); return; }
1017 if (symlink(Slnkpth, Spath) < 0) {
1018 fprintf(stderr, "%s: Can't create symlink %s",
1019 arg0, Spath);
1020 because();
1021 return;
1023 printf("%s %s -> %s\n",
1024 forced_update ? "Updated: " : "Installed:",
1025 Spath, Slnkpth);
1026 break;
1027 #endif
1028 case S_IFREG:
1029 if (interact && !update) {
1030 printf("Install %s", Spath);
1031 if (!ask('n')) { order(ORDER_CANCEL); return; }
1033 order(CAT);
1034 if (answer() != DATA) return;
1036 busy= 1;
1037 if ((f= creat(Spath, backup ? 0644 : Sst.st_mode&07777)) < 0) {
1038 busy= 0;
1039 fprintf(stderr, "%s: Can't create %s", arg0, Spath);
1040 because();
1043 while ((n= receive(buf, sizeof(buf)))>0) {
1044 if (f >= 0 && write(f, buf, n) != n) {
1045 fprintf(stderr, "%s: Write error on %s",
1046 arg0, Spath);
1047 because();
1048 close(f); f= -1;
1051 if (n < 0) {
1052 fprintf(stderr, "%s: Slave read err on %s\n",
1053 arg0, Spath);
1055 if (f >= 0) close(f);
1056 if (n < 0 || f < 0) { makeold(); busy= 0; return; }
1057 busy= 0;
1058 printf("%s %s\n",
1059 forced_update ?
1060 Sst.st_mtime < st.st_mtime ? "Restored: " :
1061 "Updated: " :
1062 "Installed:",
1063 Spath
1065 break;
1066 default:
1067 fprintf(stderr,
1068 "%s: Won't add file with strange mode %05o: %s\n",
1069 arg0, Sst.st_mode, Spath);
1070 order(ORDER_CANCEL);
1071 return;
1073 setmodes(1);
1076 static int delete(update) int update;
1077 /* Delete path. */
1079 int forced_update= force && update;
1081 if (S_ISDIR(st.st_mode)) {
1082 if (install) return 0;
1083 if (!force) {
1084 printf("Delete dir %s", path);
1085 if (!ask('n')) return 0;
1087 if (!removedir(path)) { ex= 1; return 0; }
1088 if (!forced_update) printf("Directory %s deleted.\n", path);
1089 return 1;
1092 if (install && !update) return 0;
1094 if (!force) {
1095 printf("Delete %s", path);
1096 if (!ask((interact && !update) ? 'n' : 'y')) return 0;
1099 if (unlink(path)<0) {
1100 fprintf(stderr, "Can't delete %s", path);
1101 because();
1102 return 0;
1104 cancellink();
1105 if (!forced_update) printf("Deleted: %s\n", path);
1106 return 1;
1109 static int different()
1110 /* Return true iff path and Spath are different. */
1112 if (! ( (linkpath == nil && Slinkpath == nil)
1113 || (linkpath != nil && Slinkpath != nil
1114 && strcmp(linkpath, Slinkpath) == 0)
1115 )) {
1116 linkpath= Slinkpath;
1117 return 1;
1120 if ((st.st_mode & S_IFMT) != (Sst.st_mode & S_IFMT)) return 1;
1122 switch (st.st_mode & S_IFMT) {
1123 case S_IFDIR:
1124 return 0;
1125 case S_IFBLK:
1126 case S_IFCHR:
1127 return st.st_rdev != Sst.st_rdev;
1128 case S_IFREG:
1129 if (install) return Sst.st_mtime > st.st_mtime;
1130 return st.st_size != Sst.st_size
1131 || st.st_mtime != Sst.st_mtime;
1132 case S_IFIFO: return 0;
1133 #ifdef S_IFLNK
1134 case S_IFLNK: return strcmp(lnkpth, Slnkpth) != 0;
1135 #endif
1136 default: return 1;
1140 static void compare()
1141 /* See if path and Spath are same. */
1143 if (different()) {
1144 if (!force) {
1145 printf("%sing %s (delete + add)\n",
1146 Sst.st_mtime < st.st_mtime ? "Restor" : "Updat",
1147 path);
1149 if (delete(1)) add(1);
1150 } else {
1151 if (!install) setmodes(0);
1153 if (S_ISDIR(st.st_mode)) {
1154 order(ENTER);
1155 enter();
1160 static int done= 0, Sdone= 0;
1162 static enum action { ADD, COMPARE, DELETE } action()
1163 /* Look at path's of master and slave, compare them alphabetically to see
1164 * who is ahead of who, then tell what is to be done.
1167 int c;
1168 char *Sp, *p;
1170 if (done) return ADD; /* Slave still has names. */
1171 if (Sdone) return DELETE; /* Master has too many names. */
1173 /* Compare paths. Let "a/a" come before "a.a". */
1174 Sp= Spath;
1175 p= path;
1176 while (*Sp == *p && *Sp != 0) { Sp++; p++; }
1177 if (*Sp == '/') return ADD;
1178 if (*p == '/') return DELETE;
1179 return (c= strcmp(Sp, p)) == 0 ? COMPARE : c < 0 ? ADD : DELETE;
1182 static void master()
1183 /* Synchronise file tree to that of its slave. */
1185 enum action a= COMPARE; /* Trick first advances. */
1187 umask(backup ? 0022 : 0000);
1189 signal(SIGPIPE, SIG_IGN);
1190 signal(SIGHUP, bail_out);
1191 signal(SIGINT, bail_out);
1192 signal(SIGTERM, bail_out);
1194 while (!done || !Sdone) {
1195 if (!Sdone && (a == ADD || a == COMPARE)) {
1196 /* Slave advances. */
1197 order(ADVANCE);
1198 switch (answer()) {
1199 case PATH:
1200 Slinkpath= nil;
1201 receive(Spath, sizeof(Spath));
1202 recstat(&Sst);
1203 break;
1204 case LINK:
1205 receive(Slnkpth, sizeof(Slnkpth));
1206 Slinkpath= Slnkpth;
1207 receive(Spath, sizeof(Spath));
1208 recstat(&Sst);
1209 break;
1210 case SYMLINK:
1211 Slinkpath= nil;
1212 receive(Slnkpth, sizeof(Slnkpth));
1213 receive(Spath, sizeof(Spath));
1214 recstat(&Sst);
1215 break;
1216 case DONE:
1217 Sdone= 1;
1218 break;
1219 default:
1220 fprintf(stderr,
1221 "%s: Strange answer from slave.\n",
1222 arg0);
1223 exit(1);
1226 if (!done && (a == COMPARE || a == DELETE)) {
1227 /* Master advances. */
1228 if (!advance()) done= 1;
1231 if (done && Sdone) break;
1233 switch (a= action()) {
1234 case ADD: /* Spath exists, path doesn't, add? */
1235 add(0);
1236 break;
1237 case COMPARE: /* They both exist, are they the same? */
1238 compare();
1239 break;
1240 case DELETE: /* path exists, Spath doesn't, delete? */
1241 delete(0);
1243 fflush(stdout); /* Don't keep user in suspense. */
1245 order(ex == 0 ? DIE : DIE_BAD);
1248 static void mediator()
1249 /* Sits at the local machine and passes orders from master to slave, both
1250 * on remote machines. Only diagnostics and questions are handled.
1253 enum orders req;
1255 for (;;) {
1256 switch (req= request()) {
1257 case DIE_BAD:
1258 ex= 1;
1259 /*FALL THROUGH*/
1260 case DIE:
1261 order(DIE);
1262 return;
1263 case POSITIVE:
1264 order(ask('y') ? PASS_YES : PASS_NO);
1265 break;
1266 case NEGATIVE:
1267 order(ask('n') ? PASS_YES : PASS_NO);
1268 break;
1269 default:
1270 order(req);
1275 #define P_EXIT 1 /* Make sure process doesn't return. */
1276 #define P_SHADOW 2 /* Always use exec on 68000. */
1278 static void startprocess(proc, machine, path, p_flags)
1279 void (*proc)(); char *machine, *path; int p_flags;
1281 char *argv[10], **argp= argv;
1282 char flags[10], *pfl= flags;
1284 if (machine != nil) {
1285 char *u= machine, *m;
1287 *argp++ = "rsh";
1288 if ((m= strchr(machine, '@')) != nil) {
1289 *m++ = 0;
1290 *argp++ = "-l";
1291 *argp++ = u;
1292 machine= m;
1294 *argp++ = machine;
1295 } else
1296 /* Without this check it would run like a pig on an non MMU 68000: */
1297 if (!(USE_SHADOWING && p_flags & P_SHADOW)) {
1298 if (chdir(path) < 0) {
1299 if (proc != master || errno != ENOENT
1300 || mkdir(path, 0700) < 0)
1301 perrx(path);
1302 if (chdir(path) < 0) perrx(path);
1303 printf("Destination directory %s created\n", path);
1305 isvisible(path);
1306 isbackup(proc == slave);
1307 (*proc)();
1308 if (p_flags & P_EXIT) exit(ex);
1309 return;
1311 *argp++ = SYNCNAME;
1312 *pfl++ = '-';
1313 if (interact) *pfl++ = 'i';
1314 if (install) *pfl++ = 'u';
1315 if (force) *pfl++ = 'f';
1316 *pfl= 0;
1317 *argp++ = flags;
1318 *argp++ = proc == slave ? SLAVENAME : MASTERNAME;
1319 *argp++ = path;
1320 *argp++ = nil;
1321 #ifdef DEBUG
1322 fprintf(stderr, "execlp(");
1323 for (argp= argv; *argp != nil; argp++) fprintf(stderr, "%s, ", *argp);
1324 fprintf(stderr, "nil);\n");
1325 #endif
1326 execvp(argv[0], argv);
1327 perrx(argv[0]);
1330 void splitcolon(path, amach, adir) char *path, **amach, **adir;
1332 char *dir= path;
1334 for (;;) {
1335 if (*dir == ':') {
1336 *dir++ = 0;
1337 *amach= path;
1338 *adir= dir;
1339 break;
1341 if (*dir == 0 || *dir == '/') {
1342 *amach= nil;
1343 *adir= path;
1344 break;
1346 dir++;
1350 static void Usage()
1352 fprintf(stderr,
1353 "Usage: %s [-iuf] [[user@]machine:]dir1 [[user@]machine:]dir2\n",
1354 arg0);
1355 exit(1);
1358 main(argc, argv) int argc; char **argv;
1360 char *s_mach, *s_dir;
1361 char *m_mach, *m_dir;
1362 int m2s[2], s2m[2], m2m[2];
1363 int s_pid= 0, m_pid= 0;
1364 int r;
1366 if ((arg0= strrchr(argv[0], '/')) == nil) arg0= argv[0]; else arg0++;
1368 while (argc>1 && argv[1][0] == '-') {
1369 char *f= argv[1]+1;
1371 while (*f != 0) {
1372 switch (*f++) {
1373 case 'i': interact= 1; break;
1374 case 'u': install= 1; break;
1375 case 'f': force= 1; break;
1376 default: Usage();
1379 argc--;
1380 argv++;
1383 if (argc != 3) Usage();
1385 if (strcmp(argv[1], SLAVENAME) == 0) {
1386 arg0= "Slave";
1387 splitcolon(argv[2], &s_mach, &s_dir);
1388 startprocess(slave, s_mach, s_dir, P_EXIT);
1389 } else
1390 if (strcmp(argv[1], MASTERNAME) == 0) {
1391 arg0= "Master";
1392 splitcolon(argv[2], &m_mach, &m_dir);
1393 startprocess(master, m_mach, m_dir, P_EXIT);
1396 splitcolon(argv[1], &s_mach, &s_dir);
1397 splitcolon(argv[2], &m_mach, &m_dir);
1399 /* How difficult can plumbing be? */
1400 if (pipe(m2s) < 0 || pipe(s2m) < 0) perrx("pipe()");
1402 if (m_mach == nil) {
1403 /* synctree [machine:]dir1 dir2 */
1404 switch (s_pid= fork()) {
1405 case -1:
1406 perrx("fork()");
1407 case 0:
1408 dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
1409 dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
1410 arg0= "Slave";
1411 startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
1413 chan[0]= s2m[0]; close(s2m[1]);
1414 chan[1]= m2s[1]; close(m2s[0]);
1415 startprocess(master, m_mach, m_dir, 0);
1416 } else
1417 if (s_mach == nil) {
1418 /* synctree dir1 machine:dir2 */
1419 switch (m_pid= fork()) {
1420 case -1:
1421 perrx("fork()");
1422 case 0:
1423 dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
1424 dup2(m2s[1], 1); close(m2s[0]); close(m2s[1]);
1425 arg0= "Master";
1426 startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
1428 chan[0]= m2s[0]; close(m2s[1]);
1429 chan[1]= s2m[1]; close(s2m[0]);
1430 startprocess(slave, s_mach, s_dir, 0);
1431 } else {
1432 /* synctree machine1:dir1 machine2:dir2 */
1433 if (pipe(m2m) < 0) perrx(pipe);
1435 switch (s_pid= fork()) {
1436 case -1:
1437 perrx("fork()");
1438 case 0:
1439 dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
1440 dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
1441 close(m2m[0]); close(m2m[1]);
1442 arg0= "Slave";
1443 startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
1446 switch (m_pid= fork()) {
1447 case -1:
1448 perrx("fork()");
1449 case 0:
1450 dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
1451 close(m2s[0]); close(m2s[1]);
1452 dup2(m2m[1], 1); close(m2m[0]); close(m2m[1]);
1453 arg0= "Master";
1454 startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
1456 close(s2m[0]); close(s2m[1]);
1457 chan[0]= m2m[0]; close(m2m[1]);
1458 chan[1]= m2s[1]; close(m2s[0]);
1459 mediator();
1461 close(chan[0]);
1462 close(chan[1]);
1464 alarm(15); /* Don't wait(2) forever. */
1466 while (s_pid != 0 || m_pid != 0) {
1467 if ((r= wait((int *) nil)) < 0) perrx("wait()");
1468 if (r == s_pid) s_pid= 0;
1469 if (r == m_pid) m_pid= 0;
1471 exit(ex);