#!/usr/bin/perl
#
# runme.pl, part of the patch-o-matig 'next generation' package.
# (C) 2003-2004 by Harald Welte <laforge@netfilter.org>
#     2004	by Jozsef Kadlecsik <kadlec@blackhole.kfki.ho>
#
# This script is subject to the GNU GPLv2
#
# runme,v 1.14 2004/02/27 21:40:20 kadlec Exp
#

use strict;
use Getopt::Long;
use Pod::Usage;

require Term::Cap;

my $POMNG_ROOT_DIR = '.';
my $VERSION = '1.14';

push(@INC, $POMNG_ROOT_DIR);
use Netfilter_POM;

$|=1;

sub print_header()
{
	print("Welcome to Patch-o-matic ($VERSION)!\n\n");
	printf("Kernel:\t\t%s\n", $Netfilter_POM::srcpath{linux});
	printf("Iptables:\t%s\n\n", $Netfilter_POM::srcpath{iptables});
	print("Each patch is a new feature: many have minimal impact, some do not.\n");
	print("Almost every one has bugs, so don't apply what you don't need!\n");
	print("-------------------------------------------------------\n");
}

sub last_words()
{
	print("\nExcellent! Source trees are ready for compilation.\n\n");
	Netfilter_POM::last_words();
}

sub print_helpfile($)
{
	my ($patchlet) = @_;

	open(HELP, $$patchlet{basedir}.'/'.$$patchlet{help}{linux});
	while (my $line = <HELP>) {
		print("   $line");
	}
	close(HELP);
}

sub print_prompt($)
{
	my ($reverse) = @_;
	my $operation;

	print("-----------------------------------------------------------------\n");
	if ($reverse) {
		$operation = 'REVERT';
	} else {
		$operation = 'apply';
	}
	printf("Do you want to %s this patch [N/y/t/f/a/r/b/w/q/?] ", $operation);
}

sub print_prompt_help()
{
	print("Answer one of the following:\n");
	print("  T to test that the patch will apply cleanly\n");
	print("  Y to apply patch\n");
	print("  N to skip this patch\n");
	print("  F to apply patch even if test fails\n");
	print("  A to restart patch-o-matic in apply mode\n");
	print("  R to restart patch-o-matic in REVERSE mode\n");
	print("  B to walk back one patch in the list\n");
	print("  W to walk forward one patch in the list\n");
	print("  Q to quit immediately\n");
	print("  ? for help\n");
}


# main

my @patchdirs;
my @repositories;
my @applied;
my $clrscr = "\n\n\n\n\n\n\n\n\n\n";

my $opt_batch; 
my $opt_test;
my $opt_check;
my $opt_reverse;
my $opt_repository;
my $opt_help;
my $opt_man;
my @opt_exclude;
my $opt_path;

my $result = GetOptions("batch" => \$opt_batch,
			"test" => \$opt_test,
			"check" => \$opt_check,
			"reverse" => \$opt_reverse,
			"exclude=s" => \@opt_exclude,
			"help" => \$opt_help,
			"man" => \$opt_man,
			"path=s" => \$opt_path) or pod2usage(2);

pod2usage(-verbose=>2, -exitval=>0) if $opt_man;
pod2usage(-verbose=>1, -exitval=>0) if $opt_help;

if ($#ARGV >= 0 && (-f ($ARGV[0].'/info'))) {
	# user wants to specify patches manually
	for my $p (@ARGV) {
		if (-f "$p/info") {
			push(@patchdirs, $p);
		}
	}
	# magic 'any' repository
	$opt_repository = 'any';
} else {
	# no patch names given on cmdline, have to consider all patches
	#
	my @unsorted;
	opendir(INDIR, $POMNG_ROOT_DIR);
	my @allfiles = readdir(INDIR);
	closedir(INDIR);
	for my $f (@allfiles) {
		if (-f "$f/info") {
			push(@unsorted, $f);
		}
	}
	@patchdirs = sort @unsorted;

	if ($#ARGV == 0) {
		# single argmument is a repository name 
		$opt_repository = $ARGV[0];
	} elsif ($#ARGV == -1) {
		# no repository given, select 'pending'
		$opt_repository = 'pending';
	} else {
		pod2usage(-verbose=>1, -exitval=>2);
	}
}

