-
Notifications
You must be signed in to change notification settings - Fork 2
/
fcp
executable file
·248 lines (230 loc) · 7.25 KB
/
fcp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#!/bin/sh
# TODO:
# * Look into ssh-hp :). (ie the slowness from ssh may just be from small
# buffers).
# * bring back stderr, currently there is no notification of errors, not even
# ENODIR or device full.
# * fcp src1 src2 src3 ... dst/ doesn't work (but glob does).
# Need to take the last positional arg as dest (and unset it) then use
# positional args at sources. Go through each one and make sure they are all
# local because we don't support mixed remote and local.
# * Doesn't work when the remote end is android. Although if I ssh over and run
# the fcp_ script manually it does work!?! (Maybe something about android
# tar, saw something else (what?) making a fifo instead of pipe.)
# * There are a couple of calls to eval in which any redirect characters
# (angle brackets) could lose our output. They need to be escaped.
# * Not sure about the handling of paths ending in slash or not is proper.
# (Eg where we rename the dest file.)
# I assume if it has no slash it the directory being sent should be renamed
# to the destination name
# (unless it is an existing directory on the dest host).
# * If the receiving (connecting) nc is started before the sending (server)
# one it will wait for one second and try again. They are started in the
# right order but if there are delays in starting the interpreter or
# something that sleep may have to be changed to something more robust.
# * If you want something more sophisticated look at http://www.slac.stanford.edu/~abh/bbcp/
portno=4365
comp=""
usage() {
cat <<EOM
Usage: $(basename $0) [-c] [-p portno] [--] [srchost:]srcpath [[dsthost:]dstpath]
Copy files between hosts on a network. Uses tar and netcat (nc) for
transferring data and ssh for setting up the transport.
\`dstpath\` defaults to the current directory, use "-" to get a tarball on
stdout.
Options:
-c Tell tar to use gzip compression.
-p portno Port used by netcat. Defaults to $portno.
EOM
}
a1="$1"
# Do a seperated gotopt run so we cat get the return code.
getopt -Q -o hcp: -l help -- "${@}" >/dev/null || {
ret=$?
usage
exit $ret
}
eval "set -- $(getopt -o hcp: -l help -- "${@}")"
# Sometimes set and/or getopt leave a -- there when it shouldn't be
[ "$a1" != "--" ] && [ "$1" = "--" ] && shift
while [ -n "$1" ];do
case "$1" in
-h|--help)
usage
exit
;;
-c) comp="-z" ;;
-p)
portno="$2"
shift
;;
--)
shift
break
;;
-*)
echo "Unrecognized argument: $1"
usage
exit 1
;;
*) break ;;
esac
shift
done
while [ -n "$1" ];do
case "$1" in
*)
if [ -z "$srcpath" ];then
srcpath="$1"
elif [ -z "$dstpath" ];then
dstpath="$1"
else
echo "Too many positional arguments (expected 2)." >&2
echo "Try escaping any globs in the destination path." >&2
usage
exit 1
fi
;;
esac
shift
done
if [ -z "$srcpath" ];then
usage
exit 1
fi
[ "$srcpath" = "." ] && srcpath="./"
[ "$srcpath" = ".." ] && srcpath="../"
[ "$dstpath" = "." ] && dstpath="./"
[ "$dstpath" = ".." ] && dstpath="../"
[ -z "$dstpath" ] && dstpath="./"
case "$srcpath" in
*\\:*) continue ;;
*:*)
srchost="${srcpath%%:*}"
srcpath="${srcpath#*:}"
;;
esac
case "$dstpath" in
*\\:*) continue ;;
*:*)
dsthost="${dstpath%%:*}"
dstpath="${dstpath#*:}"
;;
esac
sendcmd() {
dsthost="${1#*@}"
srcpath="$2"
srcdir="$(dirname "$srcpath")"
srcbase="$(basename "$srcpath")"
dstpath="$3"
[ "${dstpath%/}" = "$dstpath" ] && dstbase="$(basename "$dstpath")"
cat <<EOS
#!/bin/sh
srcdir="\$(eval echo "${srcdir}")"
cd "\$srcdir" || echo \$?
# Use find here to expand any globs. Eval doesn't like filenames with parens
# in them. This can leave us with multiple newline seperated files hence we
# pass them to tar below via stdin.
srcbase="\$(find . -maxdepth 1 -name "${srcbase}" -exec basename {} \\;)"
# Fallback in case the above failed for whatever reason.
[ -z "\$srcbase" ] && srcbase="${srcbase}"
if ! [ -d "\$srcdir/\$srcbase" ] && [ -n "$dstbase" ] && [ "$dstbase" != "-" ] ;then
trans="--transform=s/\${srcbase}\$/$dstbase/"
else
# dummy argument so there isn't an empty "" passed to tar
trans=--check-device
fi
tar --help 2>/dev/null | grep -q check-device || trans=v
pv=pv
type pv >/dev/null 2>&1 || pv=cat
# Try nc twice, with a one second timeout. In case we get called before the
# remote end has started up. May need to come up with something more
# sophisticated in situations where we have to wait longer for the initial
# setup.
echo "\$srcbase" | tar -cT- ${comp} "\$trans" |
\$pv | ( nc -q 0 -w 3 "${dsthost#*@}" $portno 2>/dev/null ||
{ sleep 1 && nc -q 0 -w 3 "${dsthost#*@}" $portno ; } )
EOS
}
recvcmd() {
dstpath="$1"
srcbase="$(basename "$2")"
cat <<EOS
#!/bin/sh
dstpath="\$(eval echo ${dstpath})"
dstbase="\$(basename "\${dstpath}")"
if [ -d "\$dstpath" ] || [ "\${dstpath%/}" != "\$dstpath" ] ;then
dstdir="\$dstpath"
# dummy argument so there isn't an empty "" passed to tar
trans=--check-device
else
# Strip trailing component and rename the incoming root object to that.
# I hope that is what was intended. If not make sure dstpath is
# terminated with a slash (/).
dstdir="\$(dirname "\$dstpath")"
trans="--transform=s/^\([\.\/]*\)${srcbase}/\\1\$dstbase/"
fi
tar --help 2>/dev/null | grep -q check-device || trans=-v
pv=pv
type pv >/dev/null 2>&1 || pv=cat
if [ "\$dstbase" != '-' ] ;then
mkdir -p "\$dstdir" || exit \$?
cd "\$dstdir" || exit \$?
fi
nc -lp $portno -w 20 | \$pv | {
if [ "\$dstbase" != '-' ] ;then
tar -x ${comp} "\$trans"
else
cat
fi
}
EOS
}
rpc() {
ssh "$1" "export ff=/tmp/fcp_$$_\$\$_cmd ; cat > \$ff ; nohup sh -c \"sh \$ff && rm -f \$ff\" </dev/null >/dev/null 2>&1 &"
[ $? -ne 0 ] && echo "Try escaping any ':' in a path if it is not supposed to be a host seperator." >&2
}
myip() {
# dig and nslookup and hosts aren't on all machines and don't look in
# /etc/hosts. Getent isn't everywhere and sometimes returns ipv6 addreses on
# non-ipv6 enabled hosts. getent ahostsv4 I don't know how prevalent that is.
tip="$(ping -c1 -W0.1 ${1#*@} 2>&1 | tr -d '():' | awk '/^PING/{print $3}')"
[ -n "$tip" ] &&
ip route get "$tip" | sed -n 's/^.*src \([^ ]*\).*$/\1/p'
}
if [ -n "$dsthost" ] && [ -n "$srchost" ];then
recvcmd "$dstpath" "$srcpath" | rpc "$dsthost"
sendcmd "$dsthost" "$srcpath" "$dstpath" | rpc "$srchost"
exit $?
elif [ -n "$dsthost" ];then
recvcmd "$dstpath" "$srcpath" | rpc "$dsthost"
sendcmd "$dsthost" "$srcpath" "$dstpath" > /tmp/fcp_$$_cmd
sh /tmp/fcp_$$_cmd
rm /tmp/fcp_$$_cmd
exit $?
elif [ -n "$srchost" ];then
dsthost="$(myip "$srchost")"
[ -z "$dsthost" ] && {
echo "Couldn't find route to $srchost" >&2
exit 1
}
recvcmd "$dstpath" "$srcpath" > /tmp/fcp_$$_cmd
sh /tmp/fcp_$$_cmd &
sendcmd "$dsthost" "$srcpath" "$dstpath" | rpc "$srchost"
wait
rm /tmp/fcp_$$_cmd
exit $?
else
srcdir="$(dirname "$srcpath")"
srcbase="$(basename "$srcpath")"
if [ -d "$dstpath" ] || [ "${dstpath%/}" != "$dstpath" ];then
dstdir="$dstpath"
else
dstdir="$(dirname "$dstpath")"
fi
# TODO: Handle file renames.
dstpath="`eval echo ${dstpath}`"
mkdir -p "$dstpath"
tar -C "$srcdir" -c "$srcbase" | tar -C "$dstdir" -x
exit $?
fi