#!/usr/local/bin/perl
#
#	nuttscp/nuttrcp						v2.3
#
#	Copyright(c) 2008-2016 Bill Fink.  All rights reserved.
#
#	nuttscp/nuttrcp is free, opensource software.  You can
#	redistribute it and/or modify it under the terms of Version 2
#	of the GNU General Public License (GPL), as published by the
#	GNU Project (http://www.gnu.org).  A copy of the license can
#	also be found in the LICENSE file.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	copy files to/from remote host using nuttcp
#
#	Usage: nuttscp or nuttscp -h      prints this usage info
#	Usage: nuttscp -V                 reports nuttscp version
#	Usage: nuttscp [options] infile [user@]host[/datahost]:outfile
#	   or: nuttscp [options] [user@]host[/datahost]:infile outfile
#	      options: -w window              default=unspecified
#	               -N num_streams         default=1
#	               -P ctlport             default=5100
#	               -p dataport            default=5101 (5001 nuttcp < 6.2.8)
#	               -l buflen              default=64KB
#	               -n numbuf              default=unlimited
#	               -T timeout             default=none
#	               -i interval            default=no interval reports
#	               -I identifier          default=no identifier
#	               -R rate_limit          default=unlimited
#	               -M mss                 default=unspecified
#	               -x extra_option        default=unspecified
#	               -b brief_option        default=standard brief output
#	               -f                     default=remote nuttcp server
#	               -F                     default=nuttcp flip off
#	               -4                     default=auto IPv4/IPv6
#	               -6                     default=auto IPv4/IPv6
#	               -v                     default=no verbose nuttcp
#	               -z                     default=no zero copy
#	               -d                     default=no direct I/O
#	               --copy-dir             default=copy file
#	                   --verbose          default=not verbose
#	                   --compressed       default=not compressed
#	                   --one-file-system  default=stay on local fs
#	                   --tar-blocking     default=128
#	               --remote-shell shell   default=same as local
#	               --remote-nuttcp path   default=nuttcp
#	               --datahost host        default=same as ssh/rsh host
#	               --ssh-id identity      default=unspecified
#	               --ssh-port port        default=22
#	               --debug                default=debugging off
#	               --noexec               default=off
#	               --clobber              default=noclobber
#	               --noclobber            default=noclobber
#
#	       infile may be a pipe:          "command |"
#	       outfile may be a pipe:         "| command"
#
#	Run nuttscp/nuttrcp with no arguments to get a usage statement
#
#	If invoked as nuttrcp will use rsh instead of ssh to start
#	remote oneshot nuttcp server
#
#	Developed by Bill Fink, billfink@mindspring.com
#
#	2.3, Bill Fink, 9-Sep-16
#		Fix to allow direct I/O on block devices
#		Suppress banner message for nuttscp (ssh)
#		Remove tabs at end of line
#		Updated Copyright notice
#	2.2, Bill Fink, 21-Feb-12
#		Updated Copyright notice
#		Add nuttcp "-d" option to support direct I/O on Linux
#		Add "--tar-blocking" option to set tar blocking factor
#		Increase default tar blocking factor from 20 to 128
#		Don't give direct I/O error for /dev/zero
#	2.1, Bill Fink, 26-Jan-10
#		Updated Copyright notice
#		Add nuttcp "-x" extras option
#		Add nuttcp "-b" brief option
#		Add "-z" zero copy option
#		Allow "host+addr_stride" format for multilink aggregation
#		Resolve $myipaddr to $myhostname for "-f" multilink aggregation
#		Don't give "File exists" error for /dev/null
#	2.0, Bill Fink, 23-Aug-09
#		Not doing correct check for using nuttrcp
#		Add "-f" option to use local nuttcp server (remote client)
#		Fix incorrect error checking affecting piped get case
#		Add nuttcp "-I" identifier option
#	1.7, Bill Fink, 31-Jul-09
#		Say "something went wrong" if can't execute remote nuttcp
#		Add comment that bash syntax is required for perl "system" exec
#		Support "nuttscp file remote:" for regular file & empty outfile
#	1.6, Bill Fink, 30-Jul-09
#		Add undocumented "-i" nuttcp interval option
#		Add "--remote-nuttcp" option to set remote nuttcp path
#		Add "--ssh-id"/"--ssh-port" options to set ssh identity and port
#		Update usage to reflect new nuttcp default data port of 5101
#	1.5, Bill Fink, 24-Mar-09
#		Allow separate ssh/rsh and nuttcp data channels
#		(using "--datahost" option or ctlhost/datahost syntax)
#		Added "-v" option for nuttcp verbose output
#	1.4, Bill Fink, 26-Aug-08
#		Change required nuttcp version to non-beta 6.1.1
#	1.3, Bill Fink, 20-Aug-08
#		Allow infile and outfile to be pipes
#		Add "--copy-dir" option to copy directories
#		Add "--verbose" option to verbosely copy directories
#		Add "--compressed" option to copy directories with compression
#		Add "--one-file-system" option to copy directories on local fs
#		Add "-V" option to get nuttscp version
#		Add "-h" option to print usage info
#		Only add "-f-beta" to nuttcp command if using beta version
#		Added GPL license notice
#	1.2.1, Bill Fink, 16-Aug-08
#		Style changes to make it more like UNIX C code
#	1.2, Bill Fink, 16-Aug-08
#		Handle copying file to a directory
#		Extend "--clobber" and "--noclobber" to remote files
#		Added "-4" and "-6" options to force using IPv4 or IPv6
#	1.1, Bill Fink, 14-Aug-08
#		Check for required nuttcp version of at least 6.0.6
#		Added "--remote-shell" parameter to specify remote user shell
#		Added "-n" option to specify numbuf (or numbytes)
#		Allow user@host: syntax like scp/rcp
#		Added "-F" option to flip direction of data connection open
#		Added check for ssh/rsh command failure (rsh always returns 0)
#		Added "--debug" and "--noexec" options for debugging
#		Gracefully handle problems with local file
#		Added "--clobber" and "--noclobber options"
#		Added ChangeLog and other boilerplate
#	1.0, Bill Fink, 13-Aug-08
#		Initial version (modeled after scp/rcp)

