#!/usr/local/bin/perl
# $Id: syncdir,v 1.19 2001/08/18 22:29:53 yuuji Exp $
# Sync two directories' hierarchy
#
# (c)1995-2001 by HIROSE Yuuji [yuuji@gentei.org]
# Last modified Sun Aug 19 07:27:07 2001 on duke

#
# [Commentary]
# 
# This script enables you to make a perfect mirror of a certain directory.
# 
# [How to use]
# 
# Just call syncdir as follows:
#
#	% syncdir SrcDir DestDir
# 
# Syncdir reads   `SrcDir' as copy  source directory  and copy each file
# that is not in `DestDir' or is newer  than that in `DestDir'.  If `-d'
# option is given,  syncdir removes all  files in `DestDir' that are not
# in `SrcDir'.  Thus `syncdir -d A B' can keep both  A and B directories
# strictly the same.  Type `syncdir -h' alone to display other available
# options.
#
# 
# $B!Z$J$K$3$l![(B
# 
# syncdir - $BFs$D$N%G%#%l%/%H%j$rF10l$KJ]$D(B
# 
# 
# $B!Z$I$d$C$F;H$&$N(B?$B![(B
# 
#	% syncdir $BJ#@=85%G%#%l%/%H%j(B $BJ#@=@h%G%#%l%/%H%j(B
# 
# $B$H5/F0$9$k$H!"J#@=85%G%#%l%/%H%j0J2<$NA4$F$N%U%!%$%k$N$&$AJ#@=@h%G%#%l(B
# $B%/%H%j$KB8:_$7$J$$!"$^$?$OB8:_$7$F$bJ#@=85$N$[$&$,?7$7$$$b$N$@$1$rA4$F(B
# $BJ#@=@h%G%#%l%/%H%j$K%3%T!<$7$^$9!#(B
# 
# -d $B%*%W%7%g%s$rIU$1$k$H!"J#@=@h$K$7$+B8:_$7$J$$%U%!%$%k$,$"$C$?>l9g$=(B
# $B$l$r:o=|$7$^$9!#(Bsyncdir -d A B $B$H$9$k$H(BA$B%G%#%l%/%H%j$H(BB$B%G%#%l%/%H%j$N(B
# $BCf?H$r40A4$KF10l$KJ]$D$3$H$,$G$-$^$9!#$?$@$7!"J#@=85%G%#%l%/%H%j!"J#@=(B
# $B@h%G%#%l%/%H%j$OI,$:N>J}$H$bB8:_$7$J$1$l$P$J$j$^$;$s!#(B
# 
# -n $B%*%W%7%g%s$r$D$1$k$H!"<B:]$K$O%3%T!<(B/$B:o=|$O9T$o$:!"$I$&$$$&:n6H$r$9(B
# $B$k$+$@$1$rI8=`=PNO$KI=<($7$^$9!#(Bsyncdir -n A B | tee hoge $B$H$7$F$I$&$$(B
# $B$&%U%!%$%k$,%3%T!<$5$l$k$+3NG'$7$?$"$H!"(Bcat hoge | sh $B$J$I$H$9$k$HNI$$(B
# $B$+$b$7$l$^$;$s(B($B!Z%P%0![$N9`;2>H(B)$B!#(B
# 
# -N $B%*%W%7%g%s$O!"$[$\(B -n $B$HF1$8$G$9$,!"J#@=@h%G%#%l%/%H%j$N!V$I$N%U%!(B
# $B%$%k$r>C$9$+!W$N$_$rD4$Y$FI=<($7$^$9!#J#@=85(B/$BJ#@=@h$N;XDj$r4V0c$($F5U(B
# $B$K$7$?>l9g8E$$%U%!%$%k$G?7$7$$$b$N$r>e=q$-$9$k;v8N$O$"$j$($^$;$s$,!"?7(B
# $B$7$/:n$C$?$O$:$N%U%!%$%k$r>C$7$F$7$^$&$3$H$O9M$($i$l$k$N$G!">C5n%A%'%C(B
# $B%/$@$1$r9T$$$^$9!#(B
# 
# -p $B%*%W%7%g%s$r$D$1$k$HJ#@=85$N%U%!%$%k$N(B atime (access time) $B$rJ]B8$7(B
# $B$^$9!#(Batime$B$r5$$K$9$kJ}$O(B -p $B$r$D$1$F$/$@$5$$!#(B
# 
# -q $B%*%W%7%g%s$r$D$1$k$H%a%C%;!<%8$rI=<($;$:$K<B9T$7$^$9!#$7$+$7!"$3$N(B
# $B%9%/%j%W%H$,40A4$KF0:n$9$k$3$H$,J,$+$C$?$i(B -q $B$r%G%U%)%k%H$K$7$F(B -v $B%*(B
# $B%W%7%g%s$G%a%C%;!<%8$r=P$9$h$&$KJQ$($kM=Dj$G$9!#$H!";W$C$F$$$?$s$G$9$,!"(B
# $B$d$C$Q$j?4G[>I$J$N$G%a%C%;!<%8$,$J$$$HIT0B$G$9;d!#I=<($9$k$r%G%U%)%k%H(B
# $B$K$5$;$F$*$$$F2<$5$$!#(B
# 
# -l $B%*%W%7%g%s$r$D$1$k$H!"J#@=85$N!"%U%!%$%k$r;X$7$F$$$k%7%s%\%j%C%/%j(B
# $B%s%/!"$r%7%s%\%j%C%/%j%s%/%U%!%$%k$H$7$F%3%T!<$;$:$K!"$=$N%7%s%\%j%C%/(B
# $B%j%s%/$,;X$7$F$$$k%U%!%$%k$r%3%T!<$7$^$9!#(B
# 
# -L $B%*%W%7%g%s$r$D$1$k$H!"J#@=85$N!"%G%#%l%/%H%j$r;X$7$F$$$k%7%s%\%j%C(B
# $B%/%j%s%/!"$r%7%s%\%j%C%/%j%s%/$H$7$F%3%T!<$;$:$K!"$=$N%7%s%\%j%C%/%j%s(B
# $B%/$,;X$7$F$$$k%G%#%l%/%H%j$K9_$j$F$5$i$K:F5"%3%T!<:n6H$rB3$1$^$9!#(B
# 
# -e $B%*%W%7%g%s$r$D$1$k$H!"J#@=85$N%j%s%/@h$r;}$?$J$$%U%!%$%k$b$=$N$^$^(B
# $B%3%T!<$7$^$9!#(B
# 
# -c $B%*%W%7%g%s$r$D$1$k$H40A40lCW%b!<%I$K$J$j!"J#@=@h$N%U%!%$%k$NJ}$,?7(B
# $B$7$/$F$b!"%U%!%$%k%5%$%:!&F|IU!&%U%!%$%kB0@-$N0l$D$G$b0c$&>l9g$K6/@)%3(B
# $B%T!<$7$^$9!#(B
# 
# -P $B%*%W%7%g%s$r$D$1$k$HJ#@=@h$N(B *$B%G%#%l%/%H%j(B* $B$N%?%$%`%9%?%s%W$bJ#@=(B
# $B85$N$b$N$HF10l$K$7$^$9!#(B
# 
# -A $B%*%W%7%g%s$r$D$1$k$H!"40A4$KF10l$J%G%#%l%/%H%j%D%j!<$r:n$k$H$-$K;X(B
# $BDj$9$k$G$"$m$&%*%W%7%g%s!"(B-dpecP $B$r$D$1$?$N$HF1$8F/$-$r$7$^$9!#(B
# 
# -x $B%*%W%7%g%s$KB3$1$F@55,I=8=(B(perl$B$N$b$N(B)$B$r;XDj$9$k$H!"$=$N@55,I=8=$K(B
# $B%^%C%A$9$k%U%!%$%k$^$?$O%G%#%l%/%H%j$OL5;k$7$^$9!#(Bperl$B$N@55,I=8=$J$N$G!"(B
# $B$?$H$($P(B a $B$H$$$&%U%!%$%k$rL5;k$7$?$$>l9g$O(B `-x ^a$' $B$N$h$&$K;XDj$7$F(B
# $B$/$@$5$$!#C1$K(B a $B$H$9$k$H(B a $B$H$$$&J8;z$r;}$D%U%!%$%kA4$F$r=|30$7$F$7$^(B
# $B$$$^$9!#$J$*!"(B-x $B%*%W%7%g%s$OJ#?t;XDj$G$-$^$9!#(B
# 
# -X $B%*%W%7%g%s$O(B -x $B%*%W%7%g%s$K;w$F$$$^$9$,!"@55,I=8=$N%^%C%A%s%0$r%U%!(B
# $B%$%kL>(B(or$B%G%#%l%/%H%jL>(B)$B$@$1$G$J$/!"%Q%9L>A4BN$KBP$7$F9T$$$^$9!#(B
# 
# -b $B%*%W%7%g%s$K?tCM$rB3$1$k$H!"%3%T!<;~$K%U%!%$%kFbMF$r0l;~E*$KFI$_9~(B
# $B$`%P%C%U%!$N%P%$%H%5%$%:$r;XDj$G$-$^$9!#%G%U%)%k%H$O(B1MB$B$G$9!#%a%b%jIT(B
# $BB-$N$H$-$O>.$5$/!"%a%b%jIY9k$N$H$-$OBg$-$/$9$k$J$I$7$F$/$@$5$$!#(B-b 2M
# $B$d!"(B-b 20K $B$N$h$&$K!"$=$l$>$l(BMB$BC10L!"(BKB$BC10L$G$N;XDj$b$G$-$^$9(B($B>.J8;z$G(B
# $B$b2D(B)$B!#(B
# 
# $B!Z$9$9$s$@;H$$J}![(B
# 
# syncdir -pd a b $B$J$s$F$d$C$F!"4V0c$C$F(B a $B$,6u$C$]$J;~$K$O(B b $B$b6u$C$]$K(B
# $B$J$C$F$7$^$$$^$9!#$=$s$J$o$1$G(B a, b $B$r;XDj$9$k$N$K7k9=%I%-%I%-$7$F$7$^(B
# $B$$$^$9!#$=$3$G0J2<$N$h$&$J%U%!%$%k$r(B ~/.syncdir $B$H$$$&L>A0$G=q$$$F$*$-(B
# $B$^$9!#(B
# 
#	home:	src=/home/yuuji;dest=/mo/mirror/home;keyfile=.cshrc
#	work:	src=/private/yuuji/work;dest=/mo/mirror/private/work
#		keyfile=Makefile;exclude=^trashbox$
# 
# $B=q<0$O!"(B
# 
#	$B%\%j%e!<%`L>(B:	src=$BJ#@=85%G%#%l%/%H%j(B;dest=$BJ#@=@h%G%#%l%/%H%j(B
#			keyfile=$BG'>Z%U%!%$%k(B
#			exclude=$B=|30%U%!%$%kL>(B($B%G%#%l%/%H%jL>(B)$B%Q%?!<%s(B
#			EXCLUDE=$B=|30%Q%9L>%Q%?!<%s(B
# 
# $B!VG'>Z%U%!%$%k!W$,J#@=85%G%#%l%/%H%j$K$J$+$C$?>l9g$K$O!"$=$3$OK\Mh$"$k(B
# $B$Y$-;Q$NJ#@=85%G%#%l%/%H%j$G$O$J$$$HH=Dj$7!":n6H$rCf;_$7$^$9!#Nc$($P!"(B
# $B%[!<%`%G%#%l%/%H%j$,;v8N$K$h$jGK2u$5$l$F$$$F(B ~ $B$N2<$K2?$b$J$/$J$C$F$7(B
# $B$^$C$?>l9g$K$bJ#@=@h$rGK2u$;$:$K:Q$_$^$9!#!V=|30%Q%?!<%s!W$K$D$$$F$O(B
# -x $B%*%W%7%g%s!"(B-X $B%*%W%7%g%s$HF1MM(Bperl$B$N@55,I=8=$G;XDj$7$F$/$@$5$$!#(B
# 
# $B$5$F!"(B~/.syncdir $B$rMQ0U$7$?$i0J2<$N$h$&$K!"(B-V $B%*%W%7%g%s$G%\%j%e!<%`L>(B
# $B$r;XDj$7$^$9!#(B
# 
#	% syncdir -pd -V home
# $B$"$k$$$O(B
#	% syncdir -pd -r -V home
# 
# $B>e$N=q<0$O%\%j%e!<%`L>$,(Bhome$B$N$b$N$rFI$_9~$s$G%3%T!<(B/$B>C5n$r9T$$!"2<$N(B
# $B=q<0$O%\%j%e!<%`L>$,(Bhome$B$N$b$N$rFI$_9~$s$G!"(Bsrc, dest $B$rH?E>$7$F:n6H$r(B
# $B9T$$$^$9!#$5$i$K(B
# 
#	% syncdir -pd -a
# 
# $B$N$h$&$K(B -a $B%*%W%7%g%s$r$D$1$k$H!"(B~/.syncdir $B$KEPO?$7$F$"$kA4$F$N%\%j%e!<(B
# $B%`$KBP$7$F%3%T!<:n6H$r9T$$$^$9!#$^$?!"%\%j%e!<%`L>$O(B -V home,www $B$N$h(B
# $B$&$K(B ,($B%+%s%^(B)$B$G6h@Z$C$FJ#?t8D;XDj$9$k$3$H$,$G$-$^$9(B($B$7$?$,$C$F%+%s%^(B
# $B$r4^$`%\%j%e!<%`L>$O@_Dj$G$-$^$;$s(B)$B!#(B
# 
# $B$^$?!"(B~/.syncdir $B%U%!%$%k$N%\%j%e!<%`Dj5A$K$OJL$N%\%j%e!<%`L>$rNs5s$9(B
# $B$k$3$H$,$G$-$^$9!#$?$H$($P0J2<$N$h$&$K%\%j%e!<%`$N%0%k!<%T%s%0$,$G$-$^(B
# $B$9!#(B
#	Mon:	work
#	Tue:	src
#	Wed:	foo,bar
#	weekly:	work,src,foo,bar
#	
#	work:	src=~/work; dest=/mo/work; keyfile=Makefile
#	src:	src=~/src; dest=/mo/src; keyfile=RCS
#	foo:	src=~/foo; dest=/mo/foo; keyfile=foo.c
#	bar:	src=~/bar; dest=/mo/bar; keyfile=bar.pl
# 
# 
# $B%\%j%e!<%`;XDj%U%!%$%k$H$7$F(B ~/.syncdir $B0J30$N$b$N$rMxMQ$7$?$$>l9g$O!"(B
# 
#	% syncdir -f ~/.syncdir_2 -V hoge
# 
# $B$N$h$&$K(B -f $B%*%W%7%g%s$KB3$1$F%\%j%e!<%`;XDj%U%!%$%k$r5-=R$7$F$/$@$5$$!#(B
# 
# 
# $B!Z%P%0![(B
# 
# $B%G%P%$%9%U%!%$%k(B/$B%=%1%C%H$J$I$NFC<l%U%!%$%k$O07$&$3$H$,$G$-$^$;$s!#$7(B
# $B$?$,$C$F(B /dev $B$J$I$N%P%C%/%"%C%WL\E*$K$O;H$o$J$$$G$/$@$5$$!#(B
# 
# $B%O!<%I%j%s%/$5$l$F$$$k%U%!%$%k$r%3%T!<$9$k;~!"%j%s%/Aj<j$,J#@=85%G%#%l(B
# $B%/%H%jG[2<$KB8:_$7$J$$>l9g!"J#@=@h$N%U%!%$%k$O%O!<%I%j%s%/$r;}$?$J$$(B
# ($B%j%s%/%+%&%s%H(B1$B$N(B)$B%U%!%$%k$H$J$j$^$9!#$?$@$7!"$=$N8eJ#@=85%G%#%l%/%H(B
# $B%j$H$7$F%O!<%I%j%s%/Aj<j$rJq4^$9$k0LCV$r;XDj$7$F:FEY(Bsyncdir$B$r5/F0$9$k(B
# $B$H!"J#@=@h$NBP1~%U%!%$%k$O@5$7$$%j%s%/Aj<j$r;}$D$h$&$K=$@5$5$l$^$9!#(B
# 
# -n $B%*%W%7%g%s$GI=<($9$k$b$N$O!"Fs$D$N%G%#%l%/%H%j$N3,AX9=B$$,0c$&;~$K(B
# $B$O<B:]$K9T$&=hM}$r@53N$KH?1G$7$?$b$N$G$O$"$j$^$;$s!#(B
# 
# $B%U%!%$%kL>$N@hF,$"$k$$$OKvHx$K6uGr(B/$B%?%V(B/$B2~9TJ8;z$r4^$`$b$N$r<h$j07$&$3(B
# $B$H$,=PMh$^$;$s!#$3$l$O(BPerl$B$N;EMM$G$9!#(BMacintosh$B$G:n@.$7$?%U%!%$%k$K$O(B
# $B$=$&$7$?$b$N$,B8:_$9$k$3$H$,$"$k$N$GCm0U$7$F2<$5$$!#(B
# 
# $B!Z$A$&$$![(B
# 
# mh$B$N%U%)%k%@$r(B syncdir $B$9$k$H$-$K$O$+$J$i$:(B -c $B%*%W%7%g%s$rIU$1$F2<$5(B
# $B$$(B!  syncdir$B$O%U%!%$%k$N%?%$%`%9%?%s%W$r$_$F%3%T!<$7$F$$$k$N$G!"(Bmh$B%f!<(B
# $B%F%#%j%F%#$,%U%!%$%kL>JQ99$7$F%?%$%`%9%?%s%W$,JQ$o$i$J$$$^$^$N$b$N$r(B
# syncdir$B$7$F$b4|BT$7$?7k2L$H$J$j$^$;$s!#$o$?$7$3$l$G%O%^$j$^$7$?!#(B
# 
# $B!Z$*$^$1![(B
# 
# zsh $B$G(B syncdir $B$N0z?t(B/$B%*%W%7%g%sJd40$r9T$J$&5-=R$G$9!#(B~/.zshrc$B$KJ|$j9~(B
# $B$s$G$4MxMQ2<$5$$!#(B
# 
#	glob_syncdir () { #$B%\%j%e!<%`Dj5A%U%!%$%k$+$i%\%j%e!<%`L>Jd40(B
#		local n a p=1 table=${SYNCDIRTABLE:-~/.syncdir} lo
#		read -cA a		#$B%3%^%s%I%i%$%sA4BN$rG[Ns(B a $B$KF~$l$k(B
#		read -cn n		#$B0z?t$N?t$r(B n $B$KF~$l$k(B
#		while [[ $p -lt $n ]] {
#		 [[ -f $a[$p] && "$lo" = "-f" ]] && table=$a[$p]
#		 lo=$a[$p]
#		 p=$[++p]
#		}
#		reply=(`sed -n "/[^A-z]/s/:.*//p" $table`)
#	}
#	compctl -g "(.|)*(-/)" -x \
#	        's[-]' -k (d l L e c n N p P x X s b q f V r a) - \
#	        'c[-1,-f]' -f - \
#	        'c[-1,-V]' -K glob_syncdir                      -- syncdir
# 
# $B!Z<U<-![(B
# 
# $B0J2<$NJ}!9$K8f6(NO$rD:$-$^$7$?!#$3$3$K46<U?=$7>e$2$^$9!#(B
# $B!&0KLnED>0;K$5$s(B($BN)L?4[Bg3X(B)
# $B!&LSMx$3$&$$$A$5$s(B($BN)L?4[Bg3X(B)
# $B!&NkLZGn;K$5$s(B($B%F%l%SD+F|(B)
# $B!&(BHONDA Takashi $B$5$s(B(NTT)
# $B!&EDCfNICN$5$s(B
# $B!&2,EDLwB'$5$s(B($B@iMUBg(B)
# $B!&GpLZ2m1Q$5$s(B($BAa0pEDBg3X(B)
# $B!&B<ED%N0B1J$5$s(B($B@8M}3X8&5f=j(B)
# $B!&LyE/O:$5$s(B($BBg:eI\N)Bg3X(B)
# $B!&(Bbsdusers$B%a%$%j%s%0%j%9%H$N$_$J$5$s(B
# ($B=jB0$OEv;~$N$b$N(B)
# 
# $B!ZLH@U![(B
# 
# $B$3$N%9%/%j%W%H$O40A4$KF0:n$9$k$3$H$r4j$C$F:n$i$l$F$$$^$9$,!"$=$NJ]>Z$O(B
# $B$G$-$^$;$s!#$3$N%9%/%j%W%H$K$h$C$F$b$?$i$5$l$?7k2L$K$D$$$F$O:n<T$O@UG$(B
# $B$rIi$$$+$M$^$9$N$G$4Cm0U$/$@$5$$!#$H$/$K$3$N%9%/%j%W%H$NFCD9$G$b$"$k(B d 
# $B%*%W%7%g%s$O!"%U%!%$%k>C5n$rH<$&$?$a!"8mA`:n$@$1$G$J$/:n<T$NM=A[$7$F$$(B
# $B$J$$FC<l$J4D6-$G$NMxMQ$O4m81$r5Z$\$92DG=@-$,$"$j$^$9!#$3$N%9%/%j%W%H$r(B
# $B$D$+$C$F%_%i!<%j%s%0$7$?$$$H;W$&%G%#%l%/%H%j$O$+$J$jBg@Z$J>l=j$G$7$g$&(B
# $B$+$i!":G=i$KMxMQ$9$k;~$O(B($BJL<jCJ$G(B)$B%3%T!<%G%#%l%/%H%j$r$D$/$C$F$=$3$G;n(B
# $B$7$F$_$k$J$I$7$F$/$@$5$$!#(B


