#!/usr/bin/perl -w
##
## Copyright (c) 2001, 2002, Amnon BARAK (amnon@MOSIX.org).
## All rights reserved.
## This software is subject to the MOSIX SOFTWARE LICENSE AGREEMENT.
##
## THIS SOFTWARE IS PROVIDED IN ITS "AS IS" CONDITION, WITH NO WARRANTY
## WHATSOEVER. NO LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING
## FROM THE USE OF THIS SOFTWARE WILL BE ACCEPTED.
##
##
## Author(s): A.Spanier , Shlomo Matichin 
##

# This script untars, configures, and builds a new kernel. 
# The MOSIX patch is applied automatically.

# Default arguments currently compatible with all linux distributions,
# though some arguments must be specified.

use Getopt::Long;
use FindBin;
use lib "$FindBin::Bin";
use common;

$source_dir	= glob "$FindBin::Bin/.." ;
$kernel_dir	= "/usr/src/linux-$kernel_version" ;
$kernel_tar	= "" ;
$config		= "menuconfig";
$menu_tty	= "";
$pid_to_sig	= '';

GetOptions( 	"kernel_dir=s" ,		\$kernel_dir ,
		"kernel_tar=s" ,		\$kernel_tar ,
		"check_mfs_configed",		\$check_mfs_configed ,
		"skip_mandatory_config",	\$skip_mandatory_config ,
		"config=s" ,			\$config ,
		"menu_tty=s" ,			\$menu_tty ,
		"pid_to_sig=s" ,		\$pid_to_sig ,
		"cc=s" ,			\$CC ,
		"silent" ,			\$silent ,
		"help" ,			\$usage ,
		"uninstall" ,			\$uninstall );

@mandatory_config = (
[ 'CONFIG_MOSIX' , 'Y' , 'Yn' ,
	"The main MOSIX switch (eg. process migration) was disabled:\n".
	"You should really have it enabled to use MOSIX!  OK?\n" ],
[ 'CONFIG_BINFMT_ELF' , 'Y' , 'Yn' ,
	"Support for ELF binary format was omitted from the kernel configuration.\n".
	"MOSIX cannot function without it!  OK to enable?\n" ],
[ 'CONFIG_PROC_FS' , 'Y' , 'Yn' ,
	"The proc file system was omitted from the kernel configuration.\n".
	"MOSIX cannot function without it!  OK to enable?\n" ],
);
if( defined $check_mfs_configed ) {
	push @mandatory_config , [ 'CONFIG_MOSIX_FS', 'Y' , 'Ydn' ,
	"You chose to create a MFS mount point, but the correct option was\n".
	"omitted from the kernel configuration. OK to enable this option\n".
	"now?\n" ];
}
if( `grep ext3 -i /etc/fstab` =~ /ext3/ ) {
	push @mandatory_config , [ 'CONFIG_EXT3_FS' , 'Y' , 'Ymn' ,
	"Your system seems to be dependant on the ext3 file system, yet you\n".
	"did not configure it. It is HIGHLY recommended that you compile\n".
	"the kernel with ext3 file system support. OK to enable this\n".
	"option now?\n" ];
}

$source_dir =~ s/\/$// ;
$kernel_dir =~ s/\/$// ;

$config eq 'menuconfig' or $config eq 'config' or $config eq 'xconfig' or
	dead "--config must be one of the followings: config, menuconfig or xconfig";
usage() if defined $usage ;

-e "$source_dir/patches.$kernel_version" or 
    dead "MOSIX distribution is not intact: unable to find $source_dir/patches.$kernel_version";

if (defined $uninstall) {
	uninstall();
	exit(0);
}

if( defined $silent) {$SILENCE="-s";} else {$SILENCE="";}
if( defined $CC ) {
	$CC = "CC=$CC HOSTCC=$CC";
} else {
	if( safe_pipe_stderr_too("gcc -v") =~ /gcc\ version\ 2.7.2.3/ ) {
		dead "gcc version 2.7.2.3 is extremely old and cannot compile the kernel";
	} else {
		$CC = '';
	}
}
$kernel_tar and unpack_kernel_tar() and exit 1;
$kernel_dir or dead "No kernel sources tree directory specifeid!\n";
-w $kernel_dir or dead "You do not have write permissions on $kernel_dir";
install();
exit 1;
    