use Socket qw( SOCK_DGRAM SOCK_STREAM PF_INET AF_INET );

$nuttscp_vers = "2.3";

#		default noclobber setting (change if desired)
$def_noclobber	= 1;

#		default control port for nuttcp to use (change if desired)
$def_ctlport	= 5100;

#		Minimum required nuttcp version
$vers_min	= 60101;
$vers_min_str	= "6.1.1";

#		Note: If invoked as nuttrcp will use rsh instead of ssh
#		      to start remote oneshot nuttcp server
$rsh		= "ssh -o loglevel=error";

#		local nuttcp command
$lnuttcp	= "nuttcp";

$nuttcp_vers = `$lnuttcp -V`;

if ($nuttcp_vers =~ /beta/) {
	$lnuttcp .= " -f-beta";
}

#		remote nuttcp command
$rnuttcp	= "nuttcp";

#		remote user shell - set if different than local user shell
#		e.g. if local shell is tcsh but remote is bash set to "bash"
$rshell		= "";

#		build local command to execute in $lcmd
$lcmd		= "";

#		build remote command to execute in $rcmd
$rcmd		= "";

#		build client args for client nuttcp command
$clientargs	= "";

#		default parameter settings
$window		= "0";
$num_streams	= 1;
$ctlport	= $def_ctlport;
$dataport	= 5101;
$buflen		= 65536;
$numbuf		= 0;
$timeout	= 0;
$interval	= 0;
$ratelimit	= 0;
$mss		= 0;
$xopt		= "";
$briefopt	= "";
$zerocopy	= 0;
$directio	= 0;
$debug		= 0;
$noexec		= 0;
$noclobber	= $def_noclobber;
$flip		= 0;
$af		= AF_INET;
$copy_dir	= 0;
$intaropts	= "-cf";
$outtaropts	= "-xpf";
$one_fs		= 0;
$tarblock	= "128";
$datahost	= "";

if ($0 =~ "nuttrcp") {
	$rsh = "rsh";
}

$nuttcp_vers =~ s/^nuttcp-v?//;
$nuttcp_vers =~ s/-.*$//;

($vers_major, $vers_minor, $vers_delta) = split(/\./, $nuttcp_vers);

$nuttcp_vers = 10000*$vers_major + 100*$vers_minor + $vers_delta;