($myname= $0) =~ s,.*[/\\],,;
$quiet=0;
$debug=0;
$unix = (eval 'symlink("","");', $@ eq '');
$bufsize=1024*1024;
$table=$ENV{'SYNCDIRTABLE'} || "~/.syncdir";
$usage = <<_eou_;
Synchronize two directories.
Usage:	$myname [-Options] SrcDir DestDir
     or $myname [-Options] -V VolumeName
     or $myname [-Options] -a
      Options are...
	-d 	Delete files in DestDir that are nonexistent in SrcDir
	-l 	Chase files symlinks point to
	-L 	Chase directories symlinks point to
	-e	Copy Empty symlink files
	-c	Complete consistency mode
	-n 	No execute, print jobs to stdout
	-N 	Same as -n, except -N displays only files to be removed
	-p 	Preserve access time of source files
	-P	Copy time stamp of directories
	-x PAT	Exclude files that match with PAT as perl-regexp
	-X PAT	EXclude pathnames that match with PAT as perl-regexp
	-s	Safe mode.  Copy first, then remove
	-b S	Buffer byte size set to S (suffix M, K available, default=1MB)
	-q	Quiet
	-A	Equivalent to -dpecP
	(Both SrcDir and DestDir directories should exist.)
      Options for the second or third form are...
	-f TableFile	Use TableFile as syncdir table file
	-V VolumeName	Do the job for the volume VolumeName
	-r		Reverse `src' and `dest' for each volume
	-a		Do the job for all volumes
    If -V or -a option is given, $myname reads ~/.syncdir for a
    table file.  A table file's format is as follows:
	home:	src=/usr/home/yuuji;dest=/mo/backup/home
		keyfile=.cshrc;exclude=^trashbox\$;EXCLUDE=\.netscape/cache
    where `src=' and `dest=' specify the source and destination directories,
    `keyfile=' specifies the file which is used to confirm the source
    directory's validity, `exclude=' specifies what -x option does and
    `EXCLUDE=' does -X.
    Multiple volume names can be specified by being delimited with comma 
    as `-V home,www,src'.