sub install
{
	chdir( $kernel_dir ) or 
		dead "Unable to change directory into $kernel_dir";
	chdir( ".." ) or
		dead "Unable to change directory into $kernel_dir/..";

	if ($kernel_dir ne "\/usr\/src\/linux") {
		chomp( $link_dir = `pwd` );
		$link_dir.="/linux";
		-w "/usr/src" and $link_dir = "/usr/src/linux";
		if(system( "ln -snf $kernel_dir $link_dir" )) {
			chomp($t = `pwd`);
			print "\nWarning: Unable to link $kernel_dir to $t/linux\n";
		}
	}

	chdir( $kernel_dir ) or
		dead "Unable to change directory into $kernel_dir";
	verify_kernel_version("./") and dead;
	-d "$kernel_dir/mos" and
		dead "Please Uninstall mosix";
	system( "cp -f $source_dir/patches.$kernel_version mosix_patches" ) and
		dead "Unable to copy patches to local directory";

	system( "patch -p0 --dry-run < mosix_patches > /dev/null" ) and
		dead "Patch cannot be applied.";
	print "Patching kernel...\n";
	system( "patch -p0 < mosix_patches > /dev/null" ) ;
	$temp_dir = "/tmp";
	-w $temp_dir or $temp_dir = '.';
	print "Running make $config...\n";
	if( not $menu_tty ) {
		system( "make $CC $SILENCE $config" ) and
			dead "Make $config failed\n";
	} else {
		kill( USR1 , $pid_to_sig );
		system( "make $CC $SILENCE $config > $menu_tty < $menu_tty 2>&1" ) and
			dead "Make $config failed\n";
		kill( USR2 , $pid_to_sig );
	}
	$skip_mandatory_config or check_mandatory_config( ".config" );
	print "\nCompiling the Kernel:\n";
	print "Checking dependencies...\n";
	unless($SILENCE eq "") {$direct=">/dev/null 2>&1";} else {$direct="";}
	system( "make $CC $SILENCE dep $direct " ) and
		dead "Make dep failed.";
	print "Compiling the main kernel...\n";
	system( "make $CC $SILENCE bzImage " ) and
		dead "Make bzImage failed.";
	print "Compiling the kernel modules...\n";
	system( "make $SILENCE $CC modules " ) and
		dead "Make modules failed. log file is on $temp_dir/make_modules_log";
	print "Installing the kernel modules...\n";
	system( "make $SILENCE $CC modules_install " ) and
		dead "Make modules_install failed.";
	print "\nKernel compiled successfully.\n";
	
	exit ;
}

sub unpack_kernel_tar
{
	$kernel_dir = $save_kernel_dir = "/usr/src/linux-$kernel_version" ;
  
	if( -e $save_kernel_dir ) {
		$move_kernel = 1;
                for($i=1 ; ; $i++) {
                        -e ($save_kernel_dir = "/usr/src/linux-$kernel_version-$i") or last;
                }
        }

	-e $kernel_tar or 
		dead "$kernel_tar was not found";
	-r $kernel_tar or
		dead "You do not have read permissions on $kernel_tar";
	$kernel_tar =~ /$kernel_version/ or
		print "Warning, kernel tar does not look like the correct ".
			"linux kernel version!\n";
	-w "/usr/src" or
		dead "You do not have write permissions on /usr/src";
	-l "/usr/src/linux" and ( system( "rm -f /usr/src/linux" ) and 
		dead "Unable to remove old link /usr/src/linux" ) ;

	if ($move_kernel) {
		print "Saving your previous kernel in $save_kernel_dir. \n";
		system("mv -f $kernel_dir $save_kernel_dir") and
			 dead "Unable to move directory kernel_dir \n";
	}

	print "Untarring kernel. Please wait...\n";
	if( $kernel_tar =~ /\.tar\.gz/ ) {
		system( "tar -xzf $kernel_tar --directory /usr/src" ) and 
			dead "Unttaring of $kernel_tar into /usr/src failed";	
	} elsif( $kernel_tar =~ /\.tar\.bz2/ ) {
		system( "bzip2 -d < $kernel_tar | tar -x --directory /usr/src" ) and
			dead "Unttaring of $kernel_tar into /usr/src failed";
	} else {
		dead "Kernel file must be a .tar.gz or .tar.bz2 file";	
	}

} 

sub usage {
	print "Usage : compile_kernel [OPTION]\n";
	print "--kernel_dir=   the location of a original linux-".
		"$kernel_version sources tree\n";
	print "--kernel_tar=   the location of a original linux-".
		"$kernel_version zipped tar file\n";
	print "--config=       which configuration method to use: config,\n".
              "                menuconfig, or xconfig (default: menuconfig)\n";
	print "--CC=	        which compiler will be use to compile the Kernel\n";
	print "--silent        silent make (display only errors)\n";
	print "--uninstall     uninstall\n";
	print "--help          this message\n";		
	print "  NOTE: exactly one of --kernel_dir and --kernel_tar are mandatory\n";
	exit ;
}

