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