_eou_
#'

while ($_ = $ARGV[0], /^-.+/ && shift) {
    last if /^--$/;
    while (/^-[A-z]/) {
	if (/^-p/) {
	    #$backupdir = shift;
	    $atimepr++;
	} elsif (/^-f/) {
	    $table = shift;
	} elsif (/^-d/) {
	    $delete++;
	} elsif (/^-D/) {
	    $debug++;
	} elsif (/^-n/) {
	    $noexec++; $quiet++;
	} elsif (/^-N/) {
	    $noexec++; $quiet++; $noupdate++;
	} elsif (/^-l/) {
	    $chaselinkfile++;
	} elsif (/^-L/) {
	    $chaselinkdir++;
	} elsif (/^-e/) {
	    $emptylink++;
	} elsif (/^-c/) {
	    $complete++;
	} elsif (/^-q/) {
	    $quiet++;
	} elsif (/^-v/) {
	    $quiet=0;
	} elsif (/^-V/) {
	    push(@volume, split(",", shift));
	    $_='';
	} elsif (/^-r/) {
	    $reverse++;
	} elsif (/^-a/) {
	    $all++;
	} elsif (/^-x/) {
	    @exclude = (@exclude, shift);
	} elsif (/^-X/) {
	    @EXCLUDE = (@EXCLUDE, shift);
	} elsif (/^-s$/) {
	    $safe++;
	} elsif (/^-b$/) {
	    $bufsize = shift;
	    if ($bufsize =~ /m$/i) {
		$bufsize *= 1024*1024;
	    } elsif ($bufsize =~ /k$/i) {
		$bufsize *= 1024;
	    }
	    unless ($quiet) {
		print("Bufsize set to ",
		      (($bufsize>1024) ? sprintf("%dKB\n", $bufsize/1024)
		       : sprintf("%dBytes\n", $bufsize)));
	    }
	} elsif (/^-P/) {
	    $dirtime++;
	} elsif (/^-A/) {
	    $delete=$atimepr=$emptylink=$complete=$dirtime=1;
	} else {
	    print $usage;
	    exit 0;
	}
        s/^-.(.*)/-$1/;
    }
}

