#!/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.17 2004/05/11 13:53:13 kadlec Exp
#

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

require Term::Cap;

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

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

$|=1;

sub print_header {
	my $session = shift;

	print("Welcome to Patch-o-matic ($VERSION)!\n\n");
	printf("Kernel:   %s, %s\n", $session->{projects}->{linux}->{VERSION}, 
				     $session->{projects}->{linux}->{PATH});
	printf("Iptables: %s, %s\n", $session->{projects}->{iptables}->{VERSION},
				     $session->{projects}->{iptables}->{PATH});
	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");
	$session->perror;
}

sub last_words {
	my $session = shift;

	print("\nExcellent! Source trees are ready for compilation.\n\n");
	$session->last_words;
}

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 @patchlets;
my @repositories;
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 $opt_iptpath;

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,
			"kernel-path=s" => \$opt_path,
			"iptables-path=s" => \$opt_iptpath) or pod2usage(2);

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

my %paths;
my @paths = (
	{ 'project'	=> 'linux',
	  'env'		=> 'KERNEL_DIR',
	  'opt'		=> $opt_path,
	  'txt'		=> 'kernel source',
	  'default'	=> '/usr/src/linux',
	  'check'	=> 'Makefile',
	},
	{ 'project'	=> 'iptables',
	  'env'		=> 'IPTABLES_DIR',
	  'opt'		=> $opt_iptpath,
	  'txt'		=> 'iptables source code',
	  'default'	=> '/usr/src/iptables',
	  'check'	=> 'Makefile',
	}
);

foreach my $path (@paths) {
	if (!defined($ENV{$path->{env}}) && !$path->{opt}) {
		print("Hey! $path->{env} is not set.\n");
		print("Where is your $path->{txt} directory? [$path->{default}] ");
		my $ptmp = <STDIN>;
		chomp($ptmp);
		if ($ptmp ne '') {
			$paths{$path->{project}} = $ptmp;
		} else {
			$paths{$path->{project}} = $path->{default};
		}
	} elsif ($path->{opt}) {
		$paths{$path->{project}} = $path->{opt};
	} else {
		$paths{$path->{project}} = $ENV{$path->{env}};
	}
	if (!(-d $paths{$path->{project}}
	      || (-l $paths{$path->{project}} && -d readlink($paths{$path->{project}})))) {
		print STDERR ("$paths{$path->{project}} doesn't seem to be a directory\n");
		exit(1);
	}
	if (! -f (glob("$paths{$path->{project}}/$path->{check}"))[0]) {
		print STDERR ("$paths{$path->{project}} doesn't look like a $path->{txt} directory to me.\n");
		exit(1);
	}
}

if ($#ARGV >= 0 && (-f "$ARGV[0]/info" || -f "$ARGV[0].info")) {
	# patch or update file
	# user wants to specify patches manually
	for my $p (@ARGV) {
		if (-f "$p/info") {
			push(@patchlets, $p);
		} elsif (-f "$p.info") {
			push(@patchlets, $p);
		}
	}
	# magic repository
	$opt_repository = 'magic';
} else {
	# no patch names given on cmdline, have to consider all patches
	#
	opendir(INDIR, $POMNG_ROOT_DIR);
	my @allfiles = readdir(INDIR);
	closedir(INDIR);
	for my $f (@allfiles) {
		if (-f "$f/info") {
			push(@patchlets, $f);
		}
	}
	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_patchlets;
for my $p (@patchlets) {
	push(@new_patchlets, $p)
		unless grep($_ eq $p, @opt_exclude);
}
@patchlets = sort(@new_patchlets);
# 'updates' is mandatory, unless patch is explicitly specified
unshift(@patchlets, sort(grep { $_ =~ m,.*/(.*)\.info, && ($_ = $1) } glob("$POMNG_ROOT_DIR/updates/*.patch.info")))
	unless $opt_repository eq 'magic';

# FIXME implement this as config file, not hardcoded
if ($opt_repository eq 'magic') {
	push(@repositories, $opt_repository);
} else {
	foreach my $rep (qw(updates submitted pending base extra)) {
		push(@repositories, $rep);
		last if $opt_repository eq $rep;
	}
}

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

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

$paths{POM} = $POMNG_ROOT_DIR;
my $session = Netfilter_POM->init(\%paths);
print("Loading patchlet definitions");
$session->parse_patchlets;
printf(" done\n\n");
sleep 1;

for my $rep (@repositories) {

	PATCHLET: for my $plname (@patchlets) {
		my $patchlet = $session->{patchlets}->{$plname};
		next PATCHLET unless defined $patchlet
				     && ($rep eq $patchlet->{info}->{repository}
				         || $rep eq 'magic');

		print($clrscr);
		print_header($session);
		print("Already applied: ", join(" ", @{$session->{applied}}),"\n\n");

		next PATCHLET unless $session->requirements_fulfilled($patchlet);
		print("Testing ${plname}... ");
		if (grep($_ eq $plname, @{$session->{applied}})) {
			print("applied\n");
			next PATCHLET;
		} elsif ($session->apply_patchlet($patchlet, !$opt_reverse, 1)) {
			print("applied\n");
			push(@{$session->{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", $patchlet->{info}->{status});
		printf("   Version: %s\n", $patchlet->{info}->{version})
			if exists $patchlet->{info}->{version};
		print "\n";
		print $patchlet->{info}->{help};
		my $first_try = 1;
		$session->{ERRMSG} = '';
	PROMPT:
		my $key;
		if ($opt_batch && $first_try) {
		    $key = 'y';
		    $first_try = 0;
		} else {
		    print_prompt($opt_reverse);
		    $key = <STDIN>;
		}
		chomp($key);
		$key = lc($key);
		if ($key eq 'y') {
			if ($session->dependencies_fulfilled($patchlet) < 0) {
				# conflict
				$session->perror;
				goto PROMPT;
			}
			if ($session->apply($patchlet, $opt_reverse, 1)
			    && $session->apply($patchlet, $opt_reverse, 0)) {
				if (!$opt_reverse) {
					push(@{$session->{applied}}, $plname);
				} else {
					$session->{applied} = [ grep($_ ne $plname, @{$session->{applied}}) ];
				}
			} else {
				$session->perror;
				goto PROMPT;
			}
		} elsif ($key eq 't') {
			if ($session->dependencies_fulfilled($patchlet) < 0
			    || !$session->apply($patchlet, $opt_reverse, 1)) {
				$session->perror;
			} else {
				print "Patch $plname applies cleanly\n";
			}
			goto PROMPT;
		} elsif ($key eq 'f') {
			my $ret = $session->dependencies_fulfilled($patchlet);
			if ($ret == 0) {
				next PATCHLET unless 
					$session->apply_dependencies($patchlet, 1, 0);
			} elsif ($ret == -1) {
				next PATCHLET;
			}
			$session->apply($patchlet, $opt_reverse, 0);
			push(@{$session->{applied}}, $plname);
		} elsif ($key eq 'a') {
			$opt_reverse = !$opt_reverse if $opt_reverse;
			goto PROMPT;
		} elsif ($key eq 'r') {
			$opt_reverse = !$opt_reverse unless $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($session);
			exit(0);
		} elsif ($key eq '?') {
			print_prompt_help();
			goto PROMPT;
		}
	}
}
last_words($session);

__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

=item B<--kernel-path>

specify the kernel source path

=item B<--iptables-path>

specify the iptables source path

=back

=head1 DESCRIPTION

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

=cut