if ($nuttcp_vers < $vers_min) {
	print "Error: nuttscp/nuttrcp needs at least version $vers_min_str "
			. "of nuttcp\n";
	print "       make sure remote version of nuttcp is also at least "
			. "$vers_min_str\n";
	exit( 1 );
}

while (@ARGV[0] =~ /^-/) {
	PARSE_PTIONS:
	{
		if (@ARGV[0] eq "-w") {
			shift;
			$window = @ARGV[0];
			$clientargs .= " -w $window";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-N") {
			shift;
			$num_streams = @ARGV[0];
			$clientargs .= " -N $num_streams";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-P") {
			shift;
			$ctlport = @ARGV[0];
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-p") {
			shift;
			$dataport = @ARGV[0];
			$clientargs .= " -p $dataport";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-l") {
			shift;
			$buflen = @ARGV[0];
			$clientargs .= " -l $buflen";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-n") {
			shift;
			$numbuf = @ARGV[0];
			$clientargs .= " -n $numbuf";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-T") {
			shift;
			$timeout = @ARGV[0];
			$clientargs .= " -T $timeout";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-i") {
			shift;
			$interval = @ARGV[0];
			$clientargs .= " -i $interval";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-I") {
			shift;
			$identifier = @ARGV[0];
			$clientargs .= " -I '$identifier'";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-R") {
			shift;
			$rate_limit = @ARGV[0];
			$clientargs .= " -R $rate_limit";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-M") {
			shift;
			$mss = @ARGV[0];
			$clientargs .= " -M $mss";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-x") {
			shift;
			$xopt = @ARGV[0];
			$clientargs .= " -x$xopt";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-b") {
			shift;
			$briefopt = @ARGV[0];
			$clientargs .= " -b$briefopt";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-f") {
			shift;
			$flip = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-F") {
			shift;
			$clientargs .= " -F";
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-4") {
			shift;
			$af = AF_INET;
			$clientargs .= " -4";
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-6") {
			shift;
			$af = AF_INET6;
			$clientargs .= " -6";
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-v") {
			shift;
			$clientargs .= " -v";
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-z") {
			shift;
			if (`uname` !~ /[Ll]inux/) {
				print "Warning: zero copy not supported on "
				      . `uname`;
			}
			$zerocopy = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-d") {
			shift;
			if (`uname` !~ /[Ll]inux/) {
				print "Warning: direct I/O not supported on "
				      . `uname`;
			}
			$directio = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "-V") {
			print "nuttscp-$nuttscp_vers\n";
			exit( 0 );
		}
		if (@ARGV[0] eq "-h") {
			&usage();
			exit( 0 );
		}
		if (@ARGV[0] eq "--copy-dir") {
			shift;
			$copy_dir = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--verbose") {
			shift;
			$outtaropts .= "v";
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--compressed") {
			shift;
			$intaropts .= "z";
			$outtaropts .= "z";
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--one-file-system") {
			shift;
			$one_fs = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--tar-blocking") {
			shift;
			$tarblock = @ARGV[0];
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--remote-shell") {
			shift;
			$rshell = @ARGV[0];
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--remote-nuttcp") {
			shift;
			$rnuttcp = @ARGV[0];
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--datahost") {
			shift;
			$datahost = @ARGV[0];
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--ssh-id") {
			shift;
			$rsh = "$rsh -i @ARGV[0]";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--ssh-port") {
			shift;
			$rsh = "$rsh -p @ARGV[0]";
			shift;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--debug") {
			shift;
			$debug = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--noexec") {
			shift;
			$noexec = 1;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--clobber") {
			shift;
			$noclobber = 0;
			last PARSE_PTIONS;
		}
		if (@ARGV[0] eq "--noclobber") {
			shift;
			$noclobber = 1;
			last PARSE_PTIONS;
		}
		print "$0: unknown option: \"@ARGV[0]\"\n";
		print "$0: unknown option: \"@ARGV[0]\"\n";
		&usage();
		exit( 1 );
	}
}

