Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/INIT/ditto.sh
1808 views
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 <[email protected]> #
18
# #
19
########################################################################
20
: replicate directory hierarchies
21
22
COMMAND=ditto
23
case `(getopts '[-][123:xyz]' opt --xyz; echo 0$opt) 2>/dev/null` in
24
0123) 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
67
source 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
;;
75
esac
76
77
usage()
78
{
79
OPTIND=0
80
getopts $ARGV0 "$USAGE" OPT '-?'
81
exit 2
82
}
83
84
parse() # 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
119
typeset -A chown chmod
120
typeset tw cp rm link
121
integer ntw=0 ncp=0 nrm=0 nlink=0 n
122
123
typeset src_user src_host src_path src_type src_uid src_gid src_perm src_sum
124
typeset dst_user dst_host dst_path dst_type dst_uid dst_gid dst_perm dst_sum
125
integer src_size src_mtime src_eof
126
integer dst_size dst_mtime dst_eof
127
128
integer debug=0 delete=0 exec=1 mode=1 owner=0 update=0 verbose=0 logical
129
130
typeset remote=ssh trace
131
typeset checksum='"-"' pax="pax"
132
typeset paxreadflags="" paxwriteflags="--write --format=tgz --nosummary"
133
134
tw[ntw++]=tw
135
(( logical=ntw ))
136
tw[ntw++]=--logical
137
tw[ntw++]=--chop
138
tw[ntw++]=--ignore-errors
139
tw[ntw++]=--expr=sort:name
140
141
# grab the options
142
143
while getopts $ARGV0 "$USAGE" OPT
144
do 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
158
done
159
shift $OPTIND-1
160
if (( $# != 2 ))
161
then usage
162
fi
163
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);'\'
164
if (( exec ))
165
then
166
paxreadflags="$paxreadflags --read"
167
fi
168
if (( verbose ))
169
then
170
paxreadflags="$paxreadflags --verbose"
171
fi
172
173
# start the source and destination path list generators
174
175
parse src "$1"
176
parse 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
181
if [[ $src_host ]]
182
then ($remote $src_user $src_host "{ test ! -f .profile || . ./.profile ;} && cd $src_dir && read && ${tw[*]}") 2>&1 |&
183
else (cd $src_dir && read && eval "${tw[@]}") 2>&1 |&
184
fi
185
exec 5<&p 7>&p
186
print -u7 sync
187
exec 7>&-
188
189
if [[ $dst_host ]]
190
then ($remote $dst_user $dst_host "{ test ! -f .profile || . ./.profile ;} && cd $dst_dir && read && ${tw[*]}") 2>&1 |&
191
else (cd $dst_dir && read && eval "${tw[@]}") 2>&1 |&
192
fi
193
exec 6<&p 7>&p
194
print -u7 sync
195
exec 7>&-
196
197
# scan through the sorted path lists
198
199
if (( exec ))
200
then
201
src_skip=*
202
dst_skip=*
203
else
204
src_skip=
205
dst_skip=
206
fi
207
src_path='' src_eof=0
208
dst_path='' dst_eof=0
209
ifs=${IFS-$' \t\n'}
210
IFS=$'\t'
211
while :
212
do
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
375
done
376
IFS=$ifs
377
378
(( exec )) || exit 0
379
380
# generate, transfer and execute the { rm chown chmod } script
381
382
if (( ${#rm[@]} || ${#chmod[@]} || ${#chown[@]} ))
383
then
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
}
441
fi
442
443
# generate, transfer and read back the { cp } tarball
444
445
if (( ${#cp[@]} ))
446
then
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
460
fi
461
462