&readtable if (@volume || $all);
&setall if $all;

sub syncvol {
    local(@volume) = @_;
    foreach $i (@volume) {
	&parsesyncdir($i);
	if ($reverse) {
	    $i=$srcdir;
	    $srcdir=$dstdir;
	    $dstdir=$i;
	}
	if ($srcdir && $dstdir) {
	    printf("#s=%s\n#d=%s\n#k=%s\n#x=%s\n#X=%s",
		   $srcdir, $dstdir, $keyfile,
		   join(",", @exclude), join(",", @EXCLUDE))
		if (!$quiet || $noexec);
	    &syncdir($srcdir, $dstdir, $keyfile);
	}
    }
}

if (@volume) {
    &syncvol(@volume);
} else {
    &syncdir($ARGV[0], $ARGV[1]);
}
# Done.

# -------------------- Subfunctions --------------------
sub syncdir {
    local($src, $dst, $keyfile) = @_;
    unless ($src && -d $src && $dst && -d $dst) {
	warn "$myname: Two existent directories must be specified.\n";
	warn "src=[$src], dest=[$dst]\n";
	die  "Type `$myname -h' to show help.\n";
    }
    if ($keyfile && ! -e "$srcdir/$keyfile") {
	warn "Keyfile `$srcdir/$keyfile' not found.  Skip this volume.\n";
	return;
    }
    $exptn = join('|', @exclude);
    $EXPTN = join('|', @EXCLUDE);
    @visiteddirs=%linkfiles=();
    if ($safe) {
	&updatecopy($src, $dst) unless ($noupdate);
	@visiteddirs=();
	&rmsync($src, $dst) if $delete;
    } else {
	&rmsync($src, $dst) if $delete;
	@visiteddirs=();
	&updatecopy($src, $dst) unless ($noupdate);
    }
}