if ($flip) {
	if ($zerocopy) {
		if ($directio) {
			$lcmd = "$lnuttcp -1 -szd";
		}
		else {
			$lcmd = "$lnuttcp -1 -sz";
		}
	}
	else {
		if ($directio) {
			$lcmd = "$lnuttcp -1 -sd";
		}
		else {
			$lcmd = "$lnuttcp -1 -s";
		}
	}
}
else {
	if ($zerocopy) {
		if ($directio) {
			$lcmd = "$lnuttcp -szd$clientargs";
		}
		else {
			$lcmd = "$lnuttcp -sz$clientargs";
		}
	}
	else {
		if ($directio) {
			$lcmd = "$lnuttcp -sd$clientargs";
		}
		else {
			$lcmd = "$lnuttcp -s$clientargs";
		}
	}
}

if ($flip) {
	if ($zerocopy) {
		if ($directio) {
			$rcmd = "$rnuttcp -szd$clientargs";
		}
		else {
			$rcmd = "$rnuttcp -sz$clientargs";
		}
	}
	else {
		if ($directio) {
			$rcmd = "$rnuttcp -sd$clientargs";
		}
		else {
			$rcmd = "$rnuttcp -s$clientargs";
		}
	}
}
else {
	if ($zerocopy) {
		if ($directio) {
			$rcmd = "$rnuttcp -1 -szd";
		}
		else {
			$rcmd = "$rnuttcp -1 -sz";
		}
	}
	else {
		if ($directio) {
			$rcmd = "$rnuttcp -1 -sd";
		}
		else {
			$rcmd = "$rnuttcp -1 -s";
		}
	}
}

$lcmd .= " -P $ctlport";
$rcmd .= " -P $ctlport";

$intaropts .= " -";
$outtaropts .= " -";
if ($one_fs) {
	$intaropts .= " --one-file-system";
}
$intaropts = "-b$tarblock " . "$intaropts";
$outtaropts = "-b$tarblock " . "$outtaropts";

if ($#ARGV != 1) {
	&usage();
	if ($#ARGV >= 0) {
		exit( 1 );
	}
	exit( 0 );
}

if (($rshell =~ /csh/) || ($rshell eq "") && ($ENV{'SHELL'} =~ /csh/)) {
	$rshell = "csh";
}
else {
	$rshell = "bash";
}

$inspec = @ARGV[0];
$outspec = @ARGV[1];

$cmd = "$rsh";
$ipipe = 0;
$opipe = 0;

