#! /usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2008,2022 
# 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 

# sccsid = "@(#)77   1.15   src/rsct/trace/trcspl/lstrsp.perl, trace, rsct_rady, radys004a 1/19/22 15:29:16"
# Search on "sub help" and/or "sub usage" for script description.

use Getopt::Long;
use Fcntl ':mode';
use Time::Local;

# Get a basic info
my $osname = `/bin/uname -s 2> /dev/null`;
chomp $osname;

my $pwd = `pwd`;
chomp $pwd;

my $total_spool_files = 0;
my $total_spool_files_bytes = 0;
my @unsorted_hits = ();

# trace entry list
my @entry_files = ();

my $rc;
my $spool_dir, $cluster_name, $cluster_id, $node_name, $node_id;
my $daemon_name, $megabytes, $from, $to, $previous, $tar_outfile, $gzip;
my $help, $tar_ticks, $no_usage, $show_file_bytes;
my $tar_stdout, $tar_stderr;
my $tar_timeout;

if(!GetOptions("spool_dir=s" => \$spool_dir, 
     "cluster_name=s" => \$cluster_name, "cluster_id=s" => \$cluster_id, 
     "node_name=s" => \$node_name, "node_id=s" => \$node_id, "daemon_name=s" => \$daemon_name, 
     "megabytes=i" => \$megabytes, "from=s" => \$from, "to=s" => \$to, 
     "previous=i" => \$previous, 
     "tar=s" => \$tar_outfile, "gzip" => \$gzip, 
     "help" => \$help, "h" =>\$help, 
     "tar_stdout=s" => \$tar_stdout, "tar_stderr=s" => \$tar_stderr,
     "tar_timeout=i" => \$tar_timeout,
     "tar_ticks" => \$tar_ticks, "no_usage" => \$no_usage, 
     "show_file_bytes" => \$show_file_bytes))
{
	usage(1);
}

if (defined $help)
{
	help();
}

# set the default values
$spool_dir = "."     unless defined($spool_dir);
$cluster_name = ".*" unless defined($cluster_name);
$cluster_id = ".*"   unless defined($cluster_id);
$node_name = ".*"    unless defined($node_name);
$node_id = ".*"      unless defined($node_id);
$daemon_name = ".*"  unless defined($daemon_name);
$tar_timeout = 3600  unless defined($tar_timeout);

if ((defined $previous) && ((defined $from) || (defined $to)))
{
	usage(1);
}

if (defined $from)
{
	if (! ($from =~ /^\d{4}-\d{2}-\d{2}/))
	{
		usage(1);
	}
}

if (defined $to)
{
	if (! ($to =~ /^\d{4}-\d{2}-\d{2}/))
	{
		usage(1);
	}
}


if( (defined $gzip) && !defined($tar_outfile) )
{
    # gzip is found but no tar outfile specified
    usage(1);
}


# Check parameters
my $gzip_enabled = 1;
if(!defined $gzip)
{
    $rc = system("which gzip 1>/dev/null 2>&1");
    #$rc = system("which gzip");
    $gzip_enabled = ($rc eq 0);   # gzip is defined
}

my $tar_ticks_enabled = 0;
if (defined $tar_ticks)
{
    $tar_ticks_enabled = 1;
    if (!defined $tar_outfile)
    {
        usage(1);
    }

    # Redirect tar stdout
    if(!defined($tar_stdout)) {
        # Redirect tar_stdout to /dev/null
        $tar_stdout = "/dev/null";
    }
}


my $tar_path_prefix = "";

my $chdir_root_done = 0;

if ("/" eq substr($spool_dir, 0, 1))
{
    # The current dir = "/" and "spool start with "/"

    $tar_path_prefix = ".";
    chdir("/");
    $chdir_root_done = 1;

    if(defined($tar_stdout) && ("/" ne substr($tar_stdout,0,1))) {
        $tar_stdout = "$pwd/$tar_stdout"; 	# Prepend PWD to tar_stdout
    }
    if(defined($tar_stderr) && ("/" ne substr($tar_stderr,0,1))) {
        $tar_stderr = "$pwd/$tar_stderr";       # Prepend PWD to tar_stderr
    }

    if(defined($tar_outfile) && ("/" ne substr($tar_outfile,0,1))) {
	    $tar_outfile = "$pwd/$tar_outfile";     # prepend PWD to tar_outfile
    }
}

