#!/usr/bin/perl
# Filename:	exec_trace
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License/
# Description:	Run a command with the exec_trace LD_PRELOAD library
# Similar to (but cleaner and more controllable) than:
#   % strace -f -e trace=execve -o <log> <command>
#
# Can also continue tracing through submission farms (presuming they maintain LD_PRELOAD)
# (And doesn't have the path retries that strace shows with exec*p variants)
#
use strict;

##################################################
# Setup the variables
##################################################
my ($BASENAME,$PROGNAME) = ($0 =~ m|(.*)/(.+)|) ? ($1?$1:'/',$2) : ('.',$0);

##################################################
# Usage
##################################################
sub fatal {
	foreach my $msg (@_) { print STDERR "[$PROGNAME] ERROR:  $msg\n"; }
	exit(-1);
}

sub usage {
	foreach my $msg (@_) { print STDERR "ERROR:  $msg\n"; }
	print STDERR <<USAGE;

Usage:\t$PROGNAME <trace options> -- <cmd> <cmd options..>
  Run <cmd> <args> with command/env/redirect tracing that persists down children

Options for the wrapper (before the '--')
  -log <file>    Where to print tracing info (default STDERR)
  -f             Force - log overwrites, force LD_PRELOAD overwrites
  -env           Use 'env <env vars> <cmd>' for single line command output
  -bin           Don't output any commands found in /bin  (mkdir, pwd, ls, .....)
  -pipe          Show pipe:<num> for pipes as stdout/stderr

Command is anything after the '--'

USAGE
	exit -1;
}

sub parseArgs {
	my $opt = {};
	my @cmd = ();
	my ($log,$f);
	while (my $arg=shift(@ARGV)) {
		if ($arg =~ /^-h$/) { usage(); }
		if ($arg =~ /^-f(orce)?$/) { $opt->{f} = 1; next; }
		if ($arg =~ /^-env$/) { $opt->{env} = 1; next; }
		if ($arg =~ /^-bin$/) { $opt->{bin} = 1; next; }
		if ($arg =~ /^-pipe$/) { $opt->{pipe} = 1; next; }
		if ($arg =~ /^-log$/) { $opt->{log} = shift(@ARGV); next; }
		if ($arg =~ /^--/) { @cmd = @ARGV; last; }
		usage("Unknown arg: $arg");
	}

	usage("No command defined, call with '-- cmd args...'") unless @cmd;

	if ($opt->{log}) {
		unlink($opt->{log}) if $opt->{f} && -f $opt->{log};
		usage("Log file already exists [$opt->{log}].  Use '-f' to force overwrite") if -f $opt->{log};
		$ENV{EXEC_TRACE_LOG} = $opt->{log};
	}

	$ENV{EXEC_TRACE_ENV}=1 if $opt->{env};
	$ENV{EXEC_TRACE_BIN}=1 if $opt->{bin};
	$ENV{EXEC_TRACE_PIPE}=1 if $opt->{pipe};

	($opt,@cmd);
}

##################################################
# Main code
##################################################
sub main {
	my ($opt,@cmd) = parseArgs();

	my $preload = "$BASENAME/$PROGNAME.so";
	if ($ENV{LD_PRELOAD}) {
		if ($ENV{LD_PRELOAD} ne $preload) {
			delete $ENV{LD_PRELOAD} if $opt->{f};
			usage("LD_PRELOAD is already set to a different library? [$ENV{LD_PRELOAD}]") if $ENV{LD_PRELOAD};

		} else {
			# This is the rerun of this command with LD_PRELOAD set,
			# now we actually execute the cmd
			exec(@cmd);
		}

	} else {
		# Set it and run this script again, so the LD_PRELOAD sees the exec of the first command
		$ENV{LD_PRELOAD} = $preload;
		exec($0,'--',@cmd);
	}
}
main();