if ($inspec =~ /:/) {
	if ($outspec =~ /:/) {
		&usage();
		exit( 1 );
	}
	($inhost, $infile) = split(":", $inspec);
	($inruser, $inrhost) = split("@", $inhost);
	if ($inrhost ne "") {
		$inhost = $inrhost;
		$cmd .= " -l $inruser";
	}
	if ($datahost eq "") {
		($ctlhost, $datahost) = split("/", $inhost);
		$inhost = $ctlhost;
		if ($datahost eq "") {
			$datahost = $inhost;
		}
	}
	if ($inhost =~ /\+/) {
		$inhost = substr( $inhost, 0, index( $inhost, "+" ) );
	}
	if ($infile eq "") {
		print "No input file specified\n";
		exit( 1 );
	}
	if ($infile =~ /^\s*\|/) {
		print "Error: empty remote input pipe "
		      . "(or pipe on wrong side)\n";
		exit( 1 );
	}
	if ($infile =~ /^(.*)\s*\|\s*$/) {
		$ipipe = 1;
		$ipipe_cmd = $1;
		if ($flip) {
			$rcmd .= " -a";
		}
		else {
			$lcmd .= " -a";
		}
	}
	elsif ($copy_dir) {
		$ipipe = 1;
		if ($rshell eq "csh") {
			$ipipe_cmd = "( chdir $infile ; ";
		}
		else {
			$ipipe_cmd = "( set -e ; cd $infile ; ";
		}
		$ipipe_cmd .= "tar $intaropts . )";
		if ($flip) {
			$rcmd .= " -a";
		}
		else {
			$lcmd .= " -a";
		}
	}
	if ($flip) {
		($datadest, $ipad_stride) = split(/\+/, $datahost);
		$myipaddr = &getmyiptodest( $datadest, $dataport );
		($myhostname) = gethostbyaddr( $myipaddr, $af );
		if (($num_streams =~ /m/) && ($myhostname ne "")) {
			$rcmd .= " $myhostname";
		}
		else {
			($myipaddr1, $myipaddr2, $myipaddr3, $myipaddr4) =
				unpack( "C4", $myipaddr );
			$rcmd .= " $myipaddr1.$myipaddr2.$myipaddr3.$myipaddr4";
		}
		if ($ipad_stride ne "") {
			$rcmd .= "+" . $ipad_stride;
		}
	}
	if ($outspec =~ /\|\s*$/) {
		print "Error: empty local output pipe "
		      . "(or pipe on wrong side)\n";
		exit( 1 );
	}
	if ($outspec =~ /^\s*\|\s*(.*)$/) {
		$opipe = 1;
		$opipe_cmd = $1;
	}
	else {
		if ($copy_dir) {
			if ((-d $outspec) || !(-e $outspec)) {
				$opipe = 1;
				$opipe_cmd = "( mkdir -p $outspec ; set -e ; "
					. "cd $outspec ; tar $outtaropts )";
			}
			else {
				print "$outspec: not a directory\n";
				exit( 1 );
			}
		}
		elsif (-d $outspec) {
			$intail = substr( $infile, rindex( $infile, "/" ) + 1 );
			if (($intail eq "") || ($intail eq ".") ||
			    ($intail eq "..")) {
				print "$infile: not a regular file\n";
				exit( 1 );
			}
			if ($outspec !~ /\/$/) {
				$outspec .= "/";
			}
			$outspec .= "$intail";
		}
		if (-e $outspec) {
			if ($noclobber && ($outspec ne "/dev/null")) {
				print "$outspec: File exists.\n";
				exit( 1 );
			}
			elsif (! -w $outspec) {
				print "$outspec: Permission denied\n";
				exit( 1 );
			}
		}
	}
	$cmd .= " $inhost '";
	if (!$copy_dir && !$ipipe) {
		if ($rshell eq "csh") {
			$cmd .= "if (-d $infile) echo $infile: "
					. "not a regular file ; ";
			$cmd .= "if (-d $infile) kill -HUP \$\$ ; ";
		}
		else {
			$cmd .= "if [ -d $infile ]; then "
				. "echo $infile: not a regular file ; "
				. "exit  1 ; fi ; ";
		}
	}
	if ($ipipe) {
		$cmd .= "$ipipe_cmd | $rcmd ";
	}
	else {
		$cmd .= "$rcmd < $infile ";
	}
	if ($rshell eq "csh") {
		if ($flip) {
			$cmd .= "' ";
		}
		else {
			$cmd .= ">& /dev/null' ";
		}
	}
	else {
		if ($flip) {
			$cmd .= "' ";
		}
		else {
			$cmd .= "> /dev/null 2>&1' ";
		}
	}
	if ($ipipe) {
		if ($flip) {
			$cmd .= "; ";
		}
		else {
			$cmd .= "& ";
		}
	}
	else {
		$cmd .= "; ";
	}
	# following must be bash syntax since executed by perl via system
	$cmd .= "if [ \$? -ne 0 ]; then echo nuttscp: something went wrong; ";
	$cmd .= "exit; fi ; ";
	if (!$flip) {
		$lcmd .= " -r $datahost";
	}
	if ($opipe) {
		if ($flip) {
			$lcmd .= " | $opipe_cmd &";
		}
		else {
			$lcmd .= " | $opipe_cmd";
		}
	}
	else {
		$lcmd .= " > $outspec";
	}
}
elsif ($outspec =~ /:/) {
	($outhost, $outfile) = split(":", $outspec);
	($outruser, $outrhost) = split("@", $outhost);
	if ($inspec =~ /^\s*\|/) {
		print "Error: empty local input pipe (or pipe on wrong side)\n";
		exit( 1 );
	}
	if ($inspec =~ /^(.*)\s*\|\s*$/) {
		$ipipe = 1;
		$ipipe_cmd = $1;
		if ($zerocopy) {
			print "$inspec: not a regular file: "
					. "zero copy not possible\n";
			$zerocopy = 0;
		}
		if ($directio) {
			print "$inspec: not a regular file: "
					. "direct I/O not possible\n";
			$directio = 0;
		}
	}
	else {
		if (! -e $inspec) {
			print "$inspec: No such file or directory\n";
			exit( 1 );
		}
		if (! -r $inspec) {
			print "$inspec: Permission denied\n";
			exit( 1 );
		}
		if (-d $inspec) {
			if ($copy_dir) {
				$ipipe = 1;
				$ipipe_cmd = "( set -e ; cd $inspec ; "
						. "tar $intaropts . )";
			}
			else {
				print "$inspec: not a regular file\n";
				exit( 1 );
			}
		}
		else {
			if ($copy_dir) {
				print "$inspec: not a directory\n";
				exit( 1 );
			}
			elsif ((-f $inspec) && ($outfile eq "")) {
				$outfile = substr( $inspec,
						   rindex( $inspec, "/" ) + 1 );
			}
		}
		if ($zerocopy && (! -f $inspec) && (! -b $inspec) && ($inspec ne "/dev/zero")) {
			print "$inspec: not a regular or block file: "
					. "zero copy not possible\n";
			$zerocopy = 0;
		}
		if ($directio && (! -f $inspec) && (! -b $inspec) && ($inspec ne "/dev/zero")) {
			print "$inspec: not a regular or block file: "
					. "direct I/O not possible\n";
			$directio = 0;
		}
	}
	if ($outrhost ne "") {
		$outhost = $outrhost;
		$cmd .= " -l $outruser";
	}
	if ($datahost eq "") {
		($ctlhost, $datahost) = split("/", $outhost);
		$outhost = $ctlhost;
		if ($datahost eq "") {
			$datahost = $outhost;
		}
	}
	if ($outhost =~ /\+/) {
		$outhost = substr( $outhost, 0, index( $outhost, "+" ) );
	}
	if ($outfile eq "") {
		print "No output directory specified\n";
		exit( 1 );
	}
	if ($outfile =~ /\|\s*$/) {
		print "Error: empty remote output pipe "
		      . "(or pipe on wrong side)\n";
		exit( 1 );
	}
	if ($outfile =~ /^\s*\|\s*(.*)$/) {
		$opipe = 1;
		$opipe_cmd = $1;
		if ($flip) {
			$rcmd .= " -a";
		}
		else {
			$lcmd .= " -a";
		}
	}
	else {
		if ($copy_dir) {
			$opipe = 1;
			if ($rshell eq "csh") {
				$opipe_cmd .= "( chdir $outfile ; ";
			}
			else {
				$opipe_cmd .= "( cd $outfile ; ";
			}
			$opipe_cmd .= "tar $outtaropts )";
			if ($flip) {
				$rcmd .= " -a";
			}
			else {
				$lcmd .= " -a";
			}
		}
		else {
			$intail = substr( $inspec, rindex( $inspec, "/" ) + 1 );
			if ($outfile eq "") {
				$outfile = $intail;
			}
			elsif ($outfile =~ /\/$/) {
				$outfile .= "$intail";
			}
			elsif (($outfile =~ /\/\.$/) ||
			       ($outfile =~ /\/\.\.$/) ||
			       ($outfile eq ".") || ($outfile eq "..")) {
				$outfile .= "/$intail";
			}
		}
	}

	if ($flip) {
		($datadest, $ipad_stride) = split(/\+/, $datahost);
		$myipaddr = &getmyiptodest( $datadest, $dataport );
		($myhostname) = gethostbyaddr( $myipaddr, $af );
		if (($num_streams =~ /m/) && ($myhostname ne "")) {
			$rcmd .= " -r $myhostname";
		}
		else {
			($myipaddr1, $myipaddr2, $myipaddr3, $myipaddr4) =
				unpack( "C4", $myipaddr );
			$rcmd .= " -r";
			$rcmd .= " $myipaddr1.$myipaddr2.$myipaddr3.$myipaddr4";
		}
		if ($ipad_stride ne "") {
			$rcmd .= "+" . $ipad_stride;
		}
	}

	$cmd .= " $outhost '";
	if ($rshell eq "csh") {
		if ($noclobber) {
			$cmd .= "set noclobber ; ";
		}
		else {
			$cmd .= "unset noclobber ; ";
		}
	}
	else {
		if ($noclobber) {
			$cmd .= "set -o noclobber ; ";
		}
		else {
			$cmd .= "set +o noclobber ; ";
		}
	}
	if ($copy_dir) {
		if ($rshell ne "csh") {
			$cmd .= "set -e ; ";
		}
		if ($noclobber) {
			$cmd .= "mkdir $outfile ; ";
		}
		else {
			$cmd .= "mkdir -p $outfile ; ";
		}
		if ($rshell eq "csh") {
			$cmd .= "if (\$?) kill -HUP \$\$ ; ";
		}
	}
	$cmd .= "$rcmd < /dev/null ";
	if ($rshell eq "csh") {
		if ($opipe) {
			if ($flip) {
				$cmd .= "| $opipe_cmd' ; ";
			}
			else {
				$cmd .= "|& $opipe_cmd' & ";
			}
		}
		else {
			if ($flip) {
				$cmd .= "> $outfile' ; ";
			}
			else {
				$cmd .= ">& $outfile' ; ";
			}
		}
	}
	else {
		if ($opipe) {
			if ($flip) {
				$cmd .= "| $opipe_cmd' ; ";
			}
			else {
				$cmd .= "| $opipe_cmd 2>&1' & ";
			}
		}
		else {
			if ($flip) {
				$cmd .= "> $outfile' ; ";
			}
			else {
				$cmd .= "> $outfile 2>&1' ; ";
			}
		}
	}
	# following must be bash syntax since executed by perl via system
	if (!$flip) {
		$cmd .= "if [ \$? -ne 0 ]; ";
		$cmd .= "then echo nuttscp: something went wrong; ";
		$cmd .= "exit; fi ; ";
	}
	if ($ipipe) {
		if ($flip) {
			$lcmd = "$ipipe_cmd | $lcmd &";
		}
		else {
			$cmd .= "$ipipe_cmd | ";
		}
	}
	# following must be bash syntax since executed by perl via system
	if ($flip) {
		$cmd .= "if [ \$? -ne 0 ]; ";
		$cmd .= "then echo nuttscp: something went wrong; ";
		$cmd .= "exit; fi ; ";
	}
	if (!$flip) {
		$lcmd .= " $datahost";
	}
	if (!$ipipe) {
		$lcmd .= " < $inspec";
	}
}
else {
	&usage();
	exit( 1 );
}

