#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

#
# This code is issued under the GNU General Public License.
# See the file COPYING in this distribution
#
# Copyright (C) 1998, Stelias Computing
# 
# Modified for InterMezzo from Gordian's HSM bcache device/jcm module
# Copyright (C) 1999, Carnegie Mellon University
#
# Derived from InterMezzo's incontrol, modified for OBD's
# Copyright (C) 1999, Stelias Computing
#
#

# FIXME: Appending to configuration files is too naive, so it has been
# disabled.  We can fix this by using XML::Simple to load the file, then
# change it, and write it out.

use strict;
use XML::Simple;
use Getopt::Long;
use File::stat;
#use Carp;
use Term::ReadLine;
use IO::Handle;

# make sure stdout is not buffered
STDOUT->autoflush(1);

my ($configdir, $idfile);

$configdir = '/etc/intermezzo';

GetOptions("configdir=s" => \$configdir,
	   "idfile=s" => \$idfile,
	   ) || die "Getoptions";

my $DEFAULT_PORT = 369;
my $FSNAME = 'InterMezzo';
my $DEVNAME = 'intermezzo';


$idfile = $configdir . '/sysid' unless defined $idfile;

# get a console for the app
my $term;
$term = new Term::ReadLine 'inconfig';

######################################################################
# Helper routines.

