#!/usr/bin/perl -w
#
# /usr/sbin/deloconfig -- configure delo automatically
#
# Author:	Bruce Perens <bruce@Pixar.com>,
#		Bernd Eckenfels <ecki@debian.org>,
#		Vincent Renardias <vincent@ldsol.com>,
#		Peter Maydell <pmaydell@chiark.greenend.org.uk>
#		Russell Coker <russell@coker.com.au>
#		Gergely Nagy <algernon@debian.org>
#
# Maintainer:   Gergely Nagy <algernon@debian.org>,
#
# Updated on 1999/01/24 -- Vincent Renardias <vincent@ldsol.com>
#  - never return 0 on error.
#  - updated the template to produce a more helpfull (commentwise)
#      resulting lilo.conf
#
# Updated on 1999/11/24 -- Peter Maydell <pmaydell@chiark.greenend.org.uk>
#  - added error checking of various system calls
#  - added $DEBUG switch and pulled lilo.conf and fstab filenames
#    out into config variables.
#  - turned on Perl's -w switch and use strict subs/refs
#  - now does examination of current situation up front, separated
#    from the logic of what we do in various situations.
#  - added check for special marker in /etc/fstab that indicates that we
#    are configuring the base filesystem and shouldn't actually do anything.
#
# Updated on 2000-01-23 -- Peter Maydell <pmaydell@chiark.greenend.org.uk>
#  - fixed a (harmless) warning produced if /etc/fstab had a blank line in it
#  - fixed a bug where partition number was not being pulled out of the
#    /dev/hda4 string correctly.
#  - added a paranoia check that the disk/device we get from fstab actually
#    exist in the filesystem.
#  - fixed flow-of-control problem where we simply weren't ever installing
#    MBR and making partition active.
#  - added & to some function calls, for consistency.
#  - changed all references to /usr/doc/lilo/ to /usr/share/doc/lilo/.
#  - added warning that the lilo.conf we produce does not suffice for
#    complicated situations.
#  - expanded some of the other prompts and explanatory text presented
#    to the user.
#  - added a 'ruler' to separate out questions
#  - questions no longer accept anything except RET as meaning 'go with
#    default answer'. [Previously, if the default answer was 'yes',
#    anything not beginning with Y or y would be interpreted as 'no'.
#    This is IMHO too lenient considering the consequences of getting
#    it wrong...]
#  - updated Maintainer, Author and Wishlist...
#
# Updated on 2000-01-25 -- Peter Maydell <pmaydell@chiark.greenend.org.uk>
#  - fixed important bug causing lilo not to install on RAID arrays.
#    (Bugs #56153,#56183,#56196)
#  - minor improvement to a regexp (no actual functional change) (Bug#56127)
#  - expanded cryptic 'iff' comment (Bug#56127)
#
# Updates on 2001-05-04 -- Russell Coker <russell@coker.com.au>
#  - more work on devfs root support
#
# Updates on 2001-06-22 -- Gergely Nagy <algernon@debian.org>
#  - migrate to delo, which is like lilo, but different
#
# Updated on 2001-06-23 -- Gergely Nagy <algernon@debian.org>
#  - prompt for system console
#
# Updated on 2001-08-05 -- Gergely Nagy <algernon@debian.org>
#  - make prompting for system console optional

use strict 'subs';
use strict 'refs';
# use strict 'vars' falls over on all the global variables :->

# Set this to 1 to disable all commands that do things to the
# hard disk (ie actually running delo). Note that we still write
# to $DELOCONF, so you should also tweak that to get a 'safe' test
# environment.
#$DEBUG=1;

# Various files we access
$DELOCONF='/etc/delo.conf';
$FSTAB='/etc/fstab';

$|=1;

# Print a banner now, to give the user something to look at while
# we ferret around in the fstab...
print "DELO, the DEcstation LOader, sets up your system to boot Linux directly\n";
print "from your hard disk, without the need for a boot floppy.\n";

# First we analyse the setup and set variables appropriately
$fstab_broken = 1;	    # is there a valid /etc/fstab? Assume not and prove otherwise.
$deloconf_exists = 0;	 # is there a preexisting delo.conf with a non-commented out line?
$configuring_base = 0;	# are we configuring the 'base' filesystem (special case)
$odd_fstab = 0;         # set if we don't understand the device in the fstab
# We also set $device, $disk, $partition (assuming fstab_broken == 0)
$syscons = ""; # system console
$ask_console = 0; # by default, don't ask for the system console

if ($#ARGV >= 0 && $ARGV[0] eq "-c") {
	if ($#ARGV > 0) {
		$syscons=$ARGV[1];
		print "XXX\n";
	} else {
		$syscons="/dev/ttyS0";
		$ask_console=1;
	}
}

if (-f $FSTAB) {
	# Parse fstab for the root partition...
	open(FSTAB, "<$FSTAB") or die "deloconfig: couldn't open $FSTAB: $!\n";
	while (<FSTAB>) {
		# Check for a magic string which indicates that we are configuring
		# the base filesystem and not a real machine...
		$configuring_base = 1 if /^# UNCONFIGURED FSTAB FOR BASE SYSTEM/;
		next if /^#/;	  # ignore comment lines
		($device,$filesystem) = split(/[ \t]+/);
		next unless defined $filesystem;   # ignore empty lines too
		# Stop if we found the root device...
		if ($filesystem eq '/') {
			$fstab_broken = 0;
			last;
		}
	}
	close(FSTAB) or die "deloconfig: couldn't close $FSTAB: $!\n";
}

if (!$fstab_broken) {
	# Valid device/filesystem pair, parse them
	$disk = &find_mbr ($device);
	chomp($disk);
	$partition = $device;
	my $devfs = 0;

	# Paranoia check: there should be valid /dev/ nodes for these.
	# We could check for block-special-device-ness, but perhaps
	# some people have symlink forests in /dev/ ?
	# This check will fail on things like RAID arrays, where the
	# devices don't have names like /dev/hda4. In this case we can't
	# do simple autoconfiguration, but we still want to be able
	# to allow the user to install their own handrolled delo.conf.
	$odd_fstab = 1 unless ($partition =~ /\d+$/ && -e $disk && -e $device);
}

# Check for an existing delo.conf with some non-comment lines in it...
system("grep -qsv '^#' $DELOCONF");
# Exit status is 0 iff delo.conf exists and contains at least one non-comment line.
if ($? == 0) {
	$deloconf_exists = 1;
}
       
##########################################################
# Boilerplate arrays used to produce an initial delo.conf
##########################################################

@header = (
	"# Generated by deloconfig\n",
	"\n"
	);

$boilerplate = "\nlabel=linux\n".
	"	image=/boot/vmlinuz\n".
	"	append=\"root=\@device\@ \@console\@\"".
	"\n";

####################
# Utility functions
####################

sub ruler {
	# Print a dividing line across the screen to help to
	# separate each question from the preceding text.
	print "\n   " . '=' x 74 . "\n";
}

sub asky {
	do {
		print @_,"? [Yes] ";
		$answer=<STDIN>;
	} while ($answer ne "\n" && !($answer =~ /^[YyNn].*/));
	&ruler();
	return ( !($answer =~ /^[nN].*/) );
}

sub askn {
	do {
		print @_,"? [No] ";
		$answer=<STDIN>;
	} while ($answer ne "\n" && !($answer =~ /^[YyNn].*/));
	&ruler();
	return ( $answer =~ /^[yY].*/ );
}

sub ask_text {
	my $q=shift;
	my $d=shift;

	print $q, " [$d] ";
	$answer=<STDIN>;
	&ruler();
	$answer=$d if ( $answer =~ /^[\r\n]*/ );
	return $answer;
}

sub safe_system {
	# Works like system(), but just echoes the command that would
	# be run if $DEBUG is 1.
	if ($DEBUG) {
		print "[Would have run: ", join(' ', @_), "]\n";
		$? = 0;
	} else {
		system(@_);
	}
}

sub find_mbr {
	my $root_part;
	if(defined($1))
	{
		$root_part = $1;
	}
	else
	{
		$root_part = `awk '{ if (\$2=="/") print \$1}' </etc/fstab`;
	}

	my $delo_root_part;
	foreach $item (split("\n", $root_part))
	{
		if(not $item =~ /#/)
		{
			$delo_root_part = $item;
		}
	}

	if($delo_root_part =~ /\/dev\/md/)
	{
		my $mdname = $delo_root_part;
		$mdname =~ s/\/dev\///;
		$mdname =~ s/\///;
		my $md = `grep $mdname /proc/mdstat`;
		my @devices = split(" ", $md);
		@devices = sort(@devices[4..$#devices]);
		$delo_root_part = "/dev/" . $devices[0];
		$delo_root_part =~ s/\[.*$//;
	}

	my $delo_root_disk = $delo_root_part;
	$delo_root_disk =~ s/\d+$//;
	$delo_root_disk =~ s/part$/disc/;

	return $delo_root_disk;
}

#####################################
# Actual work is done below here...
#####################################

# Debian's 'base' filesystem is a special case -- it's prebuilt
# by installing and configuring packages into a subdirectory,
# which is then made into a tarball, which is then used to
# make the initial filesystem for a fresh Debian install.
# Thus we can't actually run DELO now, because we know nothing
# of the disk layout. That will be done as part of the install
# process.
if ($configuring_base) {
	print <<EOT;

Hmm. I think you're configuring the base filesystem, and I'm therefore
simply going to exit successfully without trying to actually configure
DELO properly. If you're not doing that, this is an important bug
against Debian's delo package, and should be reported as such...
EOT
	exit(0);
}


if ($deloconf_exists) {
	# Trust and use the existing delo.conf.
	# FIX: If the current delo.conf installs a master boot record, ask
	#	to edit it to a partition boot record and install the master boot
	#	record to chain to that.
	print "\n";
	print "You already have a DELO configuration in the file $DELOCONF\n\n";

	if ( &asky("Install a boot block using your current DELO configuration") ) {
		print "Running delo...\n";
		&safe_system("/sbin/delo -d $device");
		if ( $? == 0 ) {
			exit(0);
		}
		print "\nERROR: correct $DELOCONF manually and rerun /sbin/delo\n\n";
		exit(1);
       } else {
		print "\n";
		if ( &askn("Wipe out your old DELO configuration and make a new one") ) {
			print "Saving configuration in $DELOCONF.OLD\n";
			rename($DELOCONF, "$DELOCONF.OLD") or die "deloconfig: couldn't save old $DELOCONF as $DELOCONF.OLD: $!\n";
		}
		else {
			print "No changes made.\n";
			exit(0);
		}
	}
}

# ASSERT: that we get here only if there is no delo.conf or the user
# asked us to wipe out the old one...
# We make checks for broken fstabs and odd devices only if we are 
# going to try to write a delo.conf for the user.
if ($fstab_broken) {
	print <<EOT;

WARNING!
Either your $FSTAB configuration file is missing, or it doesn't
contain a valid entry for the root filesystem! This generally
means that your system is very badly broken. Configuration of DELO
will be aborted; you should try to repair the situation and then
run /usr/sbin/deloconfig again to retry the configuration process.
EOT
	exit(1);
}

if ($odd_fstab) {
	print <<EOT;

WARNING!
Your $FSTAB configuration file gives device $device as the root
filesystem device. This doesn't look to me like an "ordinary" block
device. Either your fstab is broken and you should fix it, or you are
using hardware (such as a RAID array) which this simple configuration
program does not handle. 

You should either repair the situation or hand-roll your own
$DELOCONF configuration file; you can then run /usr/sbin/deloconfig
again to retry the configuration process.
Documentation for DELO can be found in /usr/share/doc/delo/.
EOT
	exit(1);
}

if ($ask_console) {
	$syscons=&ask_text("What device do you want to use as system console?", $syscons);
}

print "\n";
print "This will result in Linux being booted by default from the hard disk.\n";
print "If your setup is complicated or unusual you should consider writing your\n";
print "own customised $DELOCONF. To do this you should exit this configuration\n";
print "program and refer to the delo documentation, which can be found in\n";
print "/usr/share/doc/delo/.\n";
print "\n";

# Flag so we can print a warning if we fell out the bottom of the config
# without having run delo at all.
$delorun = 0;

$device=~s/\d+//g;

if ( &asky("Install a partition boot record to boot Linux from ", $device)) {
	print "Creating small delo.conf and running delo.\n";
	umask(077);
	open(CONF, ">$DELOCONF") or die "Couldn't open $DELOCONF for writing: $!\n";
	if (!chown(0, 0, "$DELOCONF")) {
		die "Couldn't make $DELOCONF owned by root.root: $!\n" unless $DEBUG;
		print "Oops, couldn't make $DELOCONF owned by root.root. Since you\n";
		print "have set the DEBUG flag, I'm going to assume this is because\n";
		print "you're running deloconfig as a normal user, and continue anyway.\n";
	}
	print CONF @header;
	my $bp=$boilerplate;
	$bp=~s/\@device\@/$device/g;
	if ($syscons) {
		$bp=~s/\@console\@/console=$syscons/g;
	} else {
		$bp=~s/\@console\@//g;
	}
	print CONF $bp;
	close(CONF) or die "Couldn't close $DELOCONF: $!\n";

	&safe_system("/sbin/delo $device");
	if ($? != 0) {
		print "\nERROR: correct /etc/delo.conf manually and rerun /sbin/delo\n\n";
		exit(1);
	}
	$delorun = 1;
}

# Trailer: summarise what we've done
print "\n";
if (! $delorun) {
	print "WARNING: you will have to set up DELO manually to ensure that\n";
	print "	 your system can be booted successfully!\n";
	print "You can rerun deloconfig at any time if you change your mind and\n";
	print "wish to use the default configuration.\n";
} else {
	# DELO was run
	print "DELO successfully configured; Linux will be booted by default.\n";
}
     
print "\n";
print "For more information about DELO, see the documentation in\n";
print "/usr/share/doc/delo/.\n";

exit(0);