# remove all 'excluded' patches
my @new_patchdirs;
PATCHDIR: for my $p (@patchdirs) {
	for my $e (@opt_exclude) {
		if ($e eq $p) {
			next PATCHDIR;
		}
	}
	push(@new_patchdirs, $p);
}
@patchdirs = @new_patchdirs;
			

# FIXME implement this as config file, not hardcoded 
if ($opt_repository) {

	if ($opt_repository eq 'any') {
		push(@repositories, 'any');
		goto OUT;
	}

	push(@repositories, 'submitted');
	if ($opt_repository eq 'submitted') {
		goto OUT;
	}
	push(@repositories, 'pending');
	if ($opt_repository eq 'pending') {
		goto OUT;
	}
	push(@repositories, 'base');
	if ($opt_repository eq 'base') {
		goto OUT;
	}
	push(@repositories, 'extra');
	if ($opt_repository eq 'extra') {
		goto OUT;
	}
	push(@repositories, $opt_repository);
OUT:
}

my $kpath = '/usr/src/linux';

if (!defined($ENV{KERNEL_DIR}) && !$opt_path) {
	print("Hey! KERNEL_DIR is not set.\n");
	print("Where is your kernel? [$kpath] ");
	my $kptmp = <STDIN>;
	chomp($kptmp);
	if ($kptmp ne '') {
		$kpath = $kptmp;
	}
} elsif ($opt_path) {
	pod2usage(2) unless $opt_path =~ /:/;
	($kpath = $opt_path) =~ s/:.*//;
} else {
	$kpath = $ENV{KERNEL_DIR};
}
# print("Examining kernel in $kpath\n");
if (! -d $kpath) {
	print STDERR ("$kpath doesn't seem to be a directory\n");
	exit(1);
}
if (! -f "$kpath/Makefile") {
	print STDERR ("$kpath doesn't look like a kernel tree to me.\n");
	exit(1);
}

my $ipath = '/usr/src/iptables';
if (!$opt_path) {
	print("Where is your iptables source code? [$ipath] ");
	my $iptmp = <STDIN>;
	chomp($iptmp);
	if ($iptmp ne '') {
		$ipath = $iptmp;
	}
} else {
	($ipath = $opt_path) =~ s/.*?://;
}

# print("Examining iptables source in $ipath\n");
if (! -d $ipath) {
	print STDERR ("$ipath doesn't seem to be a directory\n");
	exit(1);
}
if (! -f "$ipath/Makefile") {
	print STDERR ("$ipath doesn't look like an iptables source tree to me.\n");
	exit(1);
}

if (defined($ENV{TERM}) && !$opt_batch) {
	my $terminal = Tgetent Term::Cap {};
	$clrscr = $terminal->Tputs('cl', 1);
}

# list of selected patchlets misleading at this stage
# print STDERR ("selected repositories: ",join("|",@repositories),"\n");
# print STDERR ("selected patchlets: ",join("|",@patchdirs),"\n");

print("Loading patchlet definitions...");
my $plets = Netfilter_POM::parse_patchlets($POMNG_ROOT_DIR);
printf(" done\n\n");

Netfilter_POM::init($kpath, $ipath);
sleep 1;