# Return the lexical parent directory for a given name.
sub parent ($)
{
    my ($name) = @_;

    # Split the name into component directories.
    my (@tmp) = split (/\//, $name);

    # Drop the last element.
    pop (@tmp);
    if ($#tmp == -1)
    {
	# There are no elements left, so we're either at the root or cwd.
	return '/' if (substr ($name, 0, 1) eq '/');
	return '.';
    }

    # Only one element, but it's empty, so it must be the root.
    return '/' if ($#tmp == 0 && $tmp[0] eq '');
	
    # Just rejoin the components.
    return join ('/', @tmp);
}


# Make a directory and its parents.
sub mkdir_p ($$)
{
    my ($name, $mode) = @_;

    # Create directories only if they don't exist.
    my ($parent) = parent ($name);
    return (-d $parent || mkdir_p ($parent, $mode))
	&& (-d $name || mkdir ($name, $mode));
}


# Open HANDLE as given by SPEC, prompting the user if something could
# be clobbered.
sub safe_open (*$)
{
    my ($handle, $spec) = @_;

    my ($file) = $spec;
    $file =~ s/^(<|>|>>)\s*//;
    my ($mode) = $1 || '<';

    # We don't treat read-only specially.
    return open ($handle, $spec) if ($mode eq '<');

    no strict 'refs';
    my ($dir) = parent ($file);
    mkdir_p ($dir, 0777) || die "Cannot create \`$dir': $!\n";
    if (-f $file)
    {
	print "\n";
	my ($action);
	while (1) {
	    my ($astr, $achr);
	    if ($mode eq '>>')
	    {
		$astr = 'Append/';
		$achr = 'a|';
	    }
	    $action = $term->readline("\`$file' exists. ${astr}Overwrite/Skip (${achr}o|s): ");

	    if (($mode eq '>>' && $action eq "a") ||
		$action eq "o" || $action eq "s") {
		last;
	    }
	}
	if ($mode eq '>>' && $action eq "a") {
	    open $handle, ">>$file";
	} elsif ($action eq "o") {
	    open $handle, ">$file";
	} else {
	    return 0;
	}
    } else {
	open $handle, ">$file";
    }
    use strict 'refs';
    return 1;
}


# Return nonzero if a string is just whitespace or empty.
sub blank
{
    my ($str) = @_;
    return (! $str || $str =~ /^\s*$/);
}


######################################################################
# Main program

print "

Welcome to the InterMezzo Configuration Tool.

We need a unique name for your system. Internally
InterMezzo will refer to systems by this name.

It is recommended that the name resolve to an IP address. 

Unless you plan on running multiple lento's on a single system you do
not need to give a bind address.

";


if (safe_open (F, "> $idfile"))
{
    my $sysid = {};
    my $xml_sysid;

    $sysid->{name} = $term->readline("Enter the identifying name for this system: ");
    $sysid->{bindaddr} = $term->readline("Bind address [hit RET for INADDR_ANY]: ");
    $sysid->{psdev} = $term->readline("$FSNAME device [hit RET for /dev/${DEVNAME}0]: ");
    

    $sysid->{psdev} = "/dev/${DEVNAME}0" unless  $sysid->{psdev}; 

    $xml_sysid = XMLout($sysid, rootname => 'sysid');
    print F "$xml_sysid";
    close F;
}

print "

We need a server and fileset database.  

You can either copy these from an InterMezzo server or enter some data
here.  Press control-C if you want to copy the files by hand.

The server name should resolve to an IP address. 

";

if (safe_open (F, "> $configdir/serverdb")) # FIXME: allow appends?
{
    my $serverdb = {};
    my $server;
    my $name;

    $serverdb->{server} = {};
    $server = $serverdb->{server};

    while ( $name = $term->readline("Server name (RET when done): ") ) {
	last if (blank ($name));
	$server->{$name} = {};
	my $thisone = $server->{$name};
	$thisone->{ipaddr} = $term->readline("Hostname or IP [$name]: ");
	$thisone->{ipaddr} = $name unless  $thisone->{ipaddr};

	$thisone->{port} = $term->readline("Port number [$DEFAULT_PORT]: ");
	$thisone->{port} = $DEFAULT_PORT unless  $thisone->{port};
	$thisone->{bindaddr} = $term->readline("Bind address [hit RET for INADDR_ANY]: ");
    }

    print F XMLout($serverdb, rootname => "serverdb"); 
    close F;
}


print "\n\nNow the fileset database\n";


my (@fstab);
if (safe_open (F, "> $configdir/fsetdb")) # FIXME: Allow appends?
{
    my $fsetdb;
    my $servername ="";
    my $fsetname;
    my $replicator;

    $fsetdb->{fileset} = {};
    my $fileset = $fsetdb->{fileset};

    while ( $fsetname = $term->readline("Fileset name (RET when done): ")) {
	my @reparr;

	last if (blank ($fsetname));

	$fileset->{$fsetname} = {};
	my $thisfset = $fileset->{$fsetname};

	do
	{
	    $servername = $term->readline("Server name []: ");
	}
	while (blank ($servername));
	$thisfset->{servername} = $servername;

	my $val = -1;
	my $dfl = 0;
	while ( ($val != 0) &&  ($val != 1) ) {
	    $val = $term->readline("Read-only clients (0|1)? [$dfl]: ");
	    $val = $dfl if (blank ($val));
	} 
	$thisfset->{client_read_only} = ($val == -1) ? $dfl : $val;

	$val = -1;
	$dfl = 1;
	while ( ($val != 0) &&  ($val != 1) ) {
	    $val = $term->readline("Allow permit stealing? (0|1) [$dfl]: ");
	    $val = $dfl if (blank ($val));
	}
	$thisfset->{steal_permit} = $val;

	$val = "";
	$val = $term->readline("If fileset is not a cache root enter mtpt []: ");

	if (! blank ($val))
	{
	    $thisfset->{mount_point} = $val;
	}
	else
	{
	    my ($dev, $mtpt, $presto) = @_;
	    $dfl = '';
	    $val = $term->readline ("Cache device (or file if loop mount) [$dfl]: ");
	    $val = $dfl if (blank ($val));
	    $dev = $val;

	    $dfl = '';
	    $val = $term->readline ("InterMezzo cache mount point [$dfl]: ");
	    $val = $dfl if (blank ($val));
	    $mtpt = $val;

	    $dfl = "/dev/${DEVNAME}0";
	    $val = $term->readline ("InterMezzo device [$dfl]: ");
	    $val = $dfl if (blank ($val));
	    $presto = $val;

	    $val = -b $dev ? '' : 'loop,';
	    push (@fstab, "$dev $mtpt $FSNAME " . $val .
		  "fileset=$fsetname,mtpt=$mtpt,prestodev=$presto 0 0");

	    if (! -d $mtpt)
	      {
		mkdir_p ($mtpt, 0777) ||
		  warn "Cannot create mount point \`$mtpt': $!\n";
	      }
	  }


	while ( $replicator = $term->readline("Replicator (RET when done): ")) {
	    last if (blank ($replicator));
	    push @reparr, $replicator;
	}
	$thisfset->{replicator} = \@reparr;
	print "\n";
    }

    print F XMLout($fsetdb, rootname => "fsetdb");
    close F;
}


# Intelligently add the specified lines to the system fstab.
sub update_fstab
{
    my (@lines) = @_;
    return if ($#lines < 0);

    my $file = '/etc/fstab';
    print "\nUpdating $file....\n";
    open (FSTAB, "$file")
	|| die "Cannot read \`$file': $!\n";
    open (F, ">$file.new")
	|| die "Cannot create \`$file.new': $!\n";

    my (%devices, %mounts);

    while ($_ = <FSTAB>)
    {
	# Suck in the old lines.
	chomp;
	print F $_, "\n";

	# Save the important details.
	s/\#.*$//;
	my ($dev, $mnt) = split;
	$devices{$dev} = $_;
	$mounts{$mnt} = $_;
    }
    close FSTAB;

    my $manual = 0;
    for (@lines)
    {
	# Add our lines.
	my $line = $_;

	s/\#.*$//;
	my ($dev, $mnt, @rest) = split;
	my $conflict;

	if ($#rest >= 0)
	{
	    if (defined $devices{$dev} && $devices{$dev} ne $_)
	    {
		print "Device \`$dev' has conflicting entries!\n";
		$conflict = 1;
	    }
	    if (defined $mounts{$mnt} && $mounts{$mnt} ne $_)
	    {
		print "Mount point \`$mnt' has conflicting entries!\n";
		$conflict = 1;
	    }

	    if ($conflict)
	    {
		$line = '# CONFLICT: ' . $line;
		$manual = 1;
	    }
	}

	print F $line, "\n";
    }
    close F;

    # Replace the old file.
    if (! rename "$file", "$file.old")
    {
	my ($err) = $!;
	unlink "$file.new";
	die "Cannot rename \`$file' to \`$file.old': $!\n";
    }
    if (! rename "$file.new", "$file")
    {
	my ($err) = $!;
	unlink "$file.new";
	rename "$file.old", "$file";
	die "Cannot rename \`$file.new' to \`$file': $!\n";
    }
    unlink "$file.old";

    if ($manual)
    {
	printf "Manual editing of \`$file' is required before running InterMezzo.\n";
    }

    printf "Done updating \`$file'.\nThe InterMezzo HOWTO describes how \`mkizofs' formats a cache.\n";
}

    
&update_fstab (@fstab);
print "\nThank you for trying InterMezzo, and have a nice day!\n";
exit (0);