sub uninstall {
	if( not -d "$kernel_dir/mos" ) {
		print "\n$kernel_dir does not look like a MOSIX kernel directory!\n";
		exit 1;
	}
	if (not chdir( "$kernel_dir" )) {
		print "Unable to change directory into $kernel_dir";
		exit 1
	}
	if (not -e "mosix_patches") {
		print "\nSorry, backtrack log ($kernel_dir/mosix_patches) is missing!\n";
		exit 1;
	}
	system( "patch -p0 -R < mosix_patches > /dev/null" ) and
		dead "Unpatching of kernel sources failed!";

	system( "rm -f mosix_patches" ) and
		dead "Unable to erase $kernel_dir/mosix_patches";

	system( "rm -rf mos" ) and
		dead "Unable to delete $kernel_dir/mos";

	system( "rm -rf include/mos" ) and
		dead "Unable to delete $kernel_dir/include/mos";

	system( "rm -rf fs/mfs" ) and
		dead "Unable to delete $kernel_dir/fs/mfs";

	system( "rm -rf udb" ) and
		dead "Unable to delete $kernel_dir/udb";
	
}

# the next function checks and fixes the configuration file if required so.
# format of @mandatory config:
# each item in the array is a reference to an array that is used as a record:
# <option_name>,<condition>,<possible_answers>,<question>
# the first item is the configuration option name. the second is on which
# condition to ask the question:
# Y: if config is not Y,
# y: if config is not Y and not module,
# N: if config is not N,
# the third filed is the possible answers:
# y for yes, n for no, d to leave the current answer, and m for module.
# the uppercase letter is the default.
# the third field is the question to present to the user if the confition is 
# met
sub check_mandatory_config {
	my $config_file_name = shift;
	open( CONFIG , $config_file_name ) or
		dead "Unable to read '$config_file_name'";
	my @config = <CONFIG>;
	close( CONFIG );
	my @config_options = ();
	foreach my $line (@config) {
		$line =~ /^\s*(\w+)\s*\=\s*([ynm])\s*\n$/ and
			$config_options{ $1 } = $2 or
		$line =~ /^\s*\#\s*(\w+)\s*is\ not\ set\s*\n$/ and
			$config_options{ $1 } = 'n';
	}
	my $changed = 0;
	foreach my $option( 0..$#mandatory_config ) {
		my @config = @{ $mandatory_config[ $option ] };
		my $option_name = $config[0];
		my $option_condition = $config[1];
		my $option_answers = $config[2];
		my @option_answers = split // , $option_answers;
		my $default = '';
		$option_answers =~ /([[:upper:]])/ and
			$default = $1;
		my $option_question = $config[3];
		my $ask = 0;
		$config_options{ $option_name } or
			$config_options{ $option_name } = 'n';
		if( $option_condition eq 'Y' ) {
			not $config_options{$option_name} eq 'y' and $ask = 1;
		} elsif( $option_condition eq 'y' ) {
			$config_options{$option_name} eq 'n' and $ask = 1;
		} elsif( $option_condition eq 'N' ) {
			not $config_options{$option_name} eq 'n' and $ask = 1;
		}
		#skip asking this question if the configuration is ok
		$ask or next;
		chomp( $option_question );
		print STDOUT "\n$option_question\n";
		foreach my $letter ( @option_answers ) {
			$letter =~ /y/i and
				print "Y - compile the option into the kernel";
			$letter =~ /n/i and
				print "N - exclude the option from the kernel";
			$letter =~ /m/i and
				print "M - compile the option as a loadable".
					" module";
			$letter =~ /d/i and
				print "D - leave the option unchanged";
			$letter =~ /[[:upper:]]/ and
				print " (default)";
			print "\n";
		}
		my $input = '';
		while(1) {
			print "[$option_answers[0]";
			foreach my $letter ( @option_answers[1..
						$#option_answers] ) {
				print "/$letter";
			}
			print "] :-  ";
			chomp( $input = <STDIN> );
			if( scalar grep /^$input$/i , @option_answers ) {
				last;
			}
			defined $default and $input eq '' and
				($input = $default) and last;
		}
		$input =~ tr/[A-Z]/[a-z]/;
		$input eq $config_options{ $option_name } or $changed = 1;
		$config_options{ $option_name } = $input;
	}
	#skip rewriting the file if no config was changed
	$changed or return;
	open( CONFIG , ">$config_file_name" ) or
		dead "Unable to open '$config_file_name' for writing\n";
	foreach my $line (@config) {
		if( $line =~ /^\s*(\w+)\s*\=\s*([ynm])\s*\n$/ or
		    $line =~ /^\s*\#\s*(\w+)\s*is\ not\ set\s*\n$/) {
			my $option_name = $1;
			$config_options{ $option_name } eq 'n' and
				print CONFIG "# $option_name is not set\n" or
				print CONFIG "$option_name=",
					"$config_options{$option_name}\n";
			undef $config_options{ $option_name };
		} else {
			 print CONFIG $line;
		}
	}
	foreach my $option_name ( @config_options ) {
		$config_options{ $option_name } eq 'n' and
			print CONFIG "# $option_name is not set\n" or
			print CONFIG "$option_name=",
				"$config_options{$option_name}\n";
	}
	close( CONFIG );
	system( "yes '' | make config > /dev/null 2>&1" ) and
		dead "Unable to update written configuration (by 'yes '' | make config')";
}