# Copy newer files.
sub updatecopy {
    local($src, $dst, $file, @dir) = @_;
    local($umask, $s, $d, $di, $lf) = (umask);
    return if ($unix && &loopcheck($src, $dst));
    print "Synchronizing directory [$src] with [$dst]...\n" if $debug;
    opendir(SRCDIR, $src);
    @dir = readdir(SRCDIR);
    closedir(SRCDIR);
    foreach $file (@dir) {
        next if ($file =~ /^\.\.?$/);
	next if ($file =~ /$exptn/);
        $s = "$src/$file";
        $d = "$dst/$file";
	$EXPTN ne "" && next if ($s =~ /$EXPTN/); #1999/11/28 for 5.005_03
	if (-d "$s") {
            ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime,
             $mtime1, $ctime, $blksize, $bloks) = stat(_); # stat($s)
	    if (!$chaselinkdir && $unix && -l $s) {	# if src is symlink
		&buildlink($s, $d);
	    } else {
		if ($unix && -l $d) {
		    #if though $s is real directory, $d is symlink,
		    #remove symlink first
		    if ($noexec) {
			print "rm $d\n";
		    } else {
			unlink($d);
		    }
		}
		umask(000);
		if (!-d $d) {
		    if (-f $d) {	  # if $d is normal file, remove it
			if ($noexec) {
			    print "rm $d\n";
			} elsif (!unlink($d)) {
			    warn "Cannot unlink [$d]\n";
			    next;
			}
		    }
		    if ($noexec) {
			print "mkdir $d\n";
		    } elsif (! mkdir("$d", $mode)) {
			warn "Cannot create [$d]\n";
			next;
		    } else {	# Successful mkdir
			chown($uid, $gid, $d);
			# chmod($mode, $d); #moved below
			utime($atime, $mtime1, $d);
			print "mkdir $d\n" unless $quiet;
		    }
		}
		umask($umask);
		chmod($mode, $d) unless $noexec;
		&updatecopy($s, $d);
		utime($atime, $mtime1, $d) if $dirtime;	#1999/1/6
	    }
        } elsif (-f $s) {
            ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime,
             $mtime, $ctime, $blksize, $bloks) = stat(_); # stat($s)
	    if (!$chaselinkfile && $unix && -l $s) {	# if src is symlink
		&buildlink($s, $d);
	    } elsif ($nlink > 1) {			# if src has hard links
		if ($linkfiles{"$dev*$ino"}) { # already accessed
		    ($di, $lf) = ($linkfiles{"$dev*$ino"} =~ m/([^,]*),(.*)$/);
		    if (! -f $d) {
			if ($noexec) {
			    print "ln $lf, $d\n";
			} else {
			    print "$lf => $d\n" unless $quiet;
			    link($lf, $d);
			}
		    } else {
			($dev2, $ino2) = stat(_); # stat($d)
			if ("$dev2*$ino2" ne $di) {
			    if ($noexec) {
				print "rm -f $d\n";
				print "ln $lf $d\n";
			    } else {
				print "$lf => $d\n" unless $quiet;
				unlink($d);
				link($lf, $d);
			    }
			}
		    }
		} else {	# first access to this file
		    if (! -f $d) {
			&cp($s, $d, $mode, $uid, $gid, $atime, $mtime, $size);
		    }
		    ($dev2, $ino2, $mode2, $nlink, $uid2, $gid2, $rdev, $size2,
		     $atime2, $mtime2, $ctime, $blksize, $bloks) = stat(_);#$d
		    if ($mtime>$mtime2 || ($mtime==$mtime2 && $size>$size2)
			|| $complete &&
			($mtime!=$mtime2 || $size!=$size2 || $mode!=$mode2)) {
			&cp($s, $d, $mode, $uid, $gid, $atime, $mtime, $size);
		    } elsif ($mode != $mode2) {
			unless ($noexec) {
			    chmod($mode, $d);
			}
			if ($noexec || !$quiet) {
			    printf "chmod %o $d\n", $mode;
			}
		    }
		    ($dev2, $ino2) = stat(_); # stat($d)
		    # remember new file's devnum, inum and path name
		    $linkfiles{"$dev*$ino"} = "$dev2*$ino2,$d";
		}
	    } else {		# if src is normal file with link count 1
		if (-l $d) {	# if dest is symlink remove it first!
		    if ($noexec) {
			print "rm $d\n";
		        &cp($s, $d);			# to display job
		    } else {
			unlink($d);
		    }
		}
		if (! -f $d) {
		    &cp($s, $d, $mode, $uid, $gid, $atime, $mtime, $size);
		} else {
		    ($dev, $ino, $mode2, $nlink, $uid2, $gid2, $rdev, $size2,
		     $atime2, $mtime2, $ctime, $blksize, $bloks) = stat(_);#$d
		    if ($mtime>$mtime2 || ($mtime==$mtime2 && $size>$size2)
			|| $complete &&
			($mtime!=$mtime2 || $size!=$size2 || $mode!=$mode2)) {
 			&cp($s, $d, $mode, $uid, $gid, $atime, $mtime, $size);
		    } elsif ($mode !=$mode2) {
			unless ($noexec) {
			    chmod($mode, $d);
			}
			if ($noexec || !$quiet) {
			    printf "chmod %o $d\n", $mode;
			}
		    }
		}
            }
	} elsif (! -e $s && $emptylink) {
	    &buildlink($s, $d);
        } else {
	    warn "$s: not found\n";
	}
    }
}