# Traverse spool_dir to collect $entry_list
descend($spool_dir, 5);

# capture the spool information for IW dir as well if
# cluster_name or cluster_id is given as input.
if ((defined $cluster_name) || (defined $cluster_id))
{
    if ($cluster_name ne "IW")
    {
        $cluster_name = "IW";
        descend($spool_dir, 5);
    }
}

if ((defined $megabytes) || (defined $show_file_bytes))
{
	my @sorted_hits = ();

	if (!defined $show_file_bytes)
	{
		@sorted_hits = sort {
			(@$b[1] =~ /^.+\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d+)$/)[0]
			cmp
			(@$a[1] =~ /^.+\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d+)$/)[0];
		} @unsorted_hits;
	}
	else
	{
		@sorted_hits = sort {
			(@$a[1] =~ /^.+\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d+)$/)[0]
			cmp
			(@$b[1] =~ /^.+\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d+)$/)[0];
		} @unsorted_hits;
	}

	foreach $arrayref (@sorted_hits)
	{
		if ((defined $megabytes) && ($total_spool_files_bytes + @$arrayref[2] > $megabytes*1024*1024))
		{
			last;
		}

		process_entry(@$arrayref[0], @$arrayref[1], @$arrayref[2]);

		if (defined $megabytes)
		{
			$total_spool_files_bytes += @$arrayref[2];
		}
	}
}

if (($tar_ticks_enabled ne 0) && ($total_spool_files))
{
	print "\n";
}

#  127 - timeout
my $exitrc = 0;
local $timeout_happened = 0;

if( scalar(@entry_files) > 0 )
{
    # To deal with stdout/stderr outputs from "tar" command
    open OLDSTDOUT, ">&STDOUT";
    open OLDSTDERR, ">&STDERR";

    # Ignore stdout of tar
    if(defined($tar_stdout)) {
        open STDOUT, ">$tar_stdout" or warn "Can't open tar_stdout=$tar_stdout: $!";
    }

    if(defined($tar_stderr)) {
        open STDERR, ">$tar_stderr" or warn "Can't open tar_stderr=$tar_stderr: $!";
    }

    # Run the tar command with the timeout 
    eval {
        local $SIG{ALRM} = sub {die "tar_alarm\n"};
        alarm $tar_timeout;   # set the timeout

        # tar all collected 'trace' files under spool
        tar_trace_entry_files();

        alarm 0;              # reset alarm
    };

    if ($@) {
        die unless $@ eq "tar_alarm\n";
        $timeout_happened = 1;
        $exitrc = 127;
    }

    # close redirected stdout/stderr
    close STDOUT if defined($tar_stdout);
    close STDERR if defined($tar_stderr);

    # Restore stdout, stderr
    open STDOUT, '>&OLDSTDOUT' or die "Can't restore original stdout: $!";
    open STDERR, '>&OLDSTDERR' or die "Can't restore original stderr: $!";
    close OLDSTDOUT or warn "Can't close old stdout: $!";
    close OLDSTDERR or warn "Can't close old stderr: $!";

} # end-of-tar operation

if ($chdir_root_done)
{
	chdir("$pwd");
}

if($timeout_happened != 0)
{
   my $datestring = localtime();
   print STDERR "2 $datestring: $0 - tar command executed is timed out (timeout=$tar_timeout)\n";
}

exit $exitrc;


#######################################################################################
sub show_tick_mark
{
    if ($tar_ticks_enabled > 0)
    {
        print ".";
    }
}

sub help
{
	print STDERR <<HELP;
This utility lists trace spool files matching selection criteria,
and either outputs their paths to stdout, one per line (default),
or puts the files in a tar archive (--tar option).

HELP
	usage(0);
}

