#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/bin/cdat/helpers/get_file.pl 1.2 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2010,2011 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
# @(#)88    1.2  src/bos/usr/bin/cdat/helpers/get_file.pl, cdat, bos720 7/14/11 19:39:25
use warnings;
use strict;
use Net::FTP;
use POSIX qw(ceil floor);	# needed for ceil, floor
use File::Basename;		# needed for dirname, basename

use lib '/usr/lib/cdat/';
use cdat;

#
# Constants.
#
my $CHFS = '/usr/sbin/chfs';
my $DF = '/usr/bin/df';
my $DU = '/usr/bin/du';
my $AWK = '/usr/bin/awk';
my $SSH = '/usr/bin/ssh';
my $SCP = '/usr/bin/scp';
my $SSH_TIMEOUT = 2;
my $MB = 1024 * 1024;
my $EXTRA = 16;	# when extending fs, always allocate an extra 16MB

#
# Globals.
#
my ($growfs, $fsname, $fsfree);

######################################################################
# Function:	usage
# Purpose:	Display usage.
# Tasks:	Print usage and exit.
# Input:	None
# Output:	None
######################################################################
sub usage
{
    print(STDERR "Usage: get_file SourceFile TargetFile\n");
    print(STDERR "       get_file SourceFile ... TargetDirectory\n");
    exit(1);
}

######################################################################
# Function:	ftp_get_file
# Purpose:	Retrieve a file or directory using FTP.
# Tasks:	Retrieve a file or directory using FTP.
# Input:	Net::FTP handle, remote file name, local destination,
#		whether destination is a directory or not
# Output:	0 if success, 1 otherwise
######################################################################
sub ftp_get_file
{
    my ($ftp, $file, $dest, $isdir) = @_;
    my $rc;

    # determine whether remote filename is a file or directory
    if ($ftp->cwd($file)) {
        # remote filename is a directory
        if (!$isdir) {
	    print(STDERR "cannot retrieve directory, destination is a file\n");
	    return 1;
	}
	print("Creating directory $dest/$file\n");
	if (!mkdir("$dest/$file")) {
	    print(STDERR "cannot create directory $dest/$file\n");
	    return 1;
	}
	# Recursively call ourselves for each directory entry
	my $list = $ftp->ls();
	foreach my $elem (@$list) {
	    # call ourserlves recursively
	    if (ftp_get_file($ftp, $elem, "$dest/$file", 1)) {
	        return 1;
	    }
	}
	if (!$ftp->cdup()) {
	    print(STDERR "cannot CDUP from $file\n");
	    return 1;
	}

    } else {
	# remote filename is a file, retrieve it
	if (defined($growfs)) {
	    my $size = $ftp->size($file);
	    if (defined($size)) {
	        reserve_space($size / $MB);
	    }
	}
        if ($isdir) {
	    print("Copying $file to $dest/$file\n");
	    $rc = $ftp->get($file, "$dest/$file");
        } else {
	    print("Copying $file to $dest\n");
	    $rc = $ftp->get($file, $dest);
        }
        if (!$rc) {
	    print(STDERR "could not retrieve file $file\n");
	    return 1;
        }
    }
    return 0;
}

######################################################################
# Function:	ftp_get
# Purpose:	Retrieve remote files/directories using FTP.
# Tasks:	Connect via FTP to the specified remote node using the
#		specified login and retrieve files and directories.
# Input:	user, host name, remote files, local destination
# Output:	None
######################################################################
sub ftp_get
{
    my ($hosts, $user, $hostname, $sources, $dest) = @_;
    my $rc;

    my $passwd = $hosts->{$hostname}{users}{$user};
    if (!defined($passwd)) {
        $passwd = cdat::get_password("Enter password for \"$user\" user: ");
    }
    if (!defined($passwd)) {
	print(STDERR "No password to connect to $user\@$hostname\n");
        exit(1);
    }

    my $isdir = -d $dest;

    my $ftp = Net::FTP->new($hostname);
    if (!defined($ftp)) {
	print(STDERR "Cannot open FTP connection to $hostname\n");
        exit(1);
    }
    $rc = $ftp->login($user, $passwd);
    if (!$rc) {
	print(STDERR "FTP to $hostname failed: ".$ftp->message);
        exit(1);
    }

    # Transfer files in binary mode
    $ftp->binary();

    foreach my $file (@$sources) {
        my $dirname = dirname($file);
        if (!$ftp->cwd($dirname)) {
	    print(STDERR "Cannot CWD to $dirname\n");
	    next;
	}
	last if ftp_get_file($ftp, basename($file), $dest, $isdir);
    }

    # Close FTP session
    $ftp->quit();
}