if (!$flip) {
	$cmd .= "$lcmd";
}

if ($debug) {
	if ($flip) {
		print "$lcmd ; ";
	}
	print "$cmd\n";
}

if (!$noexec) {
	if ($flip) {
		system "$lcmd";
	}
	if ($? == 0) {
		if ($flip) {
			# check for error from executing remote command
			$error = 0;
			open( CMD, "$cmd < /dev/null 2>&1 |" );
			while (<CMD>) {
				print;
				if (/something went wrong/) {
					$error = 1;
				}
			}
			close( CMD );

			if ($error) {
				# probably left the local nuttcp server
				# running so stop it by connecting to the
				# control port and then closing it
				$hungnuttcp = &stoplocalserver( $ctlport );

				exit( 1 );
			}

			if ($rsh eq "rsh") {
				# rsh is weird and can't check its output
				# for errors so just unconditionally try
				# to stop the local nuttcp server in case
				# it was left hanging around
				$hungnuttcp = &stoplocalserver( $ctlport );

				if ($hungnuttcp) {
					exit( 1 );
				}
				else {
					exit( 0 );
				}
			}
		}
		else {
			system "$cmd";
		}
	}
	else {
		print "Error: couldn't start local nuttcp server\n";
		exit( 1 );
	}
}

exit( 0 );