# Copy file preserving modtime and owner/group.
sub cp {
    local($src, $dst, $mode, $uid, $gid, $atime, $mtime, $size) = @_;
    local($dir, $dirmode, $success);
    $currentfile = $dst;
    print "$src -> $dst\n" unless $quiet;
    if ($noexec) {
        print "cp -p $src $dst\n";
        return;
    }
    #system("cp -p $src $dst");
    open(SRC, "<$src") || warn "Cannot open $src to read.\n";
    binmode(SRC);
    chmod(0700, $d) if (-d $dst);
    if (-d $dst) {
        system("rm -rf '$dst'");
    } else {
        unlink("$dst");
    }
    if (open(DST, ">$dst")) {
	$success=1;
    } else {
	($dir) = ($dst =~ m,(.*)/(.*),);
	$dirmode = (stat($dir))[2];
	unless (($dirmode & 00200) && ($dirmode&00100)) {
	    $dirmode |= 0300;
	    chmod($dirmode, $dir);
	    print STDERR "Adding w/x permission to $dir\n";
	}
	if (open(DST, ">$dst")) {
	    $success=1;
	} else {
	    warn "Cannot open $dst to write.\n";
	}
    }
    if ($success) {
	utime(0, 0, $dst);	# Set dst's time ancient until finish writing.
	binmode(DST);
	$SIG{'INT'} = 'interrupt';
	# print DST while ($_=<SRC>, $_ ne ""); # 1.8(97/6/26)
	print DST while (read(SRC, $_, $bufsize)); # 1.13(1998/1/16)
	close(DST);
	chown($uid, $gid, $dst) if $unix;
	chmod($mode, $dst);
	utime($atime, $mtime, $src) if $atimepr;
	utime($atime, $mtime, $dst);
	if ((stat($dst))[7] != $size) {
	    print STDERR "$dst: Write failed!\n";
	    unlink($dst);
	}
	$SIG{'INT'} = 'DEFAULT';
    }
    close(SRC);
}