sub usage
{
	if (!defined $no_usage)
	{
		print STDERR <<USAGE;
Usage:
lstrsp [--spool_dir <spool directory>]
       [--cluster_name <cluster name pattern>]
       [--cluster_id <cluster id pattern>]
       [--node_name <node name pattern>]
       [--node_id <node id pattern>]
       [--daemon_name <daemon_name name pattern>]
       [--previous <number of days before today>] |
         [--from <yyyy-mm-dd[.hh[:mm[:ss]]]> [--to <yyyy-mm-dd[.hh[:mm[:ss]]]>]]
       [--megabytes <maximum megabytes>]
       [--tar <tarfile_name>] [--tar_ticks]
       [--tar_timeout <tar timeout in seconds (default = 3600)>]
       [--tar_stdout <redirected tar stdout>] [--tar_stderr <redirected tar stderr>]
       [--show_file_bytes]
       [--gzip]
       [--no_usage]
Notes:
  - expected patterns are quoted Perl regular expressions
  - pattern defaults are '.*'
  - --tar_ticks prints a '.' instead of a file name for each matching file
  - --no_usage suppresses usage statement errata
  - --show_file_bytes forces an "oldest to latest" sort
USAGE
	}

	if (defined $_[0])
	{
		exit $_[0];
	}
}

# args: parent_directory, recurse_depth (counts down to 0)
# returns: 0=okay 1=hit_megabytes_limit
sub descend
{
	local (*DIR); # makes the handle distinct across recursive calls
	my $match_expression;
	
	if (5 == $_[1])
	{
		$match_expression = sprintf("^%s\$", $cluster_name);
	}
	elsif (4 == $_[1])
	{
		$match_expression = sprintf("^%s\$", $cluster_id);
	}
	elsif (3 == $_[1])
	{
		$match_expression = sprintf("^%s\$", $node_name);
	}
	elsif (2 == $_[1])
	{
		$match_expression = sprintf("^%s\$", $node_id);
	}
	elsif (1 == $_[1])
	{
		$match_expression = sprintf("^%s\$", $daemon_name);
	}
	elsif (0 == $_[1])
	{
		if ((defined $megabytes) || (defined $show_file_bytes))
		{
			# include all files ending in timestamps
			$match_expression = "^.+\\.\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}\\.\\d+\$";
		}
		else
		{
			# include only trace spool files ending in timestamps
			$match_expression = "^.+\\.\\d+\\.sp\\.\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}\\.\\d+\$";
		}
	}

	if (! opendir DIR, "$_[0]")
	{
		printf(STDERR "Error: cannot open spool directory(%s).\n", $_[0]);
		exit 2;
	}
	
	while (my $entry = readdir(DIR))
	{
		if (("." ne $entry) && (".." ne $entry))
		{
			if ($entry =~ /$match_expression/)
			{
				my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat("$_[0]/$entry");
	
				# on 0th recurse_depth we're checking spool file names
				if ((0 == $_[1]) && (S_ISREG($mode)))
				{
					if ((defined $previous) || (defined $from) || (defined $to))
					{
						my $file_timedate_stamp;

						# split out file name from timedate stamp because file name may contain periods
						if ((defined $megabytes) || (defined $show_file_bytes))
						{
							$entry =~ /^.+\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d+)$/;
							$file_timedate_stamp = $1;
						}
						else
						{
							$entry =~ /^.+\.\d+\.sp\.(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}\.\d+)$/;
							$file_timedate_stamp = $1;
						}

						my ($file_date, $file_microseconds) = split(/\./, $file_timedate_stamp, 4);
						my ($file_year, $file_month, $file_day, $file_hour, $file_minute, $file_second) = split(/_/, $file_date, 6);

						my $specified_from_epoch_seconds = 0;

						if (defined $previous)
						{
							$specified_from_epoch_seconds = todays_midnight_epoch_seconds() - $previous*24*60*60;
						}
						elsif (defined $from)
						{
							$specified_from_epoch_seconds = epoch_seconds(constituent_date_time_elements($from));
						}

						if (epoch_seconds($file_year, $file_month, $file_day, $file_hour, $file_minute, $file_second) < $specified_from_epoch_seconds)
						{
							next;
						}

						if (defined $to)
						{
							if (epoch_seconds($file_year, $file_month, $file_day, $file_hour, $file_minute, $file_second) > epoch_seconds(constituent_date_time_elements($to)))
							{
								next;
							}
						}
					}

					if ((defined $megabytes) || (defined $show_file_bytes))
					{
						push(@unsorted_hits, [$_[0], $entry, $size]);
					}
					else
					{
						process_entry($_[0], $entry, $size);
					}
				}
				elsif (S_ISDIR($mode))
				{
					if (my $rc = descend("$_[0]/$entry", $_[1]-1))
					{
						return $rc;
					}
				}
			}
		}
	}
	
	close DIR;

	return 0;
}