sub getmyiptodest
{
	local( $dsthost, $dstport ) = @_;

	local( $myaddr );

	($fqdn) = gethostbyname( $dsthost );

	($fqdn2, $aliases, $addrtype, $addrlen, @dstaddr) = gethostbyname( $fqdn );

	if ($#dstaddr < 0) {
		print "Error: bad hostname or IP address $dsthost\n";
		exit( 1 );
	}

	($protoname, $aliases, $proto) = getprotobyname( "udp" );

	if (!socket( S, PF_INET, SOCK_DGRAM, $proto)) {
		print "Error: couldn't create socket\n";
		exit( 1 );
	}

	$wildcard[0] = 0;
	$wildcard[1] = 0;
	$wildcard[2] = 0;
	$wildcard[3] = 0;

	$sockaddr = "S n a4 x8";

	$me  = pack( $sockaddr, AF_INET, 0, $wildcard );
	$dst = pack( $sockaddr, AF_INET, $dstport, $dstaddr[0] );

	if (!bind( S, $me )) {
		print "Error: couldn't bind\n";
		close( S );
		exit( 1 );
	}

	if (!connect( S, $dst )) {
		print "Error: couldn't connect to $dsthost port $dstport\n";
		close( S );
		exit( 1 );
	}

	$mysockaddr = getsockname( S );
	($family, $myport, $myaddr) = unpack( $sockaddr, $mysockaddr );

	close( S );

	return $myaddr;
}