sub interrupt {
    unlink($currentfile) if (-f $currentfile);
    die "Abort.\n";
}

# Remove destinations dir's file nonexistent in source dir
sub rmsync {
    local($src, $dst, $f, @entry) = @_;
    return if ($unix && &loopcheck($src, $dst));
    print "Checking nonexistent files in [$dst]...\n" if $debug;
    opendir(DIR, $dst);
    @entry=readdir(DIR);
    closedir(DIR);
    foreach $f (@entry) {
	next if ($f =~ /$exptn/);
        next if ($f =~ /^\.\.?$/);
	$EXPTN ne "" && next if ("$dst/$f" =~ /$EXPTN/);
        if (-d "$dst/$f" && (! $unix || ! -l "$src/$f")) {
            if (-d "$src/$f") {
                &rmsync("$src/$f", "$dst/$f");
            } else {
                print "Removing destination dir [$dst/$f]\n" unless $quiet;
                if ($noexec) {
                    print "rm -rf $dst/$f\n";
                } else {
                    system("rm -rf '$dst/$f'");
                }
            }
        } else {
            unless (-e "$src/$f" || ($unix && -l "$src/$f")) {
                print "Removing destination file [$dst/$f]\n" unless $quiet;
		if ($noexec) {
		    print "rm -f $dst/$f\n";
		} else {
                    unlink("$dst/$f") || warn "Cannot unlink $dst/$f\n";
		}
            }
        }
    }
}

