source: trunk/minix/commands/simple/synctree.c@ 15

Last change on this file since 15 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

File size: 32.2 KB
Line 
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
5 *
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'.
11 *
12 * Hitting return lets synctree use its proposed answer. Hitting CTRL-D is
13 * like typing return to all questions that follow.
14 *
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.
18 *
19 * 89/04/05, Kees J. Bot - Birth of tree synchronizing program.
20 * 92/02/02 - General overhaul, rcp(1) like syntax.
21 */
22
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>
38
39#if _MINIX
40#include "limits.h"
41#include "minix/config.h"
42
43/*#define BLOCK_SIZE 1024*/
44#define LITTLE_ENDIAN (CHIP == INTEL)
45#define USE_SHADOWING (CHIP == M68000)
46#else
47#define LITTLE_ENDIAN 0
48#define USE_SHADOWING 0
49#endif
50
51#ifndef PATH_MAX
52#define PATH_MAX 1024
53#endif
54
55#ifndef S_ISLNK
56/* There were no symlinks in medieval times. */
57#define S_ISLNK(mode) (0)
58#define lstat stat
59#define symlink(path1, path2) (errno= ENOSYS, -1)
60#define readlink(path, buf, len) (errno= ENOSYS, -1)
61#endif
62
63#define NUMBYTES 4 /* Any number fits in this many bytes. */
64#define CHUNK 4096 /* Transfer files in this size chunks. */
65
66static int install= 0; /* Install files, do not delete, update if newer. */
67static int interact= 0; /* Ask permission to install too. */
68static int force= 0; /* Force trees to be completely equal. */
69static int backup= 0; /* This tree is for backup. */
70
71static char SYNCNAME[] = "synctree";
72static char SLAVENAME[] = "==SLAVE==";
73static char MASTERNAME[]= "==MASTER==";
74
75
76static char BACKUP[] = ".backup"; /* Backup filemodes. */
77static int filemodes; /* Filemodes fildes. */
78
79static int chan[2]= { 0, 1 }; /* In and output channel to opposite side. */
80
81#define BUCKSIZE (1+NUMBYTES+CHUNK)
82static char bucket[BUCKSIZE]; /* Put a lot of things here before sending. */
83static char *buckp= bucket; /* Fill pointer. */
84static int buckn= 0; /* # bytes in bucket. */
85
86enum orders { /* What back breaking labour should the slave perform? */
87 ENTER= 128, /* Make ready to process contents of directory. */
88 ADVANCE, /* Determine next pathname and report it back. */
89 CAT, /* Send contents of file. */
90 MORE, /* Send more file contents. */
91 CANCEL, /* Current pathname is not installed, remove as link. */
92 DIE, /* Die with exit(0); */
93 DIE_BAD, /* exit(1); */
94 POSITIVE, /* Ask a yes/no question expecting yes. */
95 NEGATIVE, /* Same expecting no. */
96 PASS_YES, /* Pass this to the master will you. */
97 PASS_NO /* Same here. */
98};
99
100#ifdef DEBUG
101char *ORDERS[]= {
102 "ENTER", "ADVANCE", "CAT", "MORE", "CANCEL", "DIE", "DIE_BAD",
103 "POSITIVE", "NEGATIVE", "PASS_YES", "PASS_NO"
104};
105#endif
106
107enum answers {
108 PATH= 128, /* Report pathname, and stat(2) info. */
109 LINK, /* Report linkname, pathname, and stat(2) info. */
110 DATA, /* Contents of file follow. */
111 NODATA, /* Can't read file. */
112 DONE, /* Nothing more to advance to. */
113 SYMLINK, /* Report symlinkname, pathname, and stat(2) info. */
114 YES, NO /* Result of an ASK. */
115};
116
117#ifdef DEBUG
118char *ANSWERS[]= {
119 "PATH", "LINK", "DATA", "NODATA", "DONE", "SYMLINK", "YES", "NO"
120};
121
122#define DPRINTF(format, arg) fprintf(stderr, format, arg0, arg)
123#else
124#define DPRINTF(format, arg)
125#endif
126
127struct mode {
128 unsigned short md_mode;
129 unsigned short md_uid;
130 unsigned short md_gid;
131 unsigned short md_rdev;
132 unsigned short md_devsiz;
133};
134
135static char *arg0; /* basename(argv[0]) */
136static int ex= 0; /* exit status. */
137
138static void because()
139{
140 fprintf(stderr, ": %s\n", strerror(errno));
141 ex= 1;
142}
143
144static void perr(label) char *label;
145{
146 fprintf(stderr, "%s: %s: %s\n", arg0, label, strerror(errno));
147 ex= 1;
148}
149
150static void perrx(label) char *label;
151{
152 perr(label);
153 exit(1);
154}
155
156#if S_HIDDEN
157/* Support for per achitecture hidden files. */
158static int transparent= 0;
159
160static void isvisible(name) char *name;
161{
162 char *p= name + strlen(name);
163
164 while (p > name && *--p == '/') {}
165
166 if (p > name && *p == '@' && p[-1] != '/') transparent= 1;
167}
168#else
169#define transparent 0
170#define isvisible(name) ((void) 0)
171#endif
172
173static void isbackup(slave) int slave;
174{
175 if ((filemodes= open(BACKUP, slave ? O_RDONLY : O_RDWR)) >= 0)
176 backup= 1;
177 else {
178 if (errno != ENOENT) perrx(BACKUP);
179 }
180}
181
182static char path[PATH_MAX+1]; /* Holds pathname of file being worked on. */
183static char lnkpth[PATH_MAX+1]; /* (Sym)link to path. */
184static char *linkpath; /* What path is, or should be linked to. */
185static struct stat st; /* Corresponding stat(2) info. */
186static char Spath[PATH_MAX+1]; /* Slave is looking at this. */
187static char Slnkpth[PATH_MAX+1];/* (Sym)link to Spath. */
188static char *Slinkpath=nil; /* Either nil or Slnkpth. */
189static struct stat Sst; /* Slave's stat(2). */
190
191static char *addpath(p, n) char *p, *n;
192/* Add a name to the path, return pointer to end. */
193{
194 if (p - path + 1 + strlen(n) > PATH_MAX) {
195 *p= 0;
196 fprintf(stderr, "%s: %s/%s is too long.\n", arg0, path, n);
197 fprintf(stderr, "%s: Unable to continue.\n", arg0);
198 exit(1);
199 }
200 if (p == path+1 && path[0] == '.') p= path;
201
202 if (p > path) *p++ = '/';
203
204 while (*n != 0) *p++ = *n++;
205 *p= 0;
206 return p;
207}
208
209struct entry { /* A directory entry. */
210 struct entry *next; /* Next entry in same directory */
211 struct entry *dir; /* It is part of this directory */
212 struct entry *con; /* If a dir, its contents */
213 char *name; /* Name of this dir entry */
214};
215
216static struct entry *E= nil; /* File being processed. */
217
218static void setpath(e) struct entry *e;
219/* Set path leading to e. */
220{
221 static char *pend;
222
223 if (e == nil)
224 pend= path;
225 else {
226 setpath(e->dir);
227 pend= addpath(pend, e->name);
228 }
229}
230
231static void sort(ae) struct entry **ae;
232/* This is either a stable mergesort, or thermal noise, I'm no longer sure.
233 * It must be called like this: if (L!=nil && L->next!=nil) sort(&L);
234 */
235{
236 /* static */ struct entry *e1, **mid; /* Need not be local */
237 struct entry *e2;
238
239 e1= *(mid= &(*ae)->next);
240 do {
241 if ((e1= e1->next) == nil) break;
242 mid= &(*mid)->next;
243 } while ((e1= e1->next) != nil);
244
245 e2= *mid;
246 *mid= nil;
247
248 if ((*ae)->next != nil) sort(ae);
249 if (e2->next != nil) sort(&e2);
250
251 e1= *ae;
252 for (;;) {
253 if (strcmp(e1->name, e2->name)<=0) {
254 if ((e1= *(ae= &e1->next)) == nil) {
255 *ae= e2;
256 break;
257 }
258 } else {
259 *ae= e2;
260 e2= *(ae= &e2->next);
261 *ae= e1;
262 if (e2 == nil) break;
263 }
264 }
265}
266
267static void enter()
268/* Collect directory entries of E. */
269{
270 struct entry **last= &E->con, *new;
271 struct dirent *e;
272 DIR *d;
273
274 if ((d= opendir(path)) == nil) {
275 fprintf(stderr, "%s: Can't read dir %s\n", arg0, path);
276 return;
277 }
278
279 while ((e= readdir(d)) != nil) {
280 if (e->d_name[0] == '.' && (e->d_name[1] == 0
281 || (e->d_name[1] == '.' && e->d_name[2] == 0)
282 )) continue;
283
284 new= (struct entry *) malloc(sizeof(*new));
285
286 new->next= nil;
287 new->dir= E;
288 new->con= nil;
289 new->name= (char *) malloc(strlen(e->d_name) + 1);
290 strcpy(new->name, e->d_name);
291 *last= new;
292 last= &new->next;
293 }
294 closedir(d);
295 if (E->con != nil && E->con->next != nil) sort(&E->con);
296}
297
298#define arraysize(a) (sizeof(a) / sizeof((a)[0]))
299#define arraylimit(a) ((a) + arraysize(a))
300
301static char *link_islink(struct stat *stp, const char *file)
302{
303 /* Tell if a file, which stat(2) information in '*stp', has been seen
304 * earlier by this function under a different name. If not return a
305 * null pointer with errno set to ENOENT, otherwise return the name of
306 * the link. Return a null pointer with an error code in errno for any
307 * error, using E2BIG for a too long file name.
308 *
309 * Use link_islink(nil, nil) to reset all bookkeeping.
310 *
311 * Call for a file twice to delete it from the store.
312 */
313
314 typedef struct link { /* In-memory link store. */
315 struct link *next; /* Hash chain on inode number. */
316 ino_t ino; /* File's inode number. */
317 off_t off; /* Offset to more info in temp file. */
318 } link_t;
319 typedef struct dlink { /* On-disk link store. */
320 dev_t dev; /* Device number. */
321 char file[PATH_MAX]; /* Name of earlier seen link. */
322 } dlink_t;
323 static link_t *links[256]; /* Hash list of known links. */
324 static int tfd= -1; /* Temp file for file name storage. */
325 static dlink_t dlink;
326 link_t *lp, **plp;
327 size_t len;
328 off_t off;
329
330 if (file == nil) {
331 /* Reset everything. */
332 for (plp= links; plp < arraylimit(links); plp++) {
333 while ((lp= *plp) != nil) {
334 *plp= lp->next;
335 free(lp);
336 }
337 }
338 if (tfd != -1) close(tfd);
339 tfd= -1;
340 return nil;
341 }
342
343 /* The file must be a non-directory with more than one link. */
344 if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) {
345 errno= ENOENT;
346 return nil;
347 }
348
349 plp= &links[stp->st_ino % arraysize(links)];
350
351 while ((lp= *plp) != nil) {
352 if (lp->ino == stp->st_ino) {
353 /* May have seen this link before. Get it and check. */
354 if (lseek(tfd, lp->off, SEEK_SET) == -1) return nil;
355 if (read(tfd, &dlink, sizeof(dlink)) < 0) return nil;
356
357 /* Only need to check the device number. */
358 if (dlink.dev == stp->st_dev) {
359 if (strcmp(file, dlink.file) == 0) {
360 /* Called twice. Forget about this link. */
361 *plp= lp->next;
362 free(lp);
363 errno= ENOENT;
364 return nil;
365 }
366
367 /* Return the name of the earlier link. */
368 return dlink.file;
369 }
370 }
371 plp= &lp->next;
372 }
373
374 /* First time I see this link. Add it to the store. */
375 if (tfd == -1) {
376 for (;;) {
377 char *tmp;
378
379 tmp= tmpnam(nil);
380 tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600);
381 if (tfd < 0) {
382 if (errno != EEXIST) return nil;
383 } else {
384 (void) unlink(tmp);
385 break;
386 }
387 }
388 }
389 if ((len= strlen(file)) >= PATH_MAX) {
390 errno= E2BIG;
391 return nil;
392 }
393
394 dlink.dev= stp->st_dev;
395 strcpy(dlink.file, file);
396 len += offsetof(dlink_t, file) + 1;
397 if ((off= lseek(tfd, 0, SEEK_END)) == -1) return nil;
398 if (write(tfd, &dlink, len) != len) return nil;
399
400 if ((lp= malloc(sizeof(*lp))) == nil) return nil;
401 lp->next= nil;
402 lp->ino= stp->st_ino;
403 lp->off= off;
404 *plp= lp;
405 errno= ENOENT;
406 return nil;
407}
408
409#define cancellink() ((void) islink())
410
411static char *islink()
412/* Returns the name of the file path is linked to. If no such link can be
413 * found, then path is added to the list and nil is returned. If all the
414 * links of a file have been seen, then it is removed from the list.
415 * Directories are not seen as linkable.
416 */
417{
418 char *name;
419
420 name= link_islink(&st, path);
421 if (name == nil && errno != ENOENT) perrx(path);
422 return name;
423}
424
425static void setstat(ino, stp) ino_t ino; struct stat *stp;
426/* Set backup status info, we know that backup is true. */
427{
428 struct mode md;
429
430 md.md_mode = stp->st_mode;
431 md.md_uid = stp->st_uid;
432 md.md_gid = stp->st_gid;
433 md.md_rdev = stp->st_rdev;
434 md.md_devsiz = stp->st_size / 1024;
435
436 if (lseek(filemodes, (off_t) sizeof(md) * (off_t) ino, 0) == -1
437 || write(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
438 ) perrx(BACKUP);
439}
440
441static int getstat(name, stp) char *name; struct stat *stp;
442/* Get status information of file name, skipping some files. Backup info
443 * is inserted as needed.
444 */
445{
446 errno= 0;
447
448 if (strcmp(name, BACKUP) == 0) return -1;
449
450 if (lstat(name, stp) < 0) return -1;
451
452 if (stp->st_mode == S_IFREG && stp->st_mtime == 0) return -1;
453
454 if (backup) {
455 struct mode md;
456
457 if (lseek(filemodes,
458 (off_t) sizeof(md) * (off_t) stp->st_ino, 0) == -1
459 || read(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
460 || md.md_mode == 0
461 ) {
462 errno= 0;
463 setstat(stp->st_ino, stp);
464 } else {
465 stp->st_mode = md.md_mode;
466 stp->st_uid = md.md_uid;
467 stp->st_gid = md.md_gid;
468 stp->st_rdev = md.md_rdev;
469 if (S_ISBLK(stp->st_mode))
470 stp->st_size= (off_t) md.md_devsiz * 1024;
471 }
472 }
473 return 0;
474}
475
476static int advance()
477/* Determine next pathname, return true on success. */
478{
479 for (;;) {
480 if (E==nil) { /* First call, enter root dir. */
481 E= (struct entry *) malloc(sizeof(*E));
482 E->dir= nil;
483 E->con= nil;
484 E->next= nil;
485 E->name= (char *) malloc(3);
486 strcpy(E->name, transparent ? ".@" : ".");
487 } else
488 if (E->con != nil) /* Dir's files must be processed. */
489 E= E->con;
490 else {
491 for (;;) {
492 /* Remove E from it's parents list, then
493 * try next entry, if none, go to parent dir.
494 */
495 struct entry *junk= E, *parent= E->dir;
496
497 if (parent != nil) parent->con= E->next;
498 E= E->next;
499 free(junk->name);
500 free(junk);
501
502 if (E != nil) break;
503
504 if ((E= parent) == nil) return 0;
505 }
506 }
507 setpath(E);
508 if (getstat(path, &st) == 0) {
509 if (S_ISLNK(st.st_mode)) {
510 int n;
511
512 if ((n= readlink(path, lnkpth, PATH_MAX)) >= 0)
513 {
514 lnkpth[n]= 0;
515 break;
516 }
517 } else {
518 break;
519 }
520 }
521 if (errno != 0 && errno != ENOENT) perr(path);
522 }
523
524 linkpath= islink();
525 DPRINTF("%s: path = %s\n", path);
526 return 1;
527}
528
529static enum orders request()
530/* Slave reads command sent by master. */
531{
532 static char buf[64], *bp;
533 static int n= 0;
534 int req;
535
536 for (;;) {
537 if (n == 0) {
538 if ((n= read(chan[0], buf, (int) sizeof(buf))) <= 0) {
539 if (n < 0) perrx("request()");
540 /* Master died, try to report it then follow. */
541 fprintf(stderr,
542 "%s: Master died prematurely.\n", arg0);
543 exit(1);
544 }
545 bp= buf;
546 }
547 req= *bp++ & 0xFF;
548 n--;
549 if (req >= (int) ENTER) break;
550
551 /* Master using slave to print to stdout: */
552 putchar(req);
553 }
554 DPRINTF("%s: request() == %s\n", ORDERS[req - (int) ENTER]);
555
556 return (enum orders) req;
557}
558
559static void report()
560{
561 int r;
562
563 DPRINTF("%s: reporting now!\n", 0);
564
565 buckp= bucket;
566
567 while (buckn > 0) {
568 r = buckn;
569 if (r > (512 << sizeof(char*))) r= (512 << sizeof(char*));
570
571 if ((r= write(chan[1], buckp, r)) < 0) perrx("report()");
572
573 buckp += r;
574 buckn -= r;
575 }
576 buckp= bucket;
577 buckn= 0;
578}
579
580static void inform(a) enum answers a;
581/* Slave replies to master. */
582{
583 DPRINTF("%s: inform(%s)\n", ANSWERS[(int) a - (int) PATH]);
584
585 *buckp++ = (int) a;
586 buckn++;
587}
588
589#define wwrite(buf, n) (memcpy(buckp, (buf), (n)), buckp+= (n), buckn+= (n))
590
591static void sendnum(n) long n;
592/* Send number from least to most significant byte. */
593{
594#if LITTLE_ENDIAN
595 wwrite((char *) &n, sizeof(n));
596#else
597 char buf[NUMBYTES];
598
599 buf[0]= (char) (n >> 0);
600 buf[1]= (char) (n >> 8);
601 buf[2]= (char) (n >> 16);
602 buf[3]= (char) (n >> 24);
603 wwrite(buf, sizeof(buf));
604#endif
605}
606
607static void send(buf, n) char *buf; int n;
608/* Slave sends size and contents of buf. */
609{
610 sendnum((long) n);
611 if (n > 0) wwrite(buf, (size_t) n);
612}
613
614static void sendstat(stp) struct stat *stp;
615{
616 sendnum((long) stp->st_mode);
617 sendnum((long) stp->st_uid);
618 sendnum((long) stp->st_gid);
619 sendnum((long) stp->st_rdev);
620 sendnum((long) stp->st_size);
621 sendnum((long) stp->st_mtime);
622}
623
624static int ask();
625
626static void slave()
627/* Carry out orders from the master, such as transmitting path names.
628 * Note that the slave uses path, not Spath, the master uses Spath.
629 */
630{
631 int f, n;
632 char buf[CHUNK];
633 enum { run, done, die } state= run;
634
635 do {
636 switch (request()) {
637 case ENTER:
638 enter();
639 break;
640 case ADVANCE:
641 if (!advance() || state == done) {
642 inform(DONE);
643 state= done;
644 } else {
645 if (linkpath!=nil) {
646 inform(LINK);
647 send(linkpath, strlen(linkpath) + 1);
648 } else
649 if (S_ISLNK(st.st_mode)) {
650 inform(SYMLINK);
651 send(lnkpth, strlen(lnkpth) + 1);
652 } else {
653 inform(PATH);
654 }
655 send(path, strlen(path) + 1);
656 sendstat(&st);
657 }
658 break;
659 case CAT:
660 if ((f= open(path, O_RDONLY))<0) {
661 fprintf(stderr, "%s: Can't open %s",
662 arg0, path);
663 because();
664 inform(NODATA);
665 break;
666 }
667 inform(DATA);
668 do {
669 n= read(f, buf, sizeof(buf));
670 if (n < 0) perr(path);
671 send(buf, n);
672 if (n > 0) report();
673 } while (n > 0);
674 close(f);
675 break;
676 case CANCEL:
677 cancellink();
678 break;
679 case DIE_BAD:
680 ex= 1;
681 /*FALL THROUGH*/
682 case DIE:
683 state= die;
684 break;
685 case POSITIVE:
686 inform(ask('y') ? YES : NO);
687 break;
688 case NEGATIVE:
689 inform(ask('n') ? YES : NO);
690 break;
691 case PASS_YES:
692 inform(YES);
693 break;
694 case PASS_NO:
695 inform(NO);
696 break;
697 default:
698 fprintf(stderr, "%s: strange request\n", arg0);
699 exit(1);
700 }
701 report();
702 } while (state != die);
703}
704
705static int execute(argv) char **argv;
706/* Execute a command and return its success or failure. */
707{
708 int pid, r, status;
709
710 if ((pid= fork())<0) {
711 perr("fork()");
712 return 0;
713 }
714 if (pid == 0) {
715 execvp(argv[0], argv);
716 perrx(argv[0]);
717 }
718 while ((r= wait(&status)) != pid) {
719 if (r < 0) {
720 perr(argv[0]);
721 return 0;
722 }
723 }
724 return status == 0;
725}
726
727static int removedir(dir) char *dir;
728/* Remove a directory and its contents. */
729{
730 static char *argv[] = { "rm", "-r", nil, nil };
731
732 printf("(rm -r %s)\n", dir);
733
734 argv[2]= dir;
735 return execute(argv);
736}
737
738static void order(o) enum orders o;
739/* Master tells slave what to do. */
740{
741 char c= (char) o;
742
743 DPRINTF("%s: order(%s)\n", ORDERS[o - (int) ENTER]);
744
745 if (write(chan[1], &c, 1) != 1) perrx("order()");
746}
747
748static void rread(buf, n) char *buf; int n;
749/* Master gets buf of size n from slave, doing multiple reads if needed. */
750{
751 int r;
752
753 while (n > 0) {
754 if (buckn == 0) {
755 switch (buckn= read(chan[0], bucket, BUCKSIZE)) {
756 case -1:
757 perrx("reply channel from slave");
758 case 0:
759 fprintf(stderr,
760 "%s: slave died prematurely.\n",
761 arg0);
762 exit(1);
763 }
764 buckp= bucket;
765 }
766 r= n < buckn ? n : buckn;
767 memcpy(buf, buckp, r);
768 buckp+= r;
769 buckn-= r;
770 buf+= r;
771 n-= r;
772 }
773}
774
775static enum answers answer()
776/* Master reads slave's reply. */
777{
778 char c;
779 int a;
780
781 rread(&c, 1);
782 a= c & 0xFF;
783
784 DPRINTF("%s: answer() == %s\n", ANSWERS[a - (int) PATH]);
785
786 return (enum answers) a;
787}
788
789static long recnum()
790/* Read number as pack of bytes from least to most significant. The data
791 * is on the wire in little-endian format. (Mostly run on PC's).
792 */
793{
794#if LITTLE_ENDIAN
795 long n;
796
797 rread((char *) &n, (int) sizeof(n));
798 return n;
799#else
800 unsigned char buf[NUMBYTES];
801
802 rread(buf, sizeof(buf));
803 return buf[0]
804 | ((unsigned) buf[1] << 8)
805 | ((unsigned long) buf[2] << 16)
806 | ((unsigned long) buf[3] << 24);
807#endif
808}
809
810static int receive(buf, max) char *buf; int max;
811/* Master get's data from slave, by first reading size, then data. */
812{
813 int n;
814
815 n= recnum();
816 if (n > max) {
817 fprintf(stderr, "%s: panic: Can't read %d bytes\n", arg0, n);
818 exit(1);
819 }
820 if (n > 0) rread(buf, n);
821 return n;
822}
823
824static void recstat(stp) struct stat *stp;
825{
826 stp->st_mode= recnum();
827 stp->st_uid= recnum();
828 stp->st_gid= recnum();
829 stp->st_rdev= recnum();
830 stp->st_size= recnum();
831 stp->st_mtime= recnum();
832}
833
834static int key()
835{
836 int c;
837 static int tty= -1;
838
839 if (tty < 0) tty= isatty(0);
840
841 if (feof(stdin) || (c= getchar()) == EOF) {
842 c= '\n';
843 if (tty) putchar('\n');
844 }
845
846 if (!tty) putchar(c);
847
848 return c;
849}
850
851static int ask(def) int def;
852/* Ask for a yes or no, anything else means choose def. */
853{
854 int y, c;
855
856 if (chan[0] == 0) {
857 /* I'm running remote, tell the slave to ask. */
858 fflush(stdout);
859 order(def == 'y' ? POSITIVE : NEGATIVE);
860 return answer() == YES;
861 }
862
863 printf("? (%c) ", def);
864 fflush(stdout);
865
866 do c= key(); while (c == ' ' || c == '\t');
867
868 y= c;
869
870 while (c != '\n') c= key();
871
872 if (y != 'y' && y != 'Y' && y != 'n' && y != 'N') y= def;
873
874 return y == 'y' || y == 'Y';
875}
876
877static void setmodes(silent) int silent;
878{
879 struct stat st;
880 int change= 0;
881 struct utimbuf tms;
882
883 errno= 0;
884 getstat(Spath, &st);
885 if (backup && silent) {
886 setstat(st.st_ino, &Sst);
887 getstat(Spath, &st);
888 }
889
890 if (S_ISLNK(st.st_mode)) return;
891
892 if (errno == 0 && st.st_mode != Sst.st_mode) {
893 if (!backup) chmod(Spath, Sst.st_mode & 07777);
894 change= 1;
895 }
896 if (errno == 0
897 && (st.st_uid != Sst.st_uid || st.st_gid != Sst.st_gid)
898 && (backup || geteuid() == 0)
899 ) {
900 errno= 0;
901 if (!backup) chown(Spath, Sst.st_uid, Sst.st_gid);
902 change= 1;
903 }
904
905 if (backup && !silent) setstat(st.st_ino, &Sst);
906
907 if (errno == 0 && S_ISREG(Sst.st_mode) && st.st_mtime != Sst.st_mtime) {
908 time(&tms.actime);
909 tms.modtime= Sst.st_mtime;
910 errno= 0;
911 utime(Spath, &tms);
912 change= 1;
913 }
914 if (errno != 0) {
915 fprintf(stderr, "%s: Can't set modes of %s", arg0, Spath);
916 because();
917 } else
918 if (change && !silent) {
919 printf("Mode changed of %s\n", Spath);
920 }
921}
922
923static void makeold()
924{
925 static struct utimbuf tms= { 0, 0 };
926
927 if (utime(Spath, &tms) < 0) {
928 if (errno != ENOENT) {
929 fprintf(stderr,
930 "%s: can't make %s look old", arg0, Spath);
931 because();
932 }
933 } else {
934 fprintf(stderr, "%s: made %s look old.\n", arg0, Spath);
935 }
936}
937
938static int busy= 0;
939
940static void bail_out(sig) int sig;
941{
942 signal(sig, SIG_IGN);
943
944 fprintf(stderr, "%s: Exiting after signal %d\n", arg0, sig);
945
946 if (busy) {
947 fprintf(stderr, "%s: was installing %s\n", arg0, Spath);
948 makeold();
949 }
950 order(DIE_BAD);
951
952 exit(sig);
953}
954
955static int makenode(name, mode, addr, size)
956 char *name; int mode; dev_t addr; off_t size;
957{
958 int r;
959
960 if (!backup) {
961 r= mknod(name, mode, addr);
962 } else {
963 if ((r= creat(name, 0644)) >= 0) close(r);
964 }
965 return r;
966}
967
968static void add(update) int update;
969/* Add Spath to the filesystem. */
970{
971 int f, n;
972 char buf[CHUNK];
973 int forced_update= force && update;
974
975 if (Slinkpath != nil && !S_ISLNK(Sst.st_mode)) {
976 if (interact && !update) {
977 printf("Link %s to %s", Spath, Slinkpath);
978 if (!ask('n')) return;
979 }
980 if (link(Slinkpath, Spath) >= 0) {
981 printf("Linked %s to %s\n", Spath, Slinkpath);
982 return;
983 } else {
984 fprintf(stderr,
985 "%s: Can't link %s to %s",
986 arg0, Slinkpath, Spath);
987 because();
988 /* Try to install instead. */
989 }
990 }
991 switch (Sst.st_mode & S_IFMT) {
992 case S_IFDIR:
993 if (!force) {
994 printf("Add dir %s", Spath);
995 if (!ask('n')) return;
996 }
997 if (mkdir(Spath, backup ? 0755 : Sst.st_mode) < 0) {
998 perr(Spath);
999 return;
1000 }
1001 printf("Directory %s created.\n", Spath);
1002 order(ENTER);
1003 break;
1004 case S_IFBLK:
1005 case S_IFCHR:
1006 case S_IFIFO:
1007 if (interact && !update) {
1008 printf("Create special file %s", Spath);
1009 if (!ask('n')) { order(CANCEL); return; }
1010 }
1011 if (makenode(Spath, Sst.st_mode, Sst.st_rdev, Sst.st_size)<0) {
1012 fprintf(stderr,
1013 "%s: Can't create special file %s",
1014 arg0, Spath);
1015 because();
1016 return;
1017 }
1018 printf("Special file %s created\n", Spath);
1019 break;
1020#ifdef S_IFLNK
1021 case S_IFLNK:
1022 if (interact && !update) {
1023 printf("Install %s -> %s", Spath, Slnkpth);
1024 if (!ask('n')) { order(CANCEL); return; }
1025 }
1026 if (symlink(Slnkpth, Spath) < 0) {
1027 fprintf(stderr, "%s: Can't create symlink %s",
1028 arg0, Spath);
1029 because();
1030 return;
1031 }
1032 printf("%s %s -> %s\n",
1033 forced_update ? "Updated: " : "Installed:",
1034 Spath, Slnkpth);
1035 break;
1036#endif
1037 case S_IFREG:
1038 if (interact && !update) {
1039 printf("Install %s", Spath);
1040 if (!ask('n')) { order(CANCEL); return; }
1041 }
1042 order(CAT);
1043 if (answer() != DATA) return;
1044
1045 busy= 1;
1046 if ((f= creat(Spath, backup ? 0644 : Sst.st_mode&07777)) < 0) {
1047 busy= 0;
1048 fprintf(stderr, "%s: Can't create %s", arg0, Spath);
1049 because();
1050 }
1051
1052 while ((n= receive(buf, sizeof(buf)))>0) {
1053 if (f >= 0 && write(f, buf, n) != n) {
1054 fprintf(stderr, "%s: Write error on %s",
1055 arg0, Spath);
1056 because();
1057 close(f); f= -1;
1058 }
1059 }
1060 if (n < 0) {
1061 fprintf(stderr, "%s: Slave read err on %s\n",
1062 arg0, Spath);
1063 }
1064 if (f >= 0) close(f);
1065 if (n < 0 || f < 0) { makeold(); busy= 0; return; }
1066 busy= 0;
1067 printf("%s %s\n",
1068 forced_update ?
1069 Sst.st_mtime < st.st_mtime ? "Restored: " :
1070 "Updated: " :
1071 "Installed:",
1072 Spath
1073 );
1074 break;
1075 default:
1076 fprintf(stderr,
1077 "%s: Won't add file with strange mode %05o: %s\n",
1078 arg0, Sst.st_mode, Spath);
1079 order(CANCEL);
1080 return;
1081 }
1082 setmodes(1);
1083}
1084
1085static int delete(update) int update;
1086/* Delete path. */
1087{
1088 int forced_update= force && update;
1089
1090 if (S_ISDIR(st.st_mode)) {
1091 if (install) return 0;
1092 if (!force) {
1093 printf("Delete dir %s", path);
1094 if (!ask('n')) return 0;
1095 }
1096 if (!removedir(path)) { ex= 1; return 0; }
1097 if (!forced_update) printf("Directory %s deleted.\n", path);
1098 return 1;
1099 }
1100
1101 if (install && !update) return 0;
1102
1103 if (!force) {
1104 printf("Delete %s", path);
1105 if (!ask((interact && !update) ? 'n' : 'y')) return 0;
1106 }
1107
1108 if (unlink(path)<0) {
1109 fprintf(stderr, "Can't delete %s", path);
1110 because();
1111 return 0;
1112 }
1113 cancellink();
1114 if (!forced_update) printf("Deleted: %s\n", path);
1115 return 1;
1116}
1117
1118static int different()
1119/* Return true iff path and Spath are different. */
1120{
1121 if (! ( (linkpath == nil && Slinkpath == nil)
1122 || (linkpath != nil && Slinkpath != nil
1123 && strcmp(linkpath, Slinkpath) == 0)
1124 )) {
1125 linkpath= Slinkpath;
1126 return 1;
1127 }
1128
1129 if ((st.st_mode & S_IFMT) != (Sst.st_mode & S_IFMT)) return 1;
1130
1131 switch (st.st_mode & S_IFMT) {
1132 case S_IFDIR:
1133 return 0;
1134 case S_IFBLK:
1135 case S_IFCHR:
1136 return st.st_rdev != Sst.st_rdev;
1137 case S_IFREG:
1138 if (install) return Sst.st_mtime > st.st_mtime;
1139 return st.st_size != Sst.st_size
1140 || st.st_mtime != Sst.st_mtime;
1141 case S_IFIFO: return 0;
1142#ifdef S_IFLNK
1143 case S_IFLNK: return strcmp(lnkpth, Slnkpth) != 0;
1144#endif
1145 default: return 1;
1146 }
1147}
1148
1149static void compare()
1150/* See if path and Spath are same. */
1151{
1152 if (different()) {
1153 if (!force) {
1154 printf("%sing %s (delete + add)\n",
1155 Sst.st_mtime < st.st_mtime ? "Restor" : "Updat",
1156 path);
1157 }
1158 if (delete(1)) add(1);
1159 } else {
1160 if (!install) setmodes(0);
1161
1162 if (S_ISDIR(st.st_mode)) {
1163 order(ENTER);
1164 enter();
1165 }
1166 }
1167}
1168
1169static int done= 0, Sdone= 0;
1170
1171static enum action { ADD, COMPARE, DELETE } action()
1172/* Look at path's of master and slave, compare them alphabetically to see
1173 * who is ahead of who, then tell what is to be done.
1174 */
1175{
1176 int c;
1177 char *Sp, *p;
1178
1179 if (done) return ADD; /* Slave still has names. */
1180 if (Sdone) return DELETE; /* Master has too many names. */
1181
1182 /* Compare paths. Let "a/a" come before "a.a". */
1183 Sp= Spath;
1184 p= path;
1185 while (*Sp == *p && *Sp != 0) { Sp++; p++; }
1186 if (*Sp == '/') return ADD;
1187 if (*p == '/') return DELETE;
1188 return (c= strcmp(Sp, p)) == 0 ? COMPARE : c < 0 ? ADD : DELETE;
1189}
1190
1191static void master()
1192/* Synchronise file tree to that of its slave. */
1193{
1194 enum action a= COMPARE; /* Trick first advances. */
1195
1196 umask(backup ? 0022 : 0000);
1197
1198 signal(SIGPIPE, SIG_IGN);
1199 signal(SIGHUP, bail_out);
1200 signal(SIGINT, bail_out);
1201 signal(SIGTERM, bail_out);
1202
1203 while (!done || !Sdone) {
1204 if (!Sdone && (a == ADD || a == COMPARE)) {
1205 /* Slave advances. */
1206 order(ADVANCE);
1207 switch (answer()) {
1208 case PATH:
1209 Slinkpath= nil;
1210 receive(Spath, sizeof(Spath));
1211 recstat(&Sst);
1212 break;
1213 case LINK:
1214 receive(Slnkpth, sizeof(Slnkpth));
1215 Slinkpath= Slnkpth;
1216 receive(Spath, sizeof(Spath));
1217 recstat(&Sst);
1218 break;
1219 case SYMLINK:
1220 Slinkpath= nil;
1221 receive(Slnkpth, sizeof(Slnkpth));
1222 receive(Spath, sizeof(Spath));
1223 recstat(&Sst);
1224 break;
1225 case DONE:
1226 Sdone= 1;
1227 break;
1228 default:
1229 fprintf(stderr,
1230 "%s: Strange answer from slave.\n",
1231 arg0);
1232 exit(1);
1233 }
1234 }
1235 if (!done && (a == COMPARE || a == DELETE)) {
1236 /* Master advances. */
1237 if (!advance()) done= 1;
1238 }
1239
1240 if (done && Sdone) break;
1241
1242 switch (a= action()) {
1243 case ADD: /* Spath exists, path doesn't, add? */
1244 add(0);
1245 break;
1246 case COMPARE: /* They both exist, are they the same? */
1247 compare();
1248 break;
1249 case DELETE: /* path exists, Spath doesn't, delete? */
1250 delete(0);
1251 }
1252 fflush(stdout); /* Don't keep user in suspense. */
1253 }
1254 order(ex == 0 ? DIE : DIE_BAD);
1255}
1256
1257static void mediator()
1258/* Sits at the local machine and passes orders from master to slave, both
1259 * on remote machines. Only diagnostics and questions are handled.
1260 */
1261{
1262 enum orders req;
1263
1264 for (;;) {
1265 switch (req= request()) {
1266 case DIE_BAD:
1267 ex= 1;
1268 /*FALL THROUGH*/
1269 case DIE:
1270 order(DIE);
1271 return;
1272 case POSITIVE:
1273 order(ask('y') ? PASS_YES : PASS_NO);
1274 break;
1275 case NEGATIVE:
1276 order(ask('n') ? PASS_YES : PASS_NO);
1277 break;
1278 default:
1279 order(req);
1280 }
1281 }
1282}
1283
1284#define P_EXIT 1 /* Make sure process doesn't return. */
1285#define P_SHADOW 2 /* Always use exec on 68000. */
1286
1287static void startprocess(proc, machine, path, p_flags)
1288 void (*proc)(); char *machine, *path; int p_flags;
1289{
1290 char *argv[10], **argp= argv;
1291 char flags[10], *pfl= flags;
1292
1293 if (machine != nil) {
1294 char *u= machine, *m;
1295
1296 *argp++ = "rsh";
1297 if ((m= strchr(machine, '@')) != nil) {
1298 *m++ = 0;
1299 *argp++ = "-l";
1300 *argp++ = u;
1301 machine= m;
1302 }
1303 *argp++ = machine;
1304 } else
1305 /* Without this check it would run like a pig on an non MMU 68000: */
1306 if (!(USE_SHADOWING && p_flags & P_SHADOW)) {
1307 if (chdir(path) < 0) {
1308 if (proc != master || errno != ENOENT
1309 || mkdir(path, 0700) < 0)
1310 perrx(path);
1311 if (chdir(path) < 0) perrx(path);
1312 printf("Destination directory %s created\n", path);
1313 }
1314 isvisible(path);
1315 isbackup(proc == slave);
1316 (*proc)();
1317 if (p_flags & P_EXIT) exit(ex);
1318 return;
1319 }
1320 *argp++ = SYNCNAME;
1321 *pfl++ = '-';
1322 if (interact) *pfl++ = 'i';
1323 if (install) *pfl++ = 'u';
1324 if (force) *pfl++ = 'f';
1325 *pfl= 0;
1326 *argp++ = flags;
1327 *argp++ = proc == slave ? SLAVENAME : MASTERNAME;
1328 *argp++ = path;
1329 *argp++ = nil;
1330#ifdef DEBUG
1331 fprintf(stderr, "execlp(");
1332 for (argp= argv; *argp != nil; argp++) fprintf(stderr, "%s, ", *argp);
1333 fprintf(stderr, "nil);\n");
1334#endif
1335 execvp(argv[0], argv);
1336 perrx(argv[0]);
1337}
1338
1339void splitcolon(path, amach, adir) char *path, **amach, **adir;
1340{
1341 char *dir= path;
1342
1343 for (;;) {
1344 if (*dir == ':') {
1345 *dir++ = 0;
1346 *amach= path;
1347 *adir= dir;
1348 break;
1349 }
1350 if (*dir == 0 || *dir == '/') {
1351 *amach= nil;
1352 *adir= path;
1353 break;
1354 }
1355 dir++;
1356 }
1357}
1358
1359static void Usage()
1360{
1361 fprintf(stderr,
1362 "Usage: %s [-iuf] [[user@]machine:]dir1 [[user@]machine:]dir2\n",
1363 arg0);
1364 exit(1);
1365}
1366
1367main(argc, argv) int argc; char **argv;
1368{
1369 char *s_mach, *s_dir;
1370 char *m_mach, *m_dir;
1371 int m2s[2], s2m[2], m2m[2];
1372 int s_pid= 0, m_pid= 0;
1373 int r;
1374
1375 if ((arg0= strrchr(argv[0], '/')) == nil) arg0= argv[0]; else arg0++;
1376
1377 while (argc>1 && argv[1][0] == '-') {
1378 char *f= argv[1]+1;
1379
1380 while (*f != 0) {
1381 switch (*f++) {
1382 case 'i': interact= 1; break;
1383 case 'u': install= 1; break;
1384 case 'f': force= 1; break;
1385 default: Usage();
1386 }
1387 }
1388 argc--;
1389 argv++;
1390 }
1391
1392 if (argc != 3) Usage();
1393
1394 if (strcmp(argv[1], SLAVENAME) == 0) {
1395 arg0= "Slave";
1396 splitcolon(argv[2], &s_mach, &s_dir);
1397 startprocess(slave, s_mach, s_dir, P_EXIT);
1398 } else
1399 if (strcmp(argv[1], MASTERNAME) == 0) {
1400 arg0= "Master";
1401 splitcolon(argv[2], &m_mach, &m_dir);
1402 startprocess(master, m_mach, m_dir, P_EXIT);
1403 }
1404
1405 splitcolon(argv[1], &s_mach, &s_dir);
1406 splitcolon(argv[2], &m_mach, &m_dir);
1407
1408 /* How difficult can plumbing be? */
1409 if (pipe(m2s) < 0 || pipe(s2m) < 0) perrx("pipe()");
1410
1411 if (m_mach == nil) {
1412 /* synctree [machine:]dir1 dir2 */
1413 switch (s_pid= fork()) {
1414 case -1:
1415 perrx("fork()");
1416 case 0:
1417 dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
1418 dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
1419 arg0= "Slave";
1420 startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
1421 }
1422 chan[0]= s2m[0]; close(s2m[1]);
1423 chan[1]= m2s[1]; close(m2s[0]);
1424 startprocess(master, m_mach, m_dir, 0);
1425 } else
1426 if (s_mach == nil) {
1427 /* synctree dir1 machine:dir2 */
1428 switch (m_pid= fork()) {
1429 case -1:
1430 perrx("fork()");
1431 case 0:
1432 dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
1433 dup2(m2s[1], 1); close(m2s[0]); close(m2s[1]);
1434 arg0= "Master";
1435 startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
1436 }
1437 chan[0]= m2s[0]; close(m2s[1]);
1438 chan[1]= s2m[1]; close(s2m[0]);
1439 startprocess(slave, s_mach, s_dir, 0);
1440 } else {
1441 /* synctree machine1:dir1 machine2:dir2 */
1442 if (pipe(m2m) < 0) perrx(pipe);
1443
1444 switch (s_pid= fork()) {
1445 case -1:
1446 perrx("fork()");
1447 case 0:
1448 dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
1449 dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
1450 close(m2m[0]); close(m2m[1]);
1451 arg0= "Slave";
1452 startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
1453 }
1454
1455 switch (m_pid= fork()) {
1456 case -1:
1457 perrx("fork()");
1458 case 0:
1459 dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
1460 close(m2s[0]); close(m2s[1]);
1461 dup2(m2m[1], 1); close(m2m[0]); close(m2m[1]);
1462 arg0= "Master";
1463 startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
1464 }
1465 close(s2m[0]); close(s2m[1]);
1466 chan[0]= m2m[0]; close(m2m[1]);
1467 chan[1]= m2s[1]; close(m2s[0]);
1468 mediator();
1469 }
1470 close(chan[0]);
1471 close(chan[1]);
1472
1473 alarm(15); /* Don't wait(2) forever. */
1474
1475 while (s_pid != 0 || m_pid != 0) {
1476 if ((r= wait((int *) nil)) < 0) perrx("wait()");
1477 if (r == s_pid) s_pid= 0;
1478 if (r == m_pid) m_pid= 0;
1479 }
1480 exit(ex);
1481}
Note: See TracBrowser for help on using the repository browser.