for my $rep (@repositories) {

	PATCHLET: for my $plname (sort @patchdirs) {
		my $patchlet = $$plets{$plname};
		if ($rep ne $$patchlet{info}{repository} &&
		    $rep ne 'any') {
			next PATCHLET;
		}

		print($clrscr);
		print_header();
		print("Already applied: ", join(" ", @applied),"\n\n");

		if (!Netfilter_POM::requirements_fulfilled($patchlet)) {
			printf("Not all requirements fulfilled for $plname, skipping:\n %s\n", 
				Netfilter_POM::strerror());
			next PATCHLET;
		}
		print("Testing ${plname}... ");
		if (Netfilter_POM::apply($patchlet, !$opt_reverse, 1)) {
			print("applied\n");
			push(@applied, $plname);
			next PATCHLET;
		}
		print("not applied\n");
		printf("The %s patch:\n", $plname);
		printf("   Author: %s\n", $$patchlet{info}{author});
		printf("   Status: %s\n\n", $$patchlet{info}{status});
		print_helpfile($patchlet);
		my $first_try = 1;
	PROMPT:
		my $key;
		if ($opt_batch && $first_try) {
		    $key = 'y';
		    $first_try = 0;
		} else {
		    print_prompt($opt_reverse);
		    $key = <STDIN>;
		}
		chomp($key);
		if ($key eq 'y') {
			my $ret = Netfilter_POM::dependencies_fulfilled($plets,
								$plname);
			if ($ret == 0) {
				if (!Netfilter_POM::apply_dependencies($plets,
								$plname, 0, 1) ||
				    !Netfilter_POM::apply_dependencies($plets,
				    				$plname, 0, 0)) {
					Netfilter_POM::perror();
					goto PROMPT;
				}
			} elsif ($ret == -1) {
				# conflict
				Netfilter_POM::perror();
				goto PROMPT;
			}
			if (Netfilter_POM::apply($patchlet, $opt_reverse, 1) &&
			    Netfilter_POM::apply($patchlet, $opt_reverse, 0)) {
				push(@applied, $plname);
			} else {
				Netfilter_POM::perror();
				goto PROMPT;
			}
		} elsif ($key eq 't') {
			if (!Netfilter_POM::apply($patchlet, $opt_reverse, 1)) {
				Netfilter_POM::perror();
			} else {
				print "Patch $plname applies cleanly\n";
			}
			goto PROMPT;
		} elsif ($key eq 'f') {
			my $ret = Netfilter_POM::dependencies_fulfilled($plets,
								$plname);
			if ($ret == 0) {
				if (!Netfilter_POM::apply_dependencies($plets,
							$plname, 1, 0)) {
					Netfilter_POM::perror();
					next PATCHLET;
				}
			} elsif ($ret == -1) {
				Netfilter_POM::perror();
				next PATCHLET;
			}
			Netfilter_POM::apply($patchlet, $opt_reverse, 0);
			push(@applied, $plname);
		} elsif ($key eq 'a') {
			print("not implemented yet");
			goto PROMPT;
		} elsif ($key eq 'r') {
			$opt_reverse = !$opt_reverse;
			goto PROMPT;
		} elsif ($key eq 'b') {
			print("not implemented yet");
			goto PROMPT;
		} elsif ($key eq 'w') {
			next PATCHLET;
		} elsif ($key eq 'q') {
			last_words();
			exit(0);
		} elsif ($key eq '?') {
			print_prompt_help();
			goto PROMPT;
		}
	}
}
last_words();


__END__

=head1 NAME

runme - The patch-o-matic main program

=head1 SYNOPSIS

./runme  [--batch] [--reverse] [--exclude suite/patch-dir ] suite|suite/patch-dir

=head1 OPTIONS

=over 8

=item B<--batch>

batch mode, automatically applying patches.

=item B<--test>

test mode, automatically test patches.

=item B<--check>

check mode, automatically checks if patches are alreay applied.
produces a logfile: rune.out-check

=item B<--reverse>

back out the selected patches.

=item B<--exclude> suite/patch-dir

excludes the named patch. can be used multiple times.

=item B<--help>

print a help message

=item B<--man>

print the whole manpage

=back

=head1 DESCRIPTION

B<runme> is the main executable of the patch-o-matic system

=cut