sub stoplocalserver
{
	local( $serverport ) = @_;

	local( $connected );

	# stop a local nuttcp server by connecting to its
	# control port on 127.0.0.1 and then closing it

	# no error messages should be output since local
	# nuttcp server may have already exited

	$connected = 0;

	($protoname, $aliases, $proto) = getprotobyname( "tcp" );

	if (!socket( S, PF_INET, SOCK_STREAM, $proto)) {
		#print "Error: couldn't create socket\n";
		return $connected;
	}

	$wildcard[0] = 0;
	$wildcard[1] = 0;
	$wildcard[2] = 0;
	$wildcard[3] = 0;

	$localhost[0] = 127;
	$localhost[1] = 0;
	$localhost[2] = 0;
	$localhost[3] = 1;

	$sockaddr = "S n a4 x8";

	$me  = pack( $sockaddr, AF_INET, 0, $wildcard );
	$dst = pack( $sockaddr, AF_INET, $serverport, $localhost );

	if (!bind( S, $me )) {
		#print "Error: couldn't bind\n";
		close( S );
		return $connected;
	}

	if (!connect( S, $dst )) {
		#print "Error: couldn't connect to 127.0.0.1 "
		#	       . "port $serverport\n";
		close( S );
		return $connected;
	}

	$connected = 1;

	close( S );

	return $connected;
}

sub usage
{
	print "Usage: nuttscp or nuttscp -h      prints this usage info\n";
	print "Usage: nuttscp -V                 reports nuttscp version\n";
	print "Usage: $0 [options] infile [user@]host[/datahost]:outfile\n";
	print "   or: $0 [options] [user@]host[/datahost]:infile outfile\n";
	print "      options: -w window                 default=unspecified\n";
	print "               -N num_streams            default=1\n";
	print "               -P ctlport                default=5100\n";
	print "               -p dataport               default=5101 (5001 on"
							. " nuttcp < 6.2.8)\n";
	print "               -l buflen                 default=64KB\n";
	print "               -n numbuf                 default=unlimited\n";
	print "               -T timeout                default=none\n";
#	problem with interval retrans info so leave undocumented for now
#	print "               -i interval               default=no interval"
#							. " reports\n";
	print "               -I identifier             default="
							. "no identifier\n";
	print "               -R rate_limit             default=unlimited\n";
	print "               -M mss                    default=unspecified\n";
	print "               -x extra_option           default=unspecified\n";
	print "               -b brief_option           default=standard"
							. " brief output\n";
	print "               -f                        default=remote"
							. " nuttcp server\n";
	print "               -F                        default="
							. "nuttcp flip off\n";
	print "               -4                        default="
							. "auto IPv4/IPv6\n";
	print "               -6                        default="
							. "auto IPv4/IPv6\n";
	print "               -v                        default="
							. "no verbose nuttcp\n";
if (`uname` =~ /[Ll]inux/) {
	print "               -z                        default=no zero copy\n";
	print "               -d                        default="
							. "no direct I/O\n";
}
	print "               --copy-dir                default=copy file\n";
	print "                   --verbose             default=not verbose\n";
	print "                   --compressed          default="
							. "not compressed\n";
	print "                   --one-file-system     default="
							. "stay on local fs\n";
	print "                   --tar-blocking        default=128\n" ;
	print "               --remote-shell shell      default="
							. "same as local\n";
	print "               --remote-nuttcp path      default=nuttcp\n";
	print "               --datahost host           default=same as "
							. "ssh/rsh host\n";
	print "               --ssh-id identity         default=unspecified\n";
	print "               --ssh-port port           default=22\n";
	if ($def_noclobber) {
		print "               --clobber                 default="
								. "noclobber\n";
	}
	else {
		print "               --noclobber               default="
								. "clobber\n";
	}
	print "\n";
	print "       infile may be a pipe:             \"command |\"\n";
	print "       outfile may be a pipe:            \"| command\"\n";
}
