#!/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 </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 </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 </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 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