sub todays_midnight_epoch_seconds
{
	my ($current_second, $current_minute, $current_hour, $current_day, $current_month, $current_year, $wday, $yday, $isdst) = localtime(time);
	return timelocal(0, 0, 0, $current_day, $current_month, $current_year);
}

# arguments: year, month (1-12), day (1-31), hour (0-23), minute (0-59), second (0-59)
sub epoch_seconds
{
	# force any undefined or blank values to zero
	# (NOTE: this has been seen to change the passed variables by reference)

	for (my $i=0; $i<6; $i++) {
		if (! $_[$i]) {
			$_[$i] = 0;
		}
	}

	# if month was specified, subtract 1 from it to fit
	# timelocal()'s argument requirements

	return timelocal($_[5], $_[4], $_[3], $_[2], ($_[1] ? $_[1] - 1 : $_[1]), $_[0]);
}

sub process_entry
{
	my $path = shift;
	my $entry = shift;
	my $entry_bytes = shift;

	if (!defined $tar_outfile)
	{
		if (!defined $show_file_bytes)
		{
			print "$path/$entry\n";
		}
		else
		{
			print "$entry_bytes $path/$entry\n";
		}
	}
	else
	{
		# push the trace entry file to list
		push ( @entry_files, "$tar_path_prefix$path/$entry" );

		show_tick_mark();
	}

	$total_spool_files++;
}

sub constituent_date_time_elements
{
	my $datetime = shift;

	my ($date, $time) = split /\./, $datetime;

	my $year, $month, $day, $hour, $minute, $seconds;

	if ($date) {
		($year, $month, $day) = split /-/, $date;
	}

	if ($time) {
		($hour, $minute, $seconds) = split /:/, $time;
	}

	return ($year, $month, $day, $hour, $minute, $seconds);
}

#
# tar the files (@entry_files) to $tar_outfile
sub tar_trace_entry_files
{
    if( scalar(@entry_files) == 0 ) {
       return;
    }

    # Create a initial tar file with 1 spool file, so that we can add files to it via 'tar -uf'
    my $first_file = shift @entry_files;
    system("tar -cvf $tar_outfile $first_file") == 0 or do {
        print STDERR "Failled to create $tar_outfile, $!, RC: $?\n";
        $exitrc = 11;
        return;
    };

    while( scalar(@entry_files) > 0 ) {
        my @entry_files_subset = splice @entry_files, 0, 100;    # Extract 100 files out each time

        if( $osname =~ "Linux" )
        {
            my $tar_cmd = "tar --ignore-failed-read -uvf $tar_outfile @entry_files_subset";
            $exitrc = system($tar_cmd);
            if ($exitrc != 0)
            {
                printf STDERR "Tar command failled with error, $!, RC: $exitrc\n";
                $exitrc = 12;
                last;
            } 
        }
        else
        {
            # AIX or other platforms
            my $tar_cmd = "tar -uvf $tar_outfile @entry_files_subset";
            $exitrc = system($tar_cmd);
            if ($exitrc != 0)
            {
                printf STDERR "Tar command failled with error, $!, $exitrc\n";
                $exitrc = 13;
                last;
            } 
        }
    } #end-of-while

    # Compress whatever has been tarred so far, may or may not be all spool files
    if($gzip_enabled)
    {
        system("gzip -f $tar_outfile");
    }
} # end-of-tar operation