######################################################################
# Function:	scp_get
# Purpose:	Retrieve remote files/directories using SCP.
# Tasks:	Connect via SSH to the specified remote node using the
#		specified login and retrieve files and directories.
# Input:	user, host name, remote files, local destination
# Output:	None
######################################################################
sub scp_get
{
    my ($hosts, $user, $hostname, $sources, $dest) = @_;

    if (defined($growfs)) {
        my @sizes = `$SSH -i $ENV{HOME}/.ssh/id_rsa -o UserKnownHostsFile=$ENV{HOME}/.ssh/known_hosts -o StrictHostKeyChecking=yes $user\@$hostname 'LANG=C $DU -sm @$sources 2>/dev/null' | $AWK '{print \$1}'`;
	if ($? == 0) {
	    my $size = 0;
	    $size += $_ foreach (@sizes);
	    reserve_space($size);
	}
    }

    system("$SCP -i $ENV{HOME}/.ssh/id_rsa -o UserKnownHostsFile=$ENV{HOME}/.ssh/known_hosts -o StrictHostKeyChecking=yes -q -r $user\@$hostname:'@$sources' '$dest'");
    if ($? != 0) {
	print(STDERR "scp command failed.\n");
        exit(1);
    }
}

######################################################################
# Function:	extendfs
# Purpose:	Extend the size of the destination filesystem.
# Tasks:	Change the size of the destination filesystem using
#		the chfs command.
# Input:	size to add in megabytes
# Output:	1 if successful, 0 otherwise
######################################################################
sub extendfs
{
    my ($size) = @_;

    print("Extending $fsname file system size by $size"."M\n");
    system("$CHFS -a size=+$size"."M '$fsname' >/dev/null 2>&1");
    return ($? == 0);
}

######################################################################
# Function:	get_free_space
# Purpose:	Get the free space on the destination filesystem.
# Tasks:	Get the free space on the destination filesystem in
#		megabytes using the df command.
# Input:	None
# Output:	free space in megabytes or undef if it fails
######################################################################
sub get_free_space
{
    my $size = `LANG=C $DF -m '$fsname' | $AWK '(NR==2){print \$3}'`;
    return undef if ($? != 0);
    chomp($size);
    return $size;
}

######################################################################
# Function:	reserve_space
# Purpose:	Make sure we have space for the specified file size.
# Tasks:	Extend the destination file system if needed.
# Input:	File size in megabytes.
# Output:	None
######################################################################
sub reserve_space
{
    my ($size) = @_;

    for (my $ratio = 1.0; $ratio <= 2.5; $ratio += .5) {
        my $free = get_free_space;
        last if (!defined($free) || ($free >= $size));
	print("File system $fsname has $free".
	    "M free, need at least $size"."M\n");
        last if (!extendfs(ceil($EXTRA + $ratio * ($size - $free))));
    }
}

######################################################################
# Function:	main
# Purpose:	Entry point of the get_file command.
# Tasks:	Retrieve the specified files/directories from the
#		specified remote node using either SCP or FTP.
# Input:	user, host name, node type, remote files,
#		local destination
# Output:	None
######################################################################
sub main
{
    usage() if (@ARGV < 2);

    my $user = $ENV{CDAT_USER};
    my $hostname = $ENV{CDAT_HOST};
    my $type = $ENV{CDAT_TYPE};
    my $dest = pop @ARGV;
    # @ARGV now only contains the remote files

   if (!defined($user) || !defined($hostname) || !defined($type)) {
       printf(STDERR "Bad environment\n");
       exit(1);
   }

    # If there is more than 1 remote file, check that destination
    # exists and is a directory.
    if (@ARGV > 1 && ! -d $dest) {
	print(STDERR "Destination directory \"$dest\" not found.\n");
	exit(1);
    }

    my %hosts;
    cdat::read_host_db(\%hosts);

    $growfs = $ENV{CDAT_GROWFS};
    if (defined($growfs)) {
        # Destination may not exist yet.
	my $file = $dest;
	$file = dirname($file) if (! -e $file);
	# Get name of destination filesystem.
	$fsname = `LANG=C $DF '$file' | $AWK '(NR==2){print \$7}'`;
	if ($? == 0) {
	    chomp($fsname);
	} else {
	    # we won't be able to extend fs, but continue anyway...
	    undef($growfs);
	}
    }

    my $protocol = $hosts{$hostname}{protocol};
    if (!defined($protocol)) {
        if ( -x $SCP && cdat::check_remote_protocol($hostname, 'ssh')) {
            $protocol = "ssh";
        } elsif (cdat::check_remote_protocol($hostname, 'exec')) {
            $protocol = "exec";
        } else {
            $protocol = "telnet";
        }
    }

    print("Retrieving ".join(', ', @ARGV)." from $type $hostname using ");
    if ($protocol eq "ssh") {
	print("scp\n");
        scp_get(\%hosts, $user, $hostname, \@ARGV, $dest);
    } else {
	print("ftp\n");
	ftp_get(\%hosts, $user, $hostname, \@ARGV, $dest);
    }
}

main;