sub buildlink {
    local($src, $dest, $link) = @_;
    local($ui, $gi, $at, $mt);
    $link = readlink($src);
    unless (-l $dest && readlink($d) eq $link) {
	if ($noexec) {
	    print "rm -rf $d\n" if (-e $d);
	    print "ln -s $link $d\n";
	} else {
	    #unlink($d);
	    #in case if $d is directory, we call rm -rf
	    system("rm -rf '$d'");
	    symlink($link, $d) || warn "Cannot create link $d\n";
	    ($ui, $gi, $at, $mt) = (lstat($src))[4,5,8,9];
	    ### chown($ui, $gi, $d);  #commented out at 1.11
	    # utime($at, $mt, $d); # Is this impossible??
	    print "${d}\@ -> $link\n" unless $quiet;
	}
    }
}

sub loopcheck {
    local($d, $D);
    foreach $d (@_) {
	($dev, $ino) = stat($d);
	$D = "$ino*$dev";
	if (grep($_ eq $D, @visiteddirs)) {
	    warn "Already visited to [$src].  Maybe link loop($dev, $ino).\n";
	    return 1;
	}
	@visiteddirs = (@visiteddirs, $D);
    }
    return 0;
}

sub readtable {
    $table =~ s/~/$ENV{'HOME'}/;
    unless (@contents) {
	open(TABLE, $table) || die "No $table found.\n";
	@contents = <TABLE>;
	close(TABLE);
    }
}

sub setall {
    undef @volume;
    foreach (@contents) {
	if (/^([^:]+):/) {
	    @volume = (@volume, $1);
	}
    }
}

sub parsesyncdir {
    local($volume) = @_;
    local($var, $val, @c, $l);
    $srcdir = $dstdir = $keyfile = @exclude = @EXCLUDE= (); # 97/2/3

    @c=@contents;
    for ($l=0; $l<@c && $c[$l] !~ /^$i:/; $l++) {}
    for (; $l<@c; $l++) {
	$_ = $c[$l];
	last if (/^\S+:/ && !/^$i:/);
	chop;
	if (/$i:\s*([^\#=]+)$/) { # recursive volume call
	    $val = $1;
	    &syncvol(split(/, */, $val));
	    $srcdir = $dstdir = '';
	} else {
	    while (s/([a-z]+=)([^;]+)//) {
		if (/^[ \t]*\#/) {$_=''; last;}
		$var="$1", $val=$2;
		$val =~ s,~/,$ENV{'HOME'}/,;
		$srcdir=$val if ($var =~ m/src/);
		$dstdir=$val if ($var =~ m/dest/);
		$keyfile=$val  if ($var =~ m/keyfile/);
		@exclude=(@exclude, $val)  if ($var =~ m/exclude/);
		@EXCLUDE=(@EXCLUDE, $val)  if ($var =~ m/EXCLUDE/);
	    }
	}
    }
}
__END__
Local Variables:
fill-prefix:	"# "
End:
