1######################################################################## 2# # 3# This software is part of the ast package # 4# Copyright (c) 1994-2011 AT&T Intellectual Property # 5# and is licensed under the # 6# Eclipse Public License, Version 1.0 # 7# by AT&T Intellectual Property # 8# # 9# A copy of the License is available at # 10# http://www.eclipse.org/org/documents/epl-v10.html # 11# (with md5 checksum b35adb5213ca9657e911e9befb180842) # 12# # 13# Information and Software Systems Research # 14# AT&T Research # 15# Florham Park NJ # 16# # 17# Glenn Fowler <gsf@research.att.com> # 18# # 19######################################################################## 20: replicate directory hierarchies 21 22COMMAND=ditto 23case `(getopts '[-][123:xyz]' opt --xyz; echo 0$opt) 2>/dev/null` in 240123) ARGV0="-a $COMMAND" 25 USAGE=$' 26[-? 27@(#)$Id: ditto (AT&T Labs Research) 2010-11-22 $ 28] 29'$USAGE_LICENSE$' 30[+NAME?ditto - replicate directory hierarchies] 31[+DESCRIPTION?\bditto\b replicates the \asource\a directory hierarchy 32 to the \adestination\a directory hierarchy. Both \asource\a and 33 \adestination\a may be of the form 34 [\auser\a@]][\ahost\a:]][\adirectory\a]]. At least one of 35 \ahost\a: or \adirectory\a must be specified. The current user is used 36 if \auser@\a is omitted, the local host is used if \ahost\a: is 37 omitted, and the user home directory is used if \adirectory\a is 38 omitted.] 39[+?Remote hosts and files are accessed via \bssh\b(1) or \brsh\b(1). \bksh\b(1), 40 \bpax\b(1), and \btw\b(1) must be installed on the local and remote hosts.] 41[+?For each source file \bditto\b does one of these actions:]{ 42 [+chmod|chown?change the mode and/or ownership of the destination 43 file to match the source] 44 [+copy?copy the source file to the destination] 45 [+delete?delete the destination file] 46 [+skip?the destination file is not changed] 47} 48[+?The source and destination hierarchies are generated by \btw\b(1) with 49 the \b--logical\b option. An \b--expr\b option may 50 be specified to prune the search. The \btw\b searches are relative to 51 the \asource\a and \adestination\a directories.] 52[c:checksum?Copy if the \btw\b(1) 32x4 checksum mismatches.] 53[d:delete?Delete \adestination\a files that are not in the \asource\a.] 54[e:expr?\btw\b(1) select expression.]:[tw-expression] 55[m!:mode?Preserve file mode.] 56[n:show?Show the operations but do not exectute.] 57[o:owner?Preserve file user and group ownership.] 58[p:physical?Generate source and destination hierarchies by \btw\b(1) with 59 the \b--physical\b option.] 60[r:remote?The remote access protocol; either \bssh\b or 61 \brsh\b.]:[protocol:=ssh] 62[u:update?Copy only if the \asource\a file is newer than the 63 \adestination\a file.] 64[v:verbose?Trace the operations as they are executed.] 65[D:debug?Enable the debug trace.] 66 67source destination 68 69[+SEE ALSO?\brdist\b(1), \brsync\b(1), \brsh\b(1), \bssh\b(1), \btw\b(1)] 70' 71 ;; 72*) ARGV0="" 73 USAGE="de:[tw-expression]mnouvD source destination" 74 ;; 75esac 76 77usage() 78{ 79 OPTIND=0 80 getopts $ARGV0 "$USAGE" OPT '-?' 81 exit 2 82} 83 84parse() # id user@host:dir 85{ 86 typeset id dir user host 87 id=$1 88 dir=$2 89 (( debug || ! exec )) && print -r $id $dir 90 if [[ $dir == *@* ]] 91 then 92 user=${dir%%@*} 93 dir=${dir#${user}@} 94 else 95 user= 96 fi 97 if [[ $dir == *:* ]] 98 then 99 host=${dir%%:*} 100 dir=${dir#${host}:} 101 else 102 host= 103 fi 104 if [[ $user ]] 105 then 106 user="-l $user" 107 if [[ ! $host ]] 108 then 109 host=$(hostname) 110 fi 111 fi 112 eval ${id}_user='$user' 113 eval ${id}_host='$host' 114 eval ${id}_dir='$dir' 115} 116 117# initialize 118 119typeset -A chown chmod 120typeset tw cp rm link 121integer ntw=0 ncp=0 nrm=0 nlink=0 n 122 123typeset src_user src_host src_path src_type src_uid src_gid src_perm src_sum 124typeset dst_user dst_host dst_path dst_type dst_uid dst_gid dst_perm dst_sum 125integer src_size src_mtime src_eof 126integer dst_size dst_mtime dst_eof 127 128integer debug=0 delete=0 exec=1 mode=1 owner=0 update=0 verbose=0 logical 129 130typeset remote=ssh trace 131typeset checksum='"-"' pax="pax" 132typeset paxreadflags="" paxwriteflags="--write --format=tgz --nosummary" 133 134tw[ntw++]=tw 135(( logical=ntw )) 136tw[ntw++]=--logical 137tw[ntw++]=--chop 138tw[ntw++]=--ignore-errors 139tw[ntw++]=--expr=sort:name 140 141# grab the options 142 143while getopts $ARGV0 "$USAGE" OPT 144do case $OPT in 145 c) checksum=checksum ;; 146 d) delete=1 ;; 147 e) tw[ntw++]=--expr=\"$OPTARG\" ;; 148 m) mode=0 ;; 149 n) exec=0 verbose=1 ;; 150 o) owner=1 ;; 151 p) tw[logical]=--physical ;; 152 r) remote=$OPTARG ;; 153 u) update=1 ;; 154 v) verbose=1 ;; 155 D) debug=1 ;; 156 *) usage ;; 157 esac 158done 159shift $OPTIND-1 160if (( $# != 2 )) 161then usage 162fi 163tw[ntw++]=--expr=\''action:printf("%d\t%d\t%s\t%s\t%s\t%-.1s\t%o\t%s\t%s\n", size, mtime, '$checksum', uid, gid, mode, perm, path, symlink);'\' 164if (( exec )) 165then 166 paxreadflags="$paxreadflags --read" 167fi 168if (( verbose )) 169then 170 paxreadflags="$paxreadflags --verbose" 171fi 172 173# start the source and destination path list generators 174 175parse src "$1" 176parse dst "$2" 177 178# the |& command may exit before the exec &p 179# the print sync + read delays the |& until the exec &p finishes 180 181if [[ $src_host ]] 182then ($remote $src_user $src_host "{ test ! -f .profile || . ./.profile ;} && cd $src_dir && read && ${tw[*]}") 2>&1 |& 183else (cd $src_dir && read && eval "${tw[@]}") 2>&1 |& 184fi 185exec 5<&p 7>&p 186print -u7 sync 187exec 7>&- 188 189if [[ $dst_host ]] 190then ($remote $dst_user $dst_host "{ test ! -f .profile || . ./.profile ;} && cd $dst_dir && read && ${tw[*]}") 2>&1 |& 191else (cd $dst_dir && read && eval "${tw[@]}") 2>&1 |& 192fi 193exec 6<&p 7>&p 194print -u7 sync 195exec 7>&- 196 197# scan through the sorted path lists 198 199if (( exec )) 200then 201 src_skip=* 202 dst_skip=* 203else 204 src_skip= 205 dst_skip= 206fi 207src_path='' src_eof=0 208dst_path='' dst_eof=0 209ifs=${IFS-$' \t\n'} 210IFS=$'\t' 211while : 212do 213 # get the next source path 214 215 if [[ ! $src_path ]] && (( ! src_eof )) 216 then 217 if read -r -u5 text src_mtime src_sum src_uid src_gid src_type src_perm src_path src_link 218 then 219 if [[ $text != +([[:digit:]]) ]] 220 then 221 print -u2 $COMMAND: source: "'$text'" 222 src_path= 223 continue 224 fi 225 src_size=$text 226 elif (( dst_eof )) 227 then 228 break 229 elif (( src_size==0 )) 230 then 231 exit 1 232 else 233 src_path= 234 src_eof=1 235 fi 236 fi 237 238 # get the next destination path 239 240 if [[ ! $dst_path ]] && (( ! dst_eof )) 241 then 242 if read -r -u6 text dst_mtime dst_sum dst_uid dst_gid dst_type dst_perm dst_path dst_link 243 then 244 if [[ $text != +([[:digit:]]) ]] 245 then 246 print -u2 $COMMAND: destination: $text 247 dst_path= 248 continue 249 fi 250 dst_size=$text 251 elif (( src_eof )) 252 then 253 break 254 elif (( dst_size==0 )) 255 then 256 exit 1 257 else 258 dst_path= 259 dst_eof=1 260 fi 261 fi 262 263 # determine the { cp rm chmod chown } ops 264 265 if (( debug )) 266 then 267 [[ $src_path ]] && print -r -u2 -f $': src %8s %10s %s %s %s %s %3s %s\n' $src_size $src_mtime $src_sum $src_uid $src_gid $src_type $src_perm "$src_path" 268 [[ $dst_path ]] && print -r -u2 -f $': dst %8s %10s %s %s %s %s %3s %s\n' $dst_size $dst_mtime $dst_sum $dst_uid $dst_gid $dst_type $dst_perm "$dst_path" 269 fi 270 if [[ $src_path == $dst_path ]] 271 then 272 if [[ $src_type != $dst_type ]] 273 then 274 rm[nrm++]=$dst_path 275 if [[ $dst_path != $dst_skip ]] 276 then 277 if [[ $dst_type == d ]] 278 then 279 dst_skip="$dst_path/*" 280 print -r rm -r "'$dst_path'" 281 else 282 dst_skip= 283 print -r rm "'$dst_path'" 284 fi 285 fi 286 fi 287 if [[ $src_type == l ]] 288 then if [[ $src_link != $dst_link ]] 289 then 290 cp[ncp++]=$src_path 291 if [[ $src_path != $src_skip ]] 292 then 293 src_skip= 294 print -r cp "'$src_path'" 295 fi 296 fi 297 elif [[ $src_type != d ]] && { (( update && src_mtime > dst_mtime )) || (( ! update )) && { (( src_size != dst_size )) || [[ $src_sum != $dst_sum ]] ;} ;} 298 then 299 if [[ $src_path != . ]] 300 then 301 cp[ncp++]=$src_path 302 if [[ $src_path != $src_skip ]] 303 then 304 src_skip= 305 print -r cp "'$src_path'" 306 fi 307 fi 308 else 309 if (( owner )) && [[ $src_uid != $dst_uid || $src_gid != $dst_gid ]] 310 then 311 chown[$src_uid.$src_gid]="${chown[$src_uid.$src_gid]} '$src_path'" 312 if [[ $src_path != $src_skip ]] 313 then 314 src_skip= 315 print -r chown $src_uid.$src_gid "'$src_path'" 316 fi 317 if (( (src_perm & 07000) || mode && src_perm != dst_perm )) 318 then 319 chmod[$src_perm]="${chmod[$src_perm]} '$src_path'" 320 if [[ $src_path != $src_skip ]] 321 then 322 src_skip= 323 print -r chmod $src_perm "'$src_path'" 324 fi 325 fi 326 elif (( mode && src_perm != dst_perm )) 327 then 328 chmod[$src_perm]="${chmod[$src_perm]} '$src_path'" 329 if [[ $src_path != $src_skip ]] 330 then 331 src_skip= 332 print -r chmod $src_perm "'$src_path'" 333 fi 334 fi 335 fi 336 src_path= 337 dst_path= 338 elif [[ ! $dst_path || $src_path && $src_path < $dst_path ]] 339 then 340 if [[ $src_path != . ]] 341 then 342 cp[ncp++]=$src_path 343 if [[ $src_path != $src_skip ]] 344 then 345 if [[ $src_type == d ]] 346 then 347 src_skip="$src_path/*" 348 print -r cp -r "'$src_path'" 349 else 350 src_skip= 351 print -r cp "'$src_path'" 352 fi 353 fi 354 fi 355 src_path= 356 elif [[ $dst_path ]] 357 then 358 if (( delete )) 359 then 360 rm[nrm++]=$dst_path 361 if [[ $dst_path != $dst_skip ]] 362 then 363 if [[ $dst_type == d ]] 364 then 365 dst_skip="$dst_path/*" 366 print -r rm -r "'$dst_path'" 367 else 368 dst_skip= 369 print -r rm "'$dst_path'" 370 fi 371 fi 372 fi 373 dst_path= 374 fi 375done 376IFS=$ifs 377 378(( exec )) || exit 0 379 380# generate, transfer and execute the { rm chown chmod } script 381 382if (( ${#rm[@]} || ${#chmod[@]} || ${#chown[@]} )) 383then 384 { 385 if (( verbose )) 386 then 387 print -r -- set -x 388 fi 389 print -nr -- cd "'$dst_dir'" 390 n=0 391 for i in ${rm[@]} 392 do 393 if (( --n <= 0 )) 394 then 395 n=32 396 print 397 print -nr -- rm -rf 398 fi 399 print -nr -- " '$i'" 400 done 401 for i in ${!chown[@]} 402 do 403 n=0 404 for j in ${chown[$i]} 405 do 406 if (( --n <= 0 )) 407 then 408 n=32 409 print 410 print -nr -- chown $i 411 fi 412 print -nr -- " $j" 413 done 414 done 415 for i in ${!chmod[@]} 416 do 417 n=0 418 for j in ${chmod[$i]} 419 do 420 if (( --n <= 0 )) 421 then 422 n=32 423 print 424 print -nr -- chmod $i 425 fi 426 print -nr -- " $j" 427 done 428 done 429 print 430 } | { 431 if (( ! exec )) 432 then 433 cat 434 elif [[ $dst_host ]] 435 then 436 $remote $dst_user $dst_host sh 437 else 438 $SHELL 439 fi 440 } 441fi 442 443# generate, transfer and read back the { cp } tarball 444 445if (( ${#cp[@]} )) 446then 447 { 448 cd $src_dir && 449 print -r -f $'%s\n' "${cp[@]}" | 450 $pax $paxwriteflags 451 } | { 452 if [[ $dst_host ]] 453 then 454 $remote $dst_user $dst_host "{ test ! -f .profile || . ./.profile ;} && { test -d \"$dst_dir\" || mkdir -p \"$dst_dir\" ;} && cd \"$dst_dir\" && gunzip | $pax $paxreadflags" 455 else 456 ( { test -d "$dst_dir" || mkdir -p "$dst_dir" ;} && cd "$dst_dir" && gunzip | $pax $paxreadflags ) 457 fi 458 } 459 wait 460fi 461