#!/usr/bin/env perl # $Id$ # (C) 2009 by the Board of Trustees of the Leland Stanford, Jr., University # All Rights Reserved # Produced by Andrew Hanushevsky for Stanford University under contract # DE-AC02-76-SFO0515 with the Deprtment of Energy # This command stages in a file from a remote site. The syntax is: # {frm_xfr.HPSS | hpsscp} [options] <[usr@]host>: | # <[usr@]host>: # options: # [-A []] [-c ] [-d] [-f] [-F {hide|none|oute|outo|}] # [-k ] [-m] [-M offs] [-n] [-p ] [-P ] [-r] [-s ] # [-t ] [-x ] [-w ] [-z] # options (valid only under hpsscp): # [-C] [-N ] [-Q ] [-W ] # -A send the output filename via a udp alert to the receiving host: # If port is not specified, the default port is used. # -c supplies an option configuration file. The options in the file are # added in front of whatever options remain. The file is structured # as if you specified the options on the command line either in a single # line or multiple lines separated by linends. # -d turn on debugging. # -k is the location of the keytab which should contain a single password # to be used when logging in (default is /var/adm/xrootd/pftp/keyfile). # -f force file retrieval even if the file exists (i.e., replace). # This is the default for zero length files. # -F specified the '.fail' file handling. The default is to create ".fail" # when a transfer fails. The file contains the pftp session along with any # pftp responses. You can change this as: # hide Creates a dot file instead: "tpath>/..fail" # none Does not create anything and stays silent about it. # oute Print what would have been written to ".fail" to stderr. # This is the default for hpsscp! # outo Print what would have been written to ".fail" to stdout. # Creates the file in the directory as "/.fail". # -m exit with non-zero status if the file exists and has a non-zero length. # -M specified he offset into the target filename where directories are to # start being created. See notes on auto-path creation. # -n do not redirect standard error when invoking the copy function # -o the onwership information in the form of : where either one # or both may be specified. The default comes from the data source. # Ownership can only be maintained by root proceses or pftp logins. # -P The MSS port number to use (default is 2021). # -p set the protection mode of the file. Specify 1 to 4 octal digits, the # letter 'r' for 0440, or the letter 'w' for 640. # -s specifies the storage class; where is a number. This has only # meaning if is remote. # -t number of times to retry a failing copy that is retriable (default is 2). # -x The pftp command to use (default is /opt/xrootd/utils/pftp_client). # -w maximum number of seconds to wait between retries. Default is 240. # -z do not reset the process group id. # hpsscp options: # -C continue execution even when a transfer slot cannot be obtained. By # default, the transfer does not occur without a transfer slot if a queue # directory exists. # # -N the location of the name space directory. If not specified, no check is # made whether or not a directory path exists in hpss. This causes mkdir's # to be executed for each copy, which is ineffecient and slow. With -N, a # shadow directory name space is maintained to avoid duplicate mkdir's. # -Q the location of execution queue directory. This directory must contain # at least one file and may contain any number. The number of files # represents the maximum number of simultaneous requests allowed. Requests # over the limit wait until a execution slot frees up. The default is # "/var/hpss/xfrq", if it exists. # -W the location of the wait41 program that implements the transfer queue # limits. The default is "/opt/xrootd/utils/wait41". # indicates that the file is in the Mass Storage System. The # is used as the login name and is the location of the MSS. # Only one of or may have this designation but # one must have it. If user if omitted, the current user is used. # Auto-path creation is supported in three ways. Primarily, if a # space appears in the hpss target path, then directories to # left of the space are assumed to exist and directories to the # are created prior to the pput of the file. If you do not want to # use space notation, use the -M option to specify where the space # would have occurred. This location must have a slash ('/'). # For hpsscp, the -N option can be used to provide a reference name # space that will determine which directories will be created. # Upon success, a zero status code is returned. Failure causes a message to # be issued to stderr and a non-zero exit status to be returned. use IPC::Open2; use Socket; # Global variables: # ($CMD) = "/$0" =~ m|^.*/([^.]*).*$|g; $isCPY = $CMD =~ m/^hpsscp/; $MsgPfx = "$CMD: "; $ER_SFX = '.fail'; # Suffix for fail file when error occurrs $OKFile = 1; # If file already present, all is good. $Replace = undef; # Replace existing file, if any $Mode = undef; $Debug = 0; $ErrLog = ''; # Command stream and response $ErrDisp = ($isCPY ? 'oute' : ''); $ErrFD = 0; $ErrFile = ''; # Absolute name of where error data resides $newPGRP = 1; # Set new process group $DMode = 0775; # Directory create mode $doGet = 1; # We assume we will be doing gets $UDPort = 0; # Alert udp port $UDPdef = 8686; # Alert udp port default # hpsscp specific items # $nsDir = ''; $nsMkd = ''; $xqDir = (-d '/var/hpss/xfrq' ? '/var/hpss/xfrq' : ''); $WT41 = (-x '/opt/xrootd/utils/wait41' ? 'opt/xrootd/utils/wait41' : ''); $PFOK = 0; # Transfer specific items # $XfrCmd = '/opt/xrootd/utils/pftp_client'; $XfrArgs = '-n -v'; $XfrOut = '2>&1'; # The redirection of stderr to stdout $XfrHost = ''; # MSS host name $XfrPort = '2021'; # MSS port number $XfrUser = ''; # MSS user name $XfrPswd = ''; # MSS user password $XfrKey = '/var/adm/xrootd/pftp/keyfile'; $XfrGet = 'open %xfrhost %xfrport;user %xfruser %xfrpswd;binary;' . 'setpblocksize 2097152;pget %xfrsfn %xfrtfn;quit;'; $XfrPut1 = 'open %xfrhost %xfrport;user %xfruser %xfrpswd;delete %xfrtfn'; $XfrPut2 = ';binary;setpblocksize 2097152;pput %xfrsfn %xfrtfn'; $XfrGID = ''; # The group ID (usually from source file) $XfrUID = ''; # The user ID (usually from source file) $XfrCOS = -1; # Class of Service $XfrApnd = ''; # Value of '-a' $XfrBase = ''; # Target prior to appending with XfrApnd $XfrMaxTR= 2; # Maximum Number of tries. $XfrMaxWT= 240; # Maximum Retry Wait Time. $XfrSrcFN= ''; # The source file $XfrTrgFN= ''; # The target file $XfrResp = ''; # The transfer response $XfrSess = ''; # The transfer session $XfrRC = 0; # The final return code $XfrMoff = -1; # Get the options # $CmdLine = join(' ',@ARGV); while (substr($ARGV[0], 0, 1) eq '-') { $op = shift(@ARGV); if ($op eq '-A') {if (&IsNum($ARGV[0])) {$UDPort= &GetVal('port', 'N');} else {$UDPort = $UDPdef;} } elsif ($op eq '-c') {$CFile = &GetVal('config file'); AddOpts($CFile);} elsif ($op eq '-d') {$Debug = 1;} elsif ($op eq '-k') {$XfrKey = &GetVal('key file');} elsif ($op eq '-f') {$Replace = 1;} elsif ($op eq '-F') {$ErrDisp = &GetVal('fail handing'); &Emsg("Invalid fail file disposition '$ErrDisp'.") if !($ErrDisp =~ /^(hide|none|outo|oute|\/.*)$/); } elsif ($op eq '-m') {$OKFile = 0;} elsif ($op eq '-M') {$XfrMoff = &GetVal('offset', 'N', 0);} elsif ($op eq '-n') {$XfrOut = ''} elsif ($op eq '-o') {&GetOwners(&GetVal('ownership'));} elsif ($op eq '-P') {$XfrPort = &GetVal('port', 'N');} elsif ($op eq '-p') {$Mode = &GetVal('mode'); if ($Mode eq 'r') {$Mode = oct('0440');} elsif ($Mode eq 'w') {$Mode = oct('0640');} else {&Emsg("Invalid mode '$Mode'.") if !IsOct($Mode) || $Mode > 7777; $Mode = oct($Mode); $Mode = undef if $Mode == 0; } } elsif ($op eq '-s') {$XfrCOS = &GetVal('storage class', 'N');} elsif ($op eq '-t') {$XfrMaxTR = &GetVal('max tries', 'N', 1);} elsif ($op eq '-w') {$XfrMaxWT = &GetVal('max wait', 'N', 1);} elsif ($op eq '-x') {$XfrCmd = &GetVal('transfer command');} elsif ($op eq '-z') {$newPGRP = 0;} elsif ($op eq '-C' && $isCPY) {$PFOK = 1;} elsif ($op eq '-N' && $isCPY) {$nsDir = &GetDir('name space directory');} elsif ($op eq '-Q' && $isCPY) {$xqDir = &GetDir('xfer queue directory');} elsif ($op eq '-W' && $isCPY) {$WT41 = &GetVal('wait41 command'); &Emsg(ET("Invalid -W command target")) if !-x $WT41; } else {&Emsg("Invalid option '$op'.",-1);} } &SayDebug("cmd = $CmdLine"); # Get the source and target filenames # &Emsg('Source file not specified.',-1) if (!($XfrSrcFN = $ARGV[0])); &Emsg('Target file not specified.',-1) if (!($XfrTrgFN = $ARGV[1])); &Emsg("Extraneous parameter, $ARGV[2].") if ($ARGV[2]); # See if the source is a remote location # ($mss_uhs, $XfrSrcFN) = split(':', $XfrSrcFN, 2); if (!$XfrSrcFN) {$XfrSrcFN = $mss_uhs, $mss_uhs = '';} else {$uhLoc = 'source'; $doGet = 1;} ($mss_uht, $XfrTrgFN) = split(':', $XfrTrgFN, 2); if (!$XfrTrgFN) {$XfrTrgFN = $mss_uht, $mss_uht = '';} else {$uhLoc = 'target'; $doGet = 0;} # Make sure we have something but not too much of it # &Emsg("Source and target files may not be both remote files!") if ($mss_uhs ne '' && $mss_uht ne ''); &Emsg("Source and target files may not be both local files!") if ($mss_uhs eq '' && $mss_uht eq ''); # Extract out the user and host # $mss_uh = ($mss_uhs ? $mss_uhs : $mss_uht); if (index($mss_uh, '@') >= 0) {($XfrUser, $XfrHost) = split('@', $mss_uh);} else {$XfrUser = (getpwuid($>))[0]; $XfrHost = $mss_uh;} &Emsg("User missing in $uhLoc file specification.") if $XfrUser eq ''; &Emsg("Host missing in $uhLoc file specification.") if $XfrHost eq ''; # Cleanup nsDir option # if ($nsDir ne '') {while ($nsDir =~ s|//|/|g) {} $nsDir .= $sfx if ($sfx = chop($nsDir)) ne '/'; } # If we need to append something, do so now # if (!$doGet) {if (index($XfrTrgFN, ' ') >= 0) {($XfrBase, $XfrApnd) = split(/ /, $XfrTrgFN, 2); $XfrTrgFN = $XfrBase.'/'.$XfrApnd; } elsif ($XfrMoff >= 0) {&Emsg("-M offset does not refer to a slash.") if substr($XfrTrgFN, $XfrMoff, 1) ne '/'; $XfrBase = substr($XfrTrgFN, 0, $XfrMoff); $XfrApnd = substr($XfrTrgFN, $XfrMoff+1); } elsif ($nsDir ne '') {($XfrBase, $XfrApnd) = Get_Base($XfrTrgFN);} } # Remove double slashes # while ($XfrSrcFN =~ s|//|/|g) {} while ($XfrTrgFN =~ s|//|/|g) {} &SayDebug("Base=$XfrBase Append=$XfrApnd Target=$XfrTrgFN") if $Debug && ($XfrBase ne '' || $XfrApnd ne ''); # Handle error file disposition # if ($ErrDisp eq '') {$ErrFile = ($doGet ? $XfrTrgFN : $XfrSrcFN).$ER_SFX;} elsif ($ErrDisp eq 'hide') {my($edir, $efn) = $XfrTrgFN =~ /^(.*)\/(.*)$/g; $ErrFile = $edir.'/.'.$efn.$ER_SFX; } elsif ($ErrDisp eq 'oute') {$ErrFD = STDERR; $ErrDisp = 'prnt';} elsif ($ErrDisp eq 'outo') {$ErrFD = STDOUT; $ErrDisp = 'prnt';} elsif (substr($ErrDisp,0,1) eq '/') {$ErrFile = $ErrDisp.$XfrTrgFN.$Er_SFX; while ($ErrFile =~ s|//|/|g) {} } unlink($ErrFile) if $ErrFile ne ''; # If this is a put, make sure the source file exists before we do anything # if (!$doGet) {@Svec = stat($XfrSrcFN); &CleanUp(&Emsg(ET("Unable to put $XfrSrcFN"), 0)) if $Svec[7] eq ''; $XfrUID = $Svec[4] if $XfrUID eq ''; $XfrGID = $Svec[5] if $XfrGID eq ''; &MakePath($nsMkd) if !($rc = TransPace(0,$Svec[7])) && $nsMkd ne ''; &CleanUp($rc); } # Check if the file already exists in the cache. If it doesn't create the # directory path to the file. # @Svec = stat($XfrTrgFN); if ($Svec[7]) {if (!$Replace) {utime(time(), $Svec[9], $XfrTrgFN); &CleanUp(&Emsg("File $XfrTrgFN already exists.", $OKFile)); } else {truncate($XfrTrgFN, 0);} } else {&MakePath($XfrTrgFN);} # At this point we need to bring the file into this cache. Issue the # appropriate command to do this and set the access mode if it succeeded. # &CleanUp($rc) if ($rc = TransPace(1,-1)); &Emsg(ET("Unable to set mode for '$XfrTrgFN'"), 1) if (defined($Mode) && !chmod($Mode, $XfrTrgFN)); # All done. # &CleanUp(0); #****************************************************************************** #* A d d O p t s * #****************************************************************************** sub AddOpts {my($cfn) = @_; my(@clines); &Emsg(ET("Unable to open config file '$cfn'")) if !open(CFD, $cfn); @clines = ; chomp(@clines); @clines = split(/ /,join(' ', @clines)); unshift(@ARGV, @clines); close(CFD); } #****************************************************************************** #* C l e a n u p * #****************************************************************************** sub CleanUp {my($rc) = @_; my($eFN); if ($rc && ($ErrLog || $XfrSess)) {$XfrSess =~ s/\;/\n/g; if ($ErrFile) {$eFN = (-e $ErrFile ? '>>' : '>').$ErrFile; if (!MakePath($ErrFile) || !open(ERFD,$eFN)) {$ErrFD=STDERR;} else {print(ERFD $XfrSess,$ErrLog); close(ERFD);} } print($ErrFD $XfrSess,$ErrLog) if $ErrFD; } exit(($rc > 255 ? 255 : $rc)); } #****************************************************************************** #* G e t _ B a s e * #****************************************************************************** sub Get_Base {my($Path) = @_; my($dir, $apnd, $Base); my(@dirs) = split('/', $Path); my($fn) = pop(@dirs); shift(@dirs) if ($dirs[0] eq ''); while(($dir = shift(@dirs)) ne '') {last if !-d $nsDir.$Base.'/'.$dir; $Base .= '/'.$dir;} if ($dir ne '') {while(($apnd = shift(@dirs)) ne '') {$dir .= '/'.$apnd;} $nsMkd = $nsDir.$Path; $dir .= '/'.$fn; return ($Base, $dir); } return ('', ''); } #****************************************************************************** #* G e t _ F i l e * #****************************************************************************** sub Get_File {my($cmd, $resp, $GetTries); # Construct the command stream # $cmd = &TransCmd($XfrGet); # Execute the command to get the file (as many times as wanted/needed) # $GetTries = 0; do {return 0 if &Transfer($cmd); $GetTries += &TransErr($GetTries+1); } while($GetTries < $XfrMaxTR); return $XfrRC; } #****************************************************************************** #* P u t _ A l e r t * #****************************************************************************** sub Put_Alert {my($ip_addr); # if there is no need to send an alert, simply return # return 0 if !$UDPort; # Get a udp socket # if (!socket(MYSOCK, PF_INET, SOCK_DGRAM, getprotobyname("udp"))) {&SayDebug("Unable to get socket for alert; $!", 1); return 0;} # Convert destination to a sockaddr # if (substr($XfrHost,0,1) =~ m/\d/) {$ip_addr = inet_aton($XfrHost);} else {$ip_addr = gethostbyname($XfrHost);} my($ip_dest) = pack_sockaddr_in($UDPort, $ip_addr); # Send the message # &SayDebug("Unable to send alert; $!", 1) if !send(MYSOCK, "$XfrUser $XfrTrgFN\n", 0, $ip_dest); return 0; } #****************************************************************************** #* P u t _ F i l e * #****************************************************************************** sub Put_File {my($Fsz) = @_; my($cmd, $dir, $resp, $sfx, $PutTries); # Establish command stream preamble # $cmd = &TransCmd($XfrPut1); # Check if we need to insert mkdir commands # if ($XfrApnd ne '') {while ($XfrApnd =~ s|//|/|g) {} while ($XfrBase =~ s|//|/|g) {} $XfrBase .= $sfx if ($sfx = chop($XfrBase)) ne '/'; my(@dirs) = split('/', $XfrApnd); pop(@dirs); shift(@dirs) if ($dirs[0] eq ''); while(($dir = shift(@dirs)) ne '') {$XfrBase .= '/'.$dir; $cmd .= ";mkdir $XfrBase";} } # Set the cos if we need to # $cmd .= ";site setcos $XfrCos" if $XfrCOS >= 0; # Construct the penultimate command stream # $cmd .= TransCmd($XfrPut2); # Now add the uid/gid settings, as needed, followed by a quit. # if ($XfrUser eq 'root') {$cmd .= ";site chuid $XfrUID $XfrTrgFN" if $XfrUID ne ''; $cmd .= ";site chgid $XfrGID $XfrTrgFN" if $XfrGID ne ''; } $cmd .= ';quit'; # Execute the command to put the file (as many times as wanted/needed) # $PutTries = 0; do {return &Put_Alert() if &Transfer($cmd, 1, $Fsz); $PutTries += &TransErr($PutTries+1, 1); } while($PutTries < $XfrMaxTR); return $XfrRC; } #****************************************************************************** #* T r a n s C m d * #****************************************************************************** sub TransCmd {my($cmd, $nofn) = @_; # Do command substitution (same for get or put) # $cmd =~ s/%xfrhost/$XfrHost/; $cmd =~ s/%xfrport/$XfrPort/; $cmd =~ s/%xfruser/$XfrUser/; $cmd =~ s/%xfrsfn/$XfrSrcFN/; $cmd =~ s/%xfrtfn/$XfrTrgFN/; return $cmd; } #****************************************************************************** #* T r a n s E r r * #****************************************************************************** sub TransErr {my($Try, $isput) = @_; # Errors that cannot be retried because nothing will work. Return a count # that will cause the counter to decrease to zero. # if ($XfrResp =~ /FileToNet: select error = 145/) { # pftp timeout Logit("pftp timeout for $XfrTrgFN"); return $XfrMaxTR+1; } if ($XfrResp =~ /cannot be opened - HPSS Error: -2/) { # ENOENT type of error if ($isput) {Logit("mkdir error; path not found for $XfrTrgFN");} else {Logit("get failed; '$XfrTrgFN' not found.");} $XfrRC = 2; return $XfrMaxTR+1; } if (!$isput && $resp =~ /NetToFile:file write failure\(5\)/) { # Disk is full Logit("pftp failed for $XfrTrgFN; disk is full."); return $XfrMaxTR+1; } # Errors which should be retried *forever* because problem is with HPSS # not the particular file being transferred. Return non-increasing value. # if ($XfrResp =~ /Bad Data Transfer.\(error = -28,moved = 0\)/) { Logit("No space in storage class for $XfrTrgFN"); sleep Min(240,$XfrMaxWT); return 0; } if ($XfrResp =~ /Login failed./) { Logit("pftp login failed for $XfrTrgFN"); sleep Min(240,$XfrMaxWT); return 0; } if ($XfrResp =~ /Service not available, remote server has closed connection/) { Logit("pftp login failed for $XfrTrgFN"); sleep Min(240,$XfrMaxWT); return 0; } if ($XfrResp =~ /connect: Connection timed out/) { Logit("pftp login failed for $XfrTrgFN"); sleep Min(240,$XfrMaxWT); return 0; } if ($XfrResp =~ /connect: Connection refused/) { Logit("pftp login failed for $XfrTrgFN"); sleep Min(240,$XfrMaxWT); return 0; } # Retriable errors that are likely file related. These have a maximum limit and # we wait between tries for an arbitrary but limited amount of time. # if ($XfrResp =~ /Bad Data Transfer.\(error = -5,moved = 0\)/) { Logit("No devices available to pftp $XfrTrgFN"); sleep Min(120 * $Try, $XfrMaxWT); return 1; } if ($XfrResp =~ /cannot be opened - HPSS Error: -5/) { Logit("Open error for $XfrTrgFN"); sleep Min(60,$XfrMaxWT); return 1; } # The following error seems to correlate to BFS end session error BFSR0096 # if ($XfrResp =~ /Bad Data Transfer.\(error = -52,moved = 0\)/) { Logit("BFS error = -52 for $XfrTrgFN"); sleep Min(60,$XfrMaxWT); return 1; } # Retriable error conditions that require notification should go here. # if ($XfrResp =~ /HPSS Error: -5/) { # I/O error in HPSS? Emsg("I/O error for $XfrTrgFN, rc=$XfrRC, try=$Try"); sleep Min(60,$XfrMaxWT); # wait a bit and retry return 1; } # We don't know why we were called but we don't want to try again # $resp = 'transfer failed for unknown reasons.' if !($resp = &LastLine($resp)); return $XfrMaxTR+2; } #****************************************************************************** #* T r a n s f e r * #****************************************************************************** sub Transfer {my($Cmds, $isWrite, $Fsz) = @_; my($i, $n, $pid, @Resp); my($xdir) = ' bytes '.($isWrite ? 'sent ' : 'received '); local(*Reader, *Writer); # Det things up # $XfrRC = 0; $XfrSess .= $Cmds.'===================================;'; # Do some debugging here to avoid spilling the password # if ($Debug) {&SayDebug("Executing '$XfrCmd $XfrArgs'"); my($Sess) = $XfrSess; $Sess =~ s/\;/\n/g; &SayDebug("Command stream:\n$Sess"); } # We now insert the password prior to transfer to avoid revealing it # &GetPswd($XfrKey); $Cmds =~ s/%xfrpswd/$XfrPswd/; # Establish a signal handler and create a process group # $SIG{PIPE} = 'PHandler'; $SIG{TERM} = 'THandler'; setpgrp() if $newPGRP; # execute the command feeding it the input # &Emsg(ET("Unable to exec '$XfrCmd'")) if !($pid = open2(\*Reader, \*Writer, "$XfrCmd $XfrArgs $XfrOut")); $Cmds =~ s/\;/\n/g; &Emsg(ET("Unable to send commands to '$XfrCmd'")) if !print Writer $Cmds; close(Writer); # Get the command output and clean-up the command # @Resp = ; waitpid($pid,0); $XfrRC = ($? != 2 ? $? : 22); $XfrResp = join('',@Resp); $XfrSess .= $XfrResp.'==================================='."\n"; # If we ended with an error, bail out # if ($XfrRC != 0) {Logit("!!! Transfer ended with non-zero status ($XfrRC)."); return 0;} # Verify that the transfer fully completed. Note that the "transfer complete" # message comes in two flavors and may be repeated. We want the last instance # which cannot be neither on the last nor first lines of the response. # $i = $#Resp; $XfrRC = 8; while($i--) {last if ($Resp[$i] =~ m/226 Transfer Complete\./) || ($Resp[$i] =~ m/226 Transfer complete\./); } # Make sure we found the transfer complete message # if ($i < 0) {Logit("!!! '226 Transfer complete' msg not found."); return 0;} # Make sure we have the number of bytes sent/received # if (($n = index( $Resp[$i+1], $xdir)) < 0) {Logit("!!! '226 Transfer bytes' msg not found."); return 0;} $XfrBytes = substr($Resp[$i+1], 0, $n); # For writes, make sure all the bytes were transferred # if ($isWrite && $XfrBytes != $Fsz) {Logit("Transfer bytes ($XfrBytes) != file bytes ($Fsz)."); return 0;} # All done # $XfrRC = 0; return 1; } #****************************************************************************** #* T r a n s P a c e * #****************************************************************************** sub TransPace {my($doGet, $Fsz) = @_; my($resp, $pid); local(*xfrRDR, *xfrWRT); my($okPace) = 0; if ($WT41 ne '' && $xqDir ne '') {&SayDebug("Obtaining xfer slot in $xqDir"); my($xsT) = time(); if (!($pid = open2(\*xfrRDR, \*xfrWRT, "$WT41 $xqDir"))) {&Emsg(ET("Unable to exec '$WT41'")); return 1 if !$PFOK; } else { $resp = ; chomp($resp); $okPace = 1; if ($resp ne 'OK') {Emsg("Unable to obtain transfer slot."); return 1 if !$PFOK; } else { $xsT = time() - $xsT; SayDebug("Xfer slot obtained in $xsT seconds.");} } } $rc = ($doGet ? Get_File($Fsz) : Put_File($Fsz)); if ($okPace) {close(xfrRDR); close(xfrWRT); waitpid($pid,0);} return $rc; } #****************************************************************************** #* U s a g e * #****************************************************************************** sub Usage {my($rc) = @_; $CMD .= '.hpss' if !$isCPY; print "Usage: $CMD [options] {<[usr@]host>: | <[usr@]host>:}\n"; print "\n"; print "options: [-c ] [-d] [-f] [-F {hide|none|oute|outo|}]\n"; print " [-k ] [-m] [-M offs] [-n] [-p ] [-P ] [-r]\n"; print " [-s ] [-t ] [-x ] [-w ] [-z]\n"; if ($isCPY) { print " [-C] [-N ] [-Q ] [-W ]\n"; } exit($rc); } #****************************************************************************** #* u t i l i t i e s * #****************************************************************************** sub MakePath {my($path) = @_; my(@dirs, $mkpath, $dname); @dirs = split('/', $path); pop(@dirs); $mkpath = shift(@dirs); # start with either "" or "." while(($dname = shift(@dirs)) ne "") {$mkpath .= "/$dname"; if (!-d $mkpath) {return &Emsg(ET("mkdir($mkpath) failed for '$path'"),1) if !mkdir($mkpath, $DMode); } } return 1; } sub Min {my($V1, $V2) = @_; return ($V1 < $V2 ? $V1 : $V2);} sub SayDebug {my($msg) = @_; print STDERR $MsgPfx, $msg, "\n" if $Debug; } sub LastLine {my($resp) = @_; my(@rr, $i); @rr = split("\n", $resp); for ($i = $#rr; $i >= 0; $i--) {return $rr[$i] if $rr[$i];} return ''; } sub GetDir {my($item) = @_; my($v); $v = shift(@ARGV); &Emsg(ucfirst($item).' not specified.') if $v eq ''; &Emsg(ET("Invalid $item, '$v'")) if !-d $v; return $v; } sub GetOwners {my($ug) = @_; my($usr, $grp) = split(/:/, $ug, 2); if ($usr ne '') {if (IsNum($usr)) {$XfrUID = $usr;} else {$XfrUID = getpwnam($usr); &Emsg("User '$usr' is not valid.") if !defined($XfrUID); } } if ($grp ne '') {if (IsNum($grp)) {$XfrGID = $grp;} else {$XfrGID = getgrnam($grp); &Emsg("Group '$grp' is not valid.") if !defined($XfrGID); } } } sub GetPswd {my($fn) = @_; &Emsg(ET("Unable to open keyfile '$fn'")) if !open(KTFD, $fn); $XfrPswd = ; close(KTFD); } sub GetVal {my($item, $type, $minv) = @_; my($v, $q); $v = shift(@ARGV); $q = 0; &Emsg(ucfirst($item).' not specified.') if $v eq ''; if ($type eq 'Q') { $q = uc(chop($v)); if ($q eq 'K') {$q = 1024;} elsif ($q eq 'M') {$q = 1024*1024;} elsif ($q eq 'G') {$q = 1024*1024*1024;} else {$v .= $q; $q = 0;} } &Emsg("Invalid $item, '$v'.") if ($type eq 'N' || $type eq 'Q') && !&IsNum($v); &Emsg("$item, '$v', is too small.") if ($type eq 'N' && $v < $minv); $v = $v*$q if $q; return $v; } sub IsNum {my($v) = @_; return ($v =~ m/^[0-9]+$/);} sub IsOct {my($v) = @_; return ($v =~ m/^[0-7]+$/);} sub Emsg {my($msg,$ret,$rc) = @_; print STDERR $MsgPfx,$msg,"\n"; Logit($msg) if !$isCPY; return 0 if $ret > 0; Usage(4) if $ret < 0; $rc = 4 if $rc eq ""; &CleanUp($rc); } sub ET {my($etxt) = @_; return $etxt.'; '.lcfirst($!).'.';} sub Logit {my($msg) = @_; $ErrLog .= $MsgPfx.$msg."\n";} sub PHandler {$SIG{PIPE} = 'DEFAULT';} sub THandler {$SIG{TERM} = 'DEFAULT'; kill(-15, getpgrp(0));}