#!/usr/bin/perl -w
#
# $Revision: #18 $
#
# upgrade - perform upgrade to latest firmware revision
#

use	Carp;
use	IO::Handle;
use	File::Basename;

require "getopts.pl";
require "stat.pl";

$pres_eq_conf	= 1;
$pres_en_conf	= 1;
$pres_sw_conf	= 1;
$no_preserve	= 0;

$set_use_ssl	= 0;

$logfile	= "./upgrade.log";
$dbgfile	= "./upgrade.dbg";
$inputfile	= "/var/tmp/eq_upgrade.in";
$outputfile	= "/var/log/eq_upgrade.log";

$vers		= "8.6.0";		# product version #

$oslevel	= 0;
$disk           = "";
$slice_old      = 1;
$slice_new      = 2;
$slice_xtra     = 0;			# extra slice always allocated

#
# We will now try to make this the same as the layout used in the
# PXE installs:
#	All sizes are expressed in 512 byte blocks!
#
#	{DISK}s1-1=ufs     503808 /		0
#
#
$root_size	=  503808;	# 512 byte sectors
$swap_size	=   10240;	# vestigeal
$usr_size	=       0;	# 512 byte sectors
$tmp_size	=       0;	# 512 byte sectors
$var_size	=       0;	# 512 byte sectors (at least)
$size           = $root_size + $swap_size + $var_size + $tmp_size + $usr_size;
$opt_R		= 0;		# reset after botched attempt
$opt_r          = 0;            # Auto reboot if set (don't ask).
$opt_b		= 0;		# batch mode
$opt_k          = 0;            # load test4QA key
$opt_e		= 0;		# preserve /eqsupport/.ssh/
$opt_l		= 0;		# preserve license files only with -C option. 
$opt_S          = 0;		# install SNMP?
$opt_G          = 0;		# GUI mode

local *DBG;

@mp = ("/");				# mount points

%slice_map = ();

@mp_x = ();				# extra mount points

%slice_map_x = ();

%bits_map = (
	     '/'	=> "./root.tgz",
	     );
%fdisk = ();
%label = ();

#
# Preserving old version information 
#

sub previous {
	system "rm -rf /.master/var/eq/eq.previous";
	system "cp /.master/var/eq/eq.conf  /.master/var/eq/eq.previous";
	system "rm -rf /version/eq.previous";
	system "cp /.version/eq  /.version/eq.previous";
}

%xlate = (
	  '/etc/hosts'		=> '/var/etc/hosts',
	  '/etc/group'		=> '/var/etc/group',
	  '/etc/resolv.conf'	=> '/var/etc/resolv.conf',
	  '/etc/ntp.conf'	=> '/var/etc/ntp.conf',
	  '/etc/syslog.conf'    => '/var/etc/syslog.conf',
	  '/etc/ntp.drift'      => '/var/etc/ntp.drift',
	  '/etc/localtime'	=> '/var/etc/localtime',
	  '/etc/ipfw.conf'	=> '/var/eq/ipfw.conf',
	  '/etc/eq.conf'	=> '/.master/var/eq/eq.conf',
	  '/etc/tac_client.conf'=> '/.master/var/eq/tac_client.conf',
	  '/etc/eq.previous'	=> '/.master/var/eq/eq.previous',
	  '/etc/switch.conf'	=> '/var/eq/switch.conf',
	  '/etc/envoy.conf'	=> '/var/eq/envoy.conf',
	  '/etc/natd.conf'      => '/var/eq/natd.conf',
	  '/eq.previous'	=> '/.version/eq.previous',
	  '/usr/local/www/cgi-bin/users'
	  => '/var/eq/users',
	  );

#
# Determine whether to get our answer from STDIN, or from our
# file

sub get_answer {
    my $ans = "x";

    if ($opt_G) {
        while($ans eq "x") {
            open (EQ_IN_FILE, $inputfile);
            chop($ans = <EQ_IN_FILE>);
            close EQ_IN_FILE;
            sleep 1;
        }
        open (EQ_IN_FILE, ">" . $inputfile);
        print EQ_IN_FILE "x\n";
        close EQ_IN_FILE;
    }
    else {
        chop($ans = <STDIN>);
    }
    return($ans);
}

sub curr_vers {
    my $vers; 

    if (-f "/.version/eq") {
        open IN, "< /.version/eq" or die;
        while (<IN>) {
	    chop;

	    if (/^EQVERS=(\d.*)$/) {
		$vers = $1;
	    }
	}
    } else {
        $vers = "0";
    }

    return $vers;
}


#
# dummy - ensure we don't get warning messages
#

sub dummy {
    print $disk . $slice_old . $slice_new;
    print $opt_R;
    print $size;
    print $ST_MODE + $ST_GID + $ST_UID;
}

#
# usage - print usage info
#

sub usage {
    print "usage: upgrade [-R]\n";

    exit 1;
}

#
# dbg - print an informational message
#

sub dbg {
    my $arg = shift;

    print DBG "$arg\n";
}

#
# dbg_save - save the debug file
#

sub dbg_save {
    my $now = time();

    &sis("gzip --best < $dbgfile > /.version/upgrade.dbg.$now.gz");
}

#
# note - print an informational message
#

sub note {
    my $arg = shift;

    &dbg("$arg");

    print "$arg\n";
}

#
# cark - cark it
#

sub cark {
    my $arg = shift;

    &dbg("cark: $arg");

    &dbg_save;
    
    &note("**** Fatal error.");
    &note("**** Please contact Coyote Point customer support.");
    &note("**** Please provide the contents of $dbgfile.");

    if ($opt_r) {
	print "NOTE: This system is currently in an intermediate state.";
	print "It must be rebooted to return it to a stable state.";
	print "Because the upgrade did *NOT* succeed, the current version";
	print "will start when the system is rebooted.";
    }
    die "$arg";
}

#
# dbg_hash - print an informational message
#

sub dbg_hash {
    my $ref = shift;

    return unless defined  %$ref;

    foreach $k (sort keys %$ref) {
	print DBG "$k $ref->{$k}\n";
    }
}

#
# dbg_array - print an informational message
#

sub dbg_array {
    my $ref = shift;
    my $siz = @$ref;

    for ($i = 0; $i < $siz; ++$i) {
	print DBG "$ref->[$i]\n";
    }
}

#
# sis - perform `system' function and croak if non-zero result.
#

sub sis {
    my $arg = shift or &cark("");

    &dbg("sis: $arg");

    #
    # This is perl-ass-backwards, if you can imagine what that is.
    #
    system $arg and sleep 1 and &cark("\n");
}

#
# vsis - verbosely perform system function (sort of)
#

sub vsis {
    local *FH;
    local *TTY;
    my $success = 0;
    my $last = '';

    my $arg = shift or &cark("");
    my @rot = ('/', '-', '\\', '|');
    my @rat = ('o', '.', 'o', '0');
    my @spc = ('    ', '-   ', ' -  ', '  - ', '   -');
    my $i = 0;
    my $j = 0;
    my $k = 0;
    my $s = 0;

    return &sis($arg) unless !$opt_b;

    if (!$opt_G) {
        open TTY, ">/dev/tty" or die;
        select((select(TTY), $| = 1)[0]);
    }	
    open FH, "sh -c '". $arg ."' && echo '>>>success<<<' 2>&1 |" or die;
    select((select(FH), $| = 1)[0]);


    my $rin = '';
    my $ein = '';
    my $rout;
    my $eout;

    vec($rin, fileno(FH), 1) = 1;

    $ein = $rin;

    my $eof  = 0;
    my $off  = 0;
    my $toss = '';

    while (!$eof && !$success) {
        my ($n, $t) = select($rout=$rin, undef, $eout=$ein, 0.05);

        $eof = vec($eout, fileno(FH), 1);

        if (!$eof && vec($rout, fileno(FH), 1)) {
            if (length($toss) > 128) {
                $off  = 128;
                $toss = substr($toss, -$off);
            }

            my $cc = sysread(FH, $toss, 1024, $off);

            if ($cc) {
                $last = $toss;
            } else {
                $eof = 1;
            }
        }

        if (!$opt_G) {
	    print TTY "\rW$rat[$i]rking$spc[$j]$rot[$k]";
        }	

	if ($s == 0) {
	    ($i = 0, $s = 1) unless ++$i <= $#rat;
	}
	if ($s == 1) {
	    ($j = 0, $s = 2) unless ++$j <= $#spc;
	}
	if ($s == 2) {
	    ($k = 0) unless ++$k <= $#rot;
	    $s = 0;
	}

	if ($toss =~ />>>success<<</) {
	    $success = 1;
	    print "\rsuccess!                                         \n";
	}
    }

    print "\r                         \r$last" unless $success;

    close FH;
    if (!$opt_G) {
        close TTY;
        select(STDOUT); $| = 1;
    }	

    print "\n";
}

sub do_mounts {

    foreach $mp (@mp) {
	sis "mount $slice_map{$mp} /mnt/$mp";
    }
    foreach $mp (@mp_x) {
	sis "mount $slice_map_x{$mp} /mnt/$mp";
    }
}

sub undo_mounts {

    foreach $mp (reverse @mp_x) {
	my $mnt = "$slice_map_x{$mp}";

	foreach (`mount`) {
	    if (/^$mnt/) {
		sis "umount $mnt";
	    }
	}
    }

    foreach $mp (reverse @mp) {
	my $mnt = "$slice_map{$mp}";

	foreach (`mount`) {
	    if (/^$mnt/) {
		sis "umount $mnt";
	    }
	}
    }
}

#
# root_disk - determine the disk to root with
#

sub root_disk {

    foreach (`df /`) {
	if (m:^/dev/(\D+\d)s(\d)[a-f]\s:) {
	    &dbg("root disk: $1:$2");
	    $disk  = $1;
	    $slice_old = $2;

	    return;
	}
    }

    &cark("Cannot determine disk type.\n");
}

#
# sync_disk - sync the in-core view of the slices with the on-disk.
#

sub sync_disk {
    local *FH;

    open FH, "./disk $disk 2>&1 |" or die;

    while (<FH>) {
	chop;
	&dbg("pre: $_");
    }

    close FH;

    open FH, "./disk -S $disk 2>&1 |" or die;

    while (<FH>) {
	chop;
	&dbg("post: $_");

	&cark("$_\n") if (/ioctl/);
    }

    close FH;
}

#
# fdisk_reset - reset the %fdisk cache
#

sub fdisk_reset {
    local $d = shift || die;
    local *FH;

    &dbg("fdisk_reset $d");

    %fdisk = ();

    open FH, "fdisk -s $d |";

    $_ = <FH>;
    chop;

    my @zot = split;

    @fdisk{'cyl', 'hd', 'sec'} = @zot[1, 3, 5];

    $_ = <FH>;

    while (<FH>) {
	chop;

	@zot = split;

	&cark("invalid input: $_") unless m/^\s+(\d+):/;

	$fdisk{$1} = ();
	@{$fdisk{$1}}{'start', 'size', 'type', 'flags'} = @zot[1..4];
    }

    close FH;

    #
    # debug
    #

    foreach $p (1..4) {
        next unless defined $fdisk{$p};

        foreach $k (keys %{$fdisk{$p}}) {
            &dbg("$p: $k $fdisk{$p}->{$k}");
        }
    }
}

#
# disklabel_reset - reset the %label cache
#

sub disklabel_reset {
    local $d = shift || die;
    local $s = shift || die
	local *FH;

    &dbg("disklabel_reset $d $s");

    %label = ();

    open FH, "disklabel ${d}s${s} |";

    while (<FH>) {
	@zot = split;

	if ($#zot >= 4 && $zot[0] =~ /^([abcdefgh]):/) {
	    $label{$1}->{'size'}   = $zot[1];
	    $label{$1}->{'fstype'} = $zot[3];
	}
    }

    close FH;
}

#
# slice_4_inuse - predicate
#

sub slice_4_inuse {
    local *FH;

    open FH, "swapinfo|";

    while (<FH>) {
	if (m:^/dev/r${disk}s4:) {
	    close FH;
	    return 1;
	}
    }

    close FH;

    open FH, "mount|";

    while (<FH>) {
	if (m:^/dev/${disk}s4:) {
	    close FH;
	    return 1;
	}
    }

    close FH;

    open FH, "</etc/rc.conf";

    while (<FH>) {
	if (m:^dumpdev=.*/dev/${disk}s4:) {
	    close FH;
	    return 1;
	}
    }

    return 0;
}

#
# reclaim_space - reclaim space on $disk
#

sub reclaim_space {
    local $d = shift;
    local $p = shift;
    local *FH;

    &dbg("reclaim($d, $p)");

    if ($p == 4 && &slice_4_inuse()) {
	&cark("**** Cannot delete slice 4.\n");
    }

    open FH, "| fdisk -f - $d" or die;

    print FH "p $p 0 0 0\n";

    close FH;

    &sync_disk($d);
    &fdisk_reset($d);
}

#
# current_slice - return current slice used
#

sub current_slice {

    #
    # XXX: this is bogus as the 0x80 has been seen to be set
    # XXX: on more than one partition at a time.
    #

    foreach (1..3) {
	next unless defined $fdisk{$_};
	next unless oct($fdisk{$_}->{'type'}) == 165;
	next unless oct($fdisk{$_}->{'flags'}) & 0x80;

	&dbg("current slice = $_");

	return $_;
    }

    &cark("no current slice\n");
}

#
# free_space - return the amount of free space for a given FDISK slice
#

sub free_space {
    my $p = shift || die;
    my $start = 0;
    my $end   = $fdisk{'cyl'} * $fdisk{'hd'} * $fdisk{'sec'};

    &dbg("free_space $p - start=$start end=$end");

    return 0 unless !defined($fdisk{$p});

    foreach $i (1..4) {
	next unless defined $fdisk{$i};

	if ($i < $p) {
	    #
	    # allocated partition prior to the one of interest.
	    #

	    $start = $fdisk{$i}->{'start'} + $fdisk{$i}->{'size'};

	    &dbg("free_space $i start=$start");
	} elsif ($p < $i) {
	    #
	    # allocated partition past the one of interest.
	    #

	    $start = $fdisk{$i}->{'start'} - $start;

	    &dbg("free_space $i start=$start");

	    return $start;
	} else {
	    #
	    # allocated partition is the one of interest.
	    #

	    &cark("not reached\n");
	}
    }

    my $space = $end - $start;
    &dbg("free_space $space");

    return $space;
}

#
# next_slice - return a slice to use for the next install
#

sub next_slice {
    my $need = shift || die;
    my $blk = 0;

    foreach $i (1..3) {
	next unless $slice_old != $i;

	&dbg("next_slice $need = $i") unless free_space($i) < $need;

	return $i unless free_space($i) < $need;
    }

    &dbg("no next slice");

    return 0;
}

#
# lm_check - check with license manager
#

sub lm_check {
    my $once = shift;
    my $prod = "";
    local *LM;
    my %opts = ();
    my %lic  = ();

    open LM, "./lm -? 2>&1 |" or &cark("$!\n");

    while (<LM>) {
	foreach $c (['V', 'I', 'S', 'p', 'f', 'r', 'v']) {
	    $opts{$c} = $c if /^\s+-$c/;
	}
    }

    close LM;

    open LM, "./lm -p 2>&1 |" or &cark("$!\n");

    while (<LM>) {
	if (/^(e[23456789]50)/) {
	    $prod = $1;
	}
    }

    close LM;
    if ($prod) {
	open LM, "./lm -Z 2>&1 |" or &cark("$!\n");

	#
	# If we are licensed, we abuse the fact that the last license
	# spat out by the debug (-Z) reflects the licensed capability.
	#
	# In the future, we will use the new '-E' option which spits out perl
	#

	my $state = 0;

	while (<LM>) {
	    if ($state == 0 && /^license {/) {
		$state = 1;
	    }
		if ($state == 1 && /^}$/) {
		$state = 2;
	    }
	    if ($state == 2 && /^license {/) {
		$state = 1;
		%lic = ();
	    }
		if ($state == 1) {
		    next unless /\s+(\S+)\s+=\s+(.*)$/;
		    $lic{$1} = $2;
		    $lic{$1} =~ s/["';'"]//g;
		}
	    }

	    close LM;

	    &cark("") unless $state == 2;

	    #
	    # Ensure version locked.  If not, that is the same as failure.
	    #

	    if (!defined $lic{'eq_vers'}) {
		note "**** Cannot upgrade with non-version locked license.";

		if (!$once) {
		    note "**** Attempting to get new license.";
		    sis "./lm -f -r -U";
		    &lm_check(1);
		    return;
		} else {
		    &cark("**** Licensing problems.\n");
		}
	    }


	    # The license is valid if:
	    # either the major number is equal and the
	    # minor number of the "allowed" is greater than
	    # or equal to the "actual"     
	    #                 or
	    # the major number of the "allowed" is greater
	    # than the major number of the actual
	    

	    my ($maj1, $min1, $junk1) = split(/\./,$lic{'eq_vers'},3);
	    my ($maj2, $min2, $junk2) = split(/\./,$vers,3);
	    
	    if (($maj1 eq $maj2 && $min1 ge $min2) ||
		($maj1 gt $maj2)) {
		return;
	    }

	    note "**** Need new license for version $vers.";
	} else {
	    note "**** Need license for version $vers.";
	}

	if (-f "/var/eq/licenses/recent") {
	    sis "cd /var/eq/licenses"
		. " && mv recent recent.$lic{'eq_vers'}";
	}

	open LM, "./lm -f -r -U 2>&1 |" or die;

	while (<LM>) {
	    #
	    # expecting nothing output, so anything must be error message.
	    #

	    chop;
	    &cark("$_\n");
	}

	close LM;

	open LM, "./lm -p 2>&1 |" or die;

	while (<LM>) {
	    &cark("Not licensed.\n") unless /^e[23456789]50/;
	}

	close LM;
    }

#
# zoneupdate - update zoneinfo
#

    sub zoneupdate {
	my $vers;
	my %md5hash;
	my $md5;
	my $file;
	my $localmd5;
	local *IN;

	if (-f "/.version/eq") {
	    open IN, "< /.version/eq" or die;
	    while (<IN>) {
		chop;

		if (/^EQVERS=(\d.*)$/) {
		    $vers = $1;
		}
	    }
	} else {
	    $vers = "0";
	}

	close IN;

	open IN, "< ./zonehash";
	while (<IN>) {
	    chop;

	    /^(.*?) (.*?)$/;
	    $md5hash{$2} = $1;
	}

	close IN;

	$localmd5 = 0;

	if ($vers lt "7.2.3g") {
	    $localmd5 = `md5 -q /.master/var/etc/localtime`;
	    chop($localmd5);

	    if ($md5hash{$localmd5}) {
		print "Your localtime has been changed. If your timezone is ";
		print "within the United States,\nyou will notice that your ";
		print "system is updated for the new Daylight Savings time\n";
		print "rules.\n";
		$file = $md5hash{$localmd5};

		system("cp /mnt/$file /mnt/.master/var/etc/localtime");
	    }
	}
    }

    sub verscheck {
	my $vers;
	my $live;

	$live = 0;

	if (-f "/.version/eq") {
	    open IN, "< /.version/eq" or die;
	    while (<IN>) {
		chop;

		if (/^EQVERS=(\d.*)$/) {
		    $vers = $1;
		}
	    }
	} else {
	    $vers = "0";
	}

	if ($vers lt "8.5.1") {
		die("You must be running Version 8.5.1 to upgrade to Version 8.6.\n".
			"Please upgrade your system to the latest 8.5.1 image and try again.");
	}

	close IN;

	open IN, "./lm -h -U 2>&1 |" or &cark("$!\n");

	while (<IN>) {
	    if (/^(e[2346]50)gx-?|(e[23456]50)si-?|(ex1[78]50)-?|(vmware)-?/) {
		$live = 1;
	    }	
	}
	&cark("This upgrade is not valid on your hardware platform.  Please contact Coyote Point Support.") unless $live == 1;
    }

#
# setup - perform necessary setup.
#

    sub setup {

	#
	# cleanup any old unsaved kernel dump info
	#
	system "/sbin/savecore -c >/dev/null 2>&1";

	open DBG, ">> $dbgfile";

	&root_disk;
	&fdisk_reset($disk);

	my $tot = $fdisk{'cyl'} * $fdisk{'hd'} * $fdisk{'sec'};

	note "root disk: $disk, root slice $slice_old, capacity $tot sectors";

	$slice_xtra = 4 unless $tot < 15625000;	# 8Gig in sectors.

	#
	# Determine if we are upgrading from 2.2.8 or newer.
	#

	chop($oslevel = `uname -r`);
	chop($opsys = `uname -s`);

	if ($oslevel =~ /^[23][.]/) {
	    #
	    # Ancient.
	    #

	    &cark("Cannot upgrade from 2.2.8 to 7.1.0");
	} elsif ($oslevel =~ /^4[.][789]/) {
	    #
	    # Slightly newer.
	    #
	    
	    $oslevel   = 4;

	    &kluges;
	} elsif ($oslevel =~ /^4[.]1[01]/) {
	    $oslevel   = 4;
	} elsif ($opsys =~ /^EQ\/OS/) {
	    $oslevel   = 4;
	} else {
	    &cark("Unsupported OS level: $oslevel\n");
	}

	chdir "/usr/l7up" or &cark("$!\n");

	if ($opt_G) {
            open(STDOUT, '>>'.$outputfile ) or die $!;
            open(STDERR, '>>'.$outputfile ) or die $!;
	}    
        else {	
  	    open(STDOUT, "| tee -a $logfile") or &cark("$!\n");  
	    open(STDERR, ">&STDOUT") or &cark("$!\n");
        }
        select(STDOUT); $| = 1;
        select(STDERR); $| = 1;

	#
	# show current level (debug assist)
	#

	open IN, "./lm -v |" or die;
	map { &note("lm: $_") } <IN>;
	close IN;

	#
	# show current mounts in debug log
	#

	open IN, "/sbin/mount |" or die;
	map { &dbg($_) } <IN>;
	close IN;

	open IN, "/sbin/fdisk -s $disk |" or die;
	map { &dbg($_) } <IN>;
	close IN;

	#
	# Give up if there is more than one active partition.
	#

	my $count = 0;

	foreach $s (1..4) {
	    next unless defined($fdisk{$s});
	    next unless (oct($fdisk{$s}->{'flags'}) & 0x80);
	    $count++;
	}

	&cark("No active partition.\n") unless $count > 0;
#    &cark("Too many active partitions: $count.\n") unless $count == 1;
    }

#
# lsmash - smash out sym links
#
    sub lsmash {
	local $f = shift;
	local $m = "";

	foreach $c (split('/', $f)) {
	    $m .= "/$c";
	    if (-l "/mnt/$m") {
		$m = readlink "/mnt/$m";
	    }
	}

	&dbg("lsmash: $f -> $m");

	return $m;
    }

#
# unshad - unshadow
#

    sub __unshad {
	local $f = shift;
	local $m = "/.master$f";

	return $m unless !-f "/mnt$m";
	return $m unless !-d dirname("/mnt$m");
	return $f
	}

    sub unshad {
	local $f = shift;
        local $s = &__unshad($f);

	&dbg("unshad: $f -> $s");
	
	return $s;
    }

    sub xlate {
	local $f = shift;

	&dbg("xlate: $f -> $xlate{$f}") unless !defined($xlate{$f});
	
	return $xlate{$f} unless !defined($xlate{$f});

	return $f;
    }

#
# preserve - preserve old version of file
#

    sub preserve {
	local $f = shift;
	local $m = "/mnt" . &unshad(&lsmash(&xlate($f)));
	local @inode;

	$f = readlink $f unless ! -l $f;
	$f = &xlate($f)  unless   -f $f;
	
	note "  ** preserve $f";

	if (-f "$f") {
	    if (-f "$m") {
		@inode = stat("$m");

		rename "$m", "${m}.dist";
	    } else {
		@inode = stat("$f");
	    }

	    if ($f ne $m) {
	        sis "cp $f $m";
	    }
	    
	    chown $inode[$ST_UID], $inode[$ST_GID], "$m";
	    chmod $inode[$ST_MODE], "$m";
	}
    }

#
# d_preserve - preserve old contents of directory (not recursive)
#

    sub d_preserve {
	local $d = shift;
	local $f;
	local @inode;


	if (-d "$d") {
	    local *DP;

	    sis "mkdir -p /mnt/$d";

	    note "  ** preserve $d/";

	    opendir(DP, $d) || &cark("$!\n");

	    foreach $f (grep { -f "$d/$_" } readdir(DP)) {
		preserve "$d/$f";
	    }

	    closedir DP;
	}
    }

    sub new_fstab {
	local *OUT;
	my $slice = $slice_map{'/'};

	note "**** new fstab";

	unlink "/mnt/etc/fstab";

	open OUT, ">/mnt/etc/fstab" or &cark("$!\n");

	if ($slice_xtra) {
	    local $xx = "b	none	swap	sw	0 0";

	    print OUT "/dev/${disk}s$slice_xtra" . "${xx}\n";
	    &dbg(     "/dev/${disk}s$slice_xtra" . "${xx}");

	    &disklabel_reset($disk, $slice_xtra);
	}
	if ($slice_xtra && $label{'a'}->{'size'} > 4000000
	    && $label{'a'}->{'fstype'} =~ "4.2BSD") {
	    #
	    # If there is an 'a' partition, we will attempt to use this as
	    # /var/crash
	    #
	    local $xx = "a	/var/crash	ufs	rw	2 2";

	    print OUT "/dev/${disk}s$slice_xtra" . "${xx}\n";
	    &dbg(     "/dev/${disk}s$slice_xtra" . "${xx}");
	}
	if ($slice_xtra && $label{'b'}->{'size'} > 4000000
	    && $label{'b'}->{'fstype'} =~ "swap") {
	    #
	    # If there is a 'b' partition, we will attempt to use this as
	    # dump device
	    #

	    sis 'echo dumpdev="/dev/${disk}s${slice_xtra}b"'
		. '>> /mnt/.master/var/etc/rc.conf';
	}

	print OUT "$slice	/	ufs	ro	1 1\n";
	&dbg(     "$slice	/	ufs	ro	1 1");

	close OUT;
    }

    sub check_gx {
	my $rc;
	open IN, "./lm -h -U 2>&1 |" or &cark("$!\n");

	while (<IN>) {
	    if (/^(e[346]50)gx-?/) {
	        $rc = 1;
	    }	
	    else {
	        $rc = 0;
	    }
	}
	close IN;
	return $rc;
    }

    sub munge_rc_conf {
	local *IN;
	local *OT;

	my %conf = ();
	my @lval = ();

	#
	# Determine {DISK} and {NET0} and {NET1} from existing rc.conf
	#
	my $net0 = "fxp0";
	my $net1 = "fxp1";
	my $vlan1 = "";
	my $agr0 = "";
	my $agrport0 = "";
	my $agrport1 = "";
	my $nix  = 0;
	my $dump = '';
	my $vlanif1 = 0;

	#
	# As part of NTP modification we will need two more extra
	# variable which are not part of earlier rc.conf into new
	#
	my $ntpd = "xntpd_enable";
	my $ntpf = "xntpd_flags";
	my $routes = "static_routes";

	open IN, "< /etc/rc.conf" or &cark("$!\n");

	while (<IN>) {
	    if (/^EQ_IF_0=(.*)$/) {

		$net0 = $1;
		$net0 =~ s/^(["''"])(.*)\1/$2/;

		&dbg("net0 = $net0");
		
		$nix |= 1;
	    } elsif (/^EQ_IF_1=(.*)$/) {

		$net1 = $1;
		$net1 =~ s/^(["''"])(.*)\1/$2/;

		&dbg("net1 = $net1");
		
		$nix |= 2;
	    } elsif (/^EQ_VLIF_0=(.*)$/) {

		$vlan1 = $1;
		$vlan1 =~ s/^(["''"])(.*)\1/$2/;

		&dbg("vlan1 = $vlan1");
		
		$nix |= 2;
	    } elsif (/^EQ_AGRIF_0=(.*)$/) {

		$agr0 = $1;
		$agr0 =~ s/^(["''"])(.*)\1/$2/;

		&dbg("agr0 = $agr0");
		
		$nix |= 2;
	    } elsif (/^EQ_AGRPORT_0=(.*)$/) {

		$agrport0 = $1;
		$agrport0 =~ s/^(["''"])(.*)\1/$2/;

		&dbg("agrport0 = $agrport0");
		
		$nix |= 2;
	    } elsif (/^EQ_AGRPORT_1=(.*)$/) {

		$agrport1 = $1;
		$agrport1 =~ s/^(["''"])(.*)\1/$2/;

		&dbg("agrport1 = $agrport1");
		
		$nix |= 2;
	    } elsif (/-r ([^ ]*) .* [.] \1/) {
		&dbg("source $1");

		close IN;
		open IN, "<$1";
	    }
	}

	close IN;

	my $rc = check_gx();
	if (($rc == 1) && ($agr0 eq '')) {
	    $vlan1 = "vlan1";
	    $agr0 = "agr0";
	    $agrport0 = "em0";
	    $agrport1 = "em1";
	}

	if ($nix != 3) {
	    #
	    # not the newer format.  Look for old style.
	    #

	    $nix = 0;

	    open IN, "</etc/rc.conf" or &cark("$!\n");

	    while (<IN>) {
		if (/^ifconfig_lo0=/) {
		    ;
		} elsif (/^ifconfig_([^_=0-9]*0)=(.*)$/) {

		    $net0 = $1;

		    &dbg("net0 = $net0");
		    
		    $nix |= 1;
		} elsif (/^ifconfig_([^_=0-9]*1)=(.*)$/) {

		    $net1 = $1;

		    &dbg("net1 = $net1");
		    
		    $nix |= 2;
		} elsif (/^dumpdev=(.*)$/) {
		    if ($1 eq '"/dev/da0s4b"') {
			$dump = $1;
		    }
		}
	    }

	    close IN;
	}

	&cark("Cannot determine {NET0} and {NET1}\n") unless $nix == 3;

	#
	# Expand the template out
	#

	open IN, "< ./etc.rc.conf.tmpl" || &cark("$!");
	open OT, "> /mnt/etc/rc.conf"   || &cark("$!");

	while (<IN>) {
	    next if /^{DUMPDEV}/;
	    if ($dump eq '' && (/^dumpdev/ || /^savecore_flags/)) {
		next;
	    }

	    s/{DISK}/$disk/g;
	    s/{NET0}/$net0/g;
	    s/{NET1}/$net1/g;
	    s/{VLAN1}/$vlan1/g;
	    s/{AGR0}/$agr0/g;
	    s/{AGRPORT0}/$agrport0/g;
	    s/{AGRPORT1}/$agrport1/g;

	    if (/^(\S+)=(.*)/) {
		push @lval, $1;
	    }

	    print OT;
	}

	close OT;
	close IN;
        
	#
	# Everything below this point preserves existing pieces of the
	# configuration, which we don't want sometimes.
	#

	if ($no_preserve == 0) {

	    #
	    # Push NTPD variable into lval
	    #
	    push @lval, $ntpd;
	    push @lval, $ntpf;
	    push @lval, $routes;

	    #
	    # Now that that has been accomplished, we need to determine what
	    # modifications to the old need to be migrated to the new.
	    #

	    open IN, "< /etc/rc.conf" or &cark("$!\n");

	    #
	    # Look for entries we wish to preserve and add them to %conf
	    #
	    
	    while (<IN>) {
		if (/-r ([^ ]*) .* [.] \1/) {
		    &dbg("source $1");

		    close IN;
		    open IN, "< $1";
		} elsif (/^(\S+)=(.*)/) {
		    if (grep(/$1/, @lval)) {
			next if $1 =~ "network_interfaces";
			#
			# It is in the new conf (@lval) so we
			# snarf a copy.
			#
			$conf{$1} = $2;

			&dbg("conf[$1] = $2");
		    } elsif (/(ifconfig_[^=]+)=(.*)/) {
			next if $1 =~ "ifconfig_lo0";

			#
			# Try to preserve non-conflicting ifconfigs.
			# These might be aliases.
			#

			$conf{$1} = $2;

			&dbg("conf[$1] = $conf{$1}");
 	        	if ($1 eq "ifconfig_vlan1") {
		    	    $vlanif1 = 1;
			}
		    } elsif (/(route_[^=]+)=(.*)/) {
			#
			# Preserve static routes list
			#
			$conf{$1} = $2;
		    } else {
			&dbg("old conf: $_");
		    }
		}
	    }

	    close IN;
	    if ($vlanif1 == 0) {
		$ifconfig1 = "ifconfig_" . $net0;
		$ifconfig2 = "ifconfig_" . $vlan1;
		&dbg("Adding $ifconfig2");
 	        $conf{$ifconfig2} = $conf{$ifconfig1};
	    }

	    &dbg("lval");
	    &dbg_array(\@lval);
	    &dbg("conf");
	    &dbg_hash(\%conf);
	    
	    #
	    # Add to %fnoc some potentially new entries
	    #

	    my %fnoc = ();

	    if ($slice_xtra) {
		$fnoc{'dumpdev'} = '"/dev/' . "${disk}s${slice_xtra}" . 'e"';
		sis "cd /mnt/.master/dev && ./MAKEDEV ${disk}s${slice_xtra}e";
	    }

	    #
	    # Run thru the new rc.conf, preserving those entries we wish to preserve.
	    #

	    my $new_conf = "/mnt/.master/var/etc/rc.conf-eq";

	    open IN, "< /mnt/etc/rc.conf" or &cark("$!\n");
	    open OT, "> ${new_conf}.new" or &cark("$!\n");

	    my $discard = 1;
	    my %seen = ();

	  line:
	    while (<IN>) {
		my $hash = '#';

		next line if ($discard and /^$hash/);

		&dbg("_ = $_");

		if (/-r ([^ ]*) .* [.] \1/) {
		    &dbg("source $1");

		    close IN;
		    open IN, "< /mnt/.master/$1" or &cark("$!\n");

		    $discard = 0;

		    next line;
		}

		foreach $k (keys %conf) {
		    if (/^($k)=(.*)/) {
			&dbg("conf: $k '$2' :: '$conf{$k}'");

			if ($2 ne $conf{$k}) {
			    note "  ** updating rc.conf entry:\n\t$k=$conf{$k}";
			    print OT "$k=$conf{$k}\n";
			}
			delete $conf{$k};

			next line;
		    }
		}

		foreach $k (keys %fnoc) {
		    if (/^($k)=(.*)/) {
			if ($2 ne $fnoc{$k}) {
			    note "  ** updating rc.conf entry:\n\t$k=$fnoc{$k}";
			    print OT "$k=$fnoc{$k}\n";
			}

			delete $fnoc{$k};

			next line;
		    }
		}

		#
		# Not overridden.
		#
		print OT $_;
	    }
	    
	    &dbg("conf remains");
	    &dbg_hash(\%conf);
	    &dbg("fnoc remains");
	    &dbg_hash(\%fnoc);

	    #
	    # Anything remaining will be new entries which we append.
	    #

	    my $once = 0;

	    foreach $k (keys %conf) {
		if (!$once++) {
		    note "  ** preserving rc.conf entry:";
		}
		note "\t$k=$conf{$k}";
		print OT "$k=$conf{$k}\n";
	    }

	    foreach $k (keys %fnoc) {
		if (!$once++) {
		    note "  ** preserving /etc/rc.conf entry:";
		}
		note "\t$k=$fnoc{$k}";
		print OT "$k=$fnoc{$k}\n";
	    }

	    close OT;
	    close IN;

	    #
	    # Commit to the new one.
	    #

	    sis "cp ${new_conf}.new .";
	    sis "mv ${new_conf}.new ${new_conf}";
	} #if no)preserve is 0
    }

#
# support_hacks - hacks for support
#

    sub support_hacks {
	local *DIR;
	my $dir = "/usr/local/etc/rc.d";

	opendir DIR, $dir or return;

	my @contents =
	    map "$dir/$_",
	    sort grep /_eq_support\.sh$/,
	    readdir DIR;

	closedir DIR;

	foreach (@contents) {
	    preserve $_;

	    chmod '0644', "/mnt/$_";

	    note "**** disabling execute on $_";
	}
    }

    sub chg_snmpd {
	local *PWE;
	local *PWD;

	my $OLD = "/var/eq/snmpd.cnf";
	my $NEW = "/var/eq/snmpd.cnf.new";
	my $need_change = 0;

	if (!-f "$OLD") {
	    note "No snmpd.cnf\n";
	    return;
	}
	open PWD, "<$OLD";

# Checking if we need to rewrite the file
	while (<PWD>) {
	    if (/^\s*sysObjectID/) {
		my $new = $_;
		my @all = split(" ", $new);
		if ($all[1] eq "0.0") {
		    $need_change = 1;
		}
	    }
	}

	close PWD;
	if ($need_change != 1) {
	    return;
	}

	open PWE, ">$NEW";
	open PWD, "<$OLD";

	while (<PWD>) {
	    if (/^\s*sysObjectID/) {
		my $correct_line  = "sysObjectID 1.3.6.1.4.1.24427.2.2.2.1\n";
		print PWE "$correct_line";
	    } else {
		print PWE;
	    }
	}
	close PWD;
	close PWE;

#saving old file in /var/eq/snmpd.cnf.OLD
	rename "$OLD", "$OLD.OLD";
	rename "$NEW", "$OLD";
    }

    sub munge_users {
	local *PWD;
	local *PWE;
	my %users = ();

	my $mve = "/mnt/.master/var/eq";
	open PWD, "</var/eq/users";

	while (<PWD>) {
	    if (/^\#/) {
	    } elsif (/^(touch|look):/) {
		my $posterity = $_;
		my ($user, $p) = split(/:/);
		$users{$user} = $posterity;
	    }
	}

	close PWD;

	open PWD, "<$mve/eq.conf";
	open PWE, ">$mve/eq.conf.new";

	while (<PWD>) {
	    print PWE;
	}

	close PWD;

	foreach $user (keys %users) {
	    my ($_user, $_p) = split(/[:\n]/, $users{$user});
	    print PWE "\nuser $_user {\n";
	    print PWE "  desc                 = \"user $_user\";\n";
	    print PWE "  password             = \"$_p\";\n";
	    if ($_user eq "touch") {
		print PWE "  change               = \"all, global\";\n}\n";
	    } else {
		print PWE "  read                 = \"all, global\";\n}\n";
	    }
	}

	close PWE;
	rename "$mve/eq.conf", "$mve/eq.conf.OLD";
	rename "$mve/eq.conf.new", "$mve/eq.conf";
    }

    sub ask_migrate_failover {

	if ($opt_G) {
	    print "\nDo you wish to preserve failover configuration? [y/n]\n";
        }
	while (1) {
	    my $ans;
	    if (!$opt_G) {
 	        print "\nDo you wish to preserve failover configuration? [y/n]";
	    }	

	    if ($opt_b) {
		print "Y\n";
		return 1;
	    }

	    $ans = &get_answer;

	    return 1 if ($ans =~ /^[Yy]/);
	    return 0 if ($ans =~ /^[Nn]/);
	}
    }

    sub munge_master_passwd {
	local *PWD;
	local *PWE;
	my %users = ();

	my $mve = "/mnt/.master/var/etc";
	open PWD, "</var/etc/master.passwd";

	while (<PWD>) {
	    if (/^\#/) {
	    } elsif (/^(toor|daemon|operator|bin|games|news|man):/) {
	    } elsif (/^(uucp|xten|pop|nobody|l7up|cpsup):/) {
	    } else {
		my $posterity = $_;
		my ($user, $p, $u, $g, $x, $y, $z, $n, $d, $s) = split(/:/);
		$users{$user} = $posterity;
	    }
	}

	close PWD;

	open PWD, "<$mve/master.passwd";
	open PWE, ">$mve/master.passwd.new";

	while (<PWD>) {
	    my ($user, $p, $u, $g, $x, $y, $z, $n, $d, $s) = split(/:/);

	    if (/^\#/) {
		print PWE;
	    } elsif (defined($users{$user})) {
		my ($_user, $_p, $_u, $_g, $_x, $_y, $_z, $_d, $_s)
		    = split(/:/, $users{$user});

		#
		# user exists in new system -- migrate passwd.
		#

		print PWE "$user:$_p:$u:$g:$x:$y:$z:$n:$d:$s";

		delete $users{$user};
	    } else {
		print PWE;
	    }
	}

	close PWD;

	foreach $user (keys %users) {
	    my ($_user, $p, $u, $g, $x, $y, $z, $n, $d, $s)
		= split(/:/, $users{$user});

	    note "*=*=*=* NOT migrating user $user.";
	}

	close PWE;
	
	rename "$mve/master.passwd" , "$mve/master.passwd.OLD";
	
	sis "pwd_mkdb -p -d $mve $mve/master.passwd.new";
	
    }

    sub keep_loghosts {
        my @oldfile;
        my $line;
        my $loghost = 0;
        my $OSC;
        my $NSC;
        my @tmpsc = ();
        my $already = 0;
        open OSC, "< /etc/syslog.conf";
        @oldfile = <OSC>;
        open NSC, "< /mnt/.master/var/etc/syslog.conf";
        foreach $line (@oldfile) {
	    chomp($line);
            if ($line =~ /^\*\.\*.*@.*/) {
                $loghost = $line;
                last;
            }
        }
        while ($line = <NSC>) {
            chomp($line);
	    # match only the commented-out example!
            if (!$already && ($line =~ /^#\*\.\*.*@/) && $loghost) {
                push @tmpsc, $loghost;
                $already = 1;
            } else {
                push @tmpsc, $line;
            }
        }
        close OSC;
        close NSC;
        open NSC, "> /mnt/.master/var/etc/syslog.conf";
        foreach $line (@tmpsc) {
            print NSC "$line\n";
        }
	close NSC;
    }

#
# phase3 -
#

    sub phase3 {

	#
	# cleanup /var/crash and clear savecore flag.
	#

	system "rm -f /var/crash/*.core";
	system "rm -f /var/crash/kernel.*";
	system "rm -f /var/crash/vmcore.*";
	system "/sbin/savecore -c 2>/dev/null";

	&new_fstab;

	# We have to be careful with rc_conf since
	# it contains hardware-specific information that can't be 
	# fixed during an upgrade.
	# We go and pull in the hardware-specific information in munge_rc_conf, 
	# and then
	# skip the rest of the function if we're returning the system to defaults.
	&munge_rc_conf;

	if ($no_preserve == 0) {
	    &munge_master_passwd;
	    &keep_loghosts;

	    #
	    # Preserve the newer machine's SSH host key(s) if present.
	    # Old machines will get a new one automatically on first boot
	    # of new system.
	    #

	    my @ssh = (
		       'ssh_host_dsa_key',
		       'ssh_host_dsa_key.pub',
		       'ssh_host_key',
		       'ssh_host_key.pub',
		       'ssh_host_rsa_key',
		       'ssh_host_rsa_key.pub',);

	    foreach $f (@ssh) {
		preserve "/etc/ssh/$f";
	    }

	    if ($opt_k) {
		preserve "/usr/home/cpsup/.ssh/authorized_keys";    
	    }
	    
	    if ($opt_e) {
		preserve "/usr/home/eqsupport/.ssh/authorized_keys";    
	    }    
	    
	    if ($pres_eq_conf == 1) {

		preserve "/etc/eq.conf";

		if ($set_use_ssl == 1) {
		    system "/usr/l7up/convert_default -i/mnt/.master/var/eq/eq.conf -v ".
			    "use_ssl_only -d \!use_ssl_only -o /mnt/.master/var/eq/eq.conf";
		}

		my $running_vers = &curr_vers();
                #
                # The translate_85_86 program takes the contents of the running system's
	        # eq.conf, rc.conf-eq and switch.conf and creates a new eq.conf that is 
	        # compatible with 8.6
                #
	        print "migrating network configurations.\n";
	       sis "/usr/l7up/./translate_85_86 -i /mnt/.master/var/eq/eq.conf -p /mnt/.master/var/eq/eq.conf";
	    } else {
		system "rm -rf /mnt/.master/var/eq/eq.conf";
		system "touch /mnt/.master/var/eq/eq.conf";
		print "eq.conf not preserved.\n";
	    }
       	   
            if ($pres_en_conf == 1) {
                preserve "/etc/envoy.conf";
            } else {
                system "rm -rf /mnt/.master/var/eq/envoy.conf";
                system "touch /mnt/.master/var/eq/envoy.conf";
                print "envoy.conf not preserved.\n";
            }

	    if ($pres_sw_conf == 1) {
		preserve "/etc/switch.conf";
	    } else {
		system "rm -rf /mnt/.master/var/eq/switch.conf";
		system "touch /mnt/.master/var/eq/switch.conf";
		print "switch.conf not preserved.\n";
	    }
	    preserve "/etc/natd.conf";
	    preserve "/etc/resolv.conf";
	    preserve "/etc/ntp.conf";
	    #preserve "/etc/syslog.conf";	# Now we preserve only lines with @.
	    preserve "/etc/ntp.drift";
	    preserve "/etc/hosts";
	    preserve "/etc/ipfw.conf";
	    preserve "/etc/localtime";
	    preserve "/etc/eq.previous";
	    preserve "/eq.previous";
	    preserve "/etc/tac_client.conf";

	    #
	    # Need to preserve the cluster certificates.
	    #

	    if (-d "/var/eq/ssl") {
		sis "find /var/eq/ssl -print | cpio -pdumL /mnt/.master/";
	    }

	    #
	    # Need to preserve the certificate and the key for equalizer if we
	    # have one else create it
	    #
	    if (-d "/var/eq/eqssl") {
		sis "find /var/eq/eqssl -print | cpio -pdumL /mnt/.master/";
	    } else {
		sis "mkdir -p /mnt/.master/var/eq/eqssl"; 
	    }
	    #
	    # Need to preserve the SKS
	    #
	    if (-d "/var/eq/sks") {
		sis "find /var/eq/sks -print | cpio -pdumL /mnt/.master/";
	    }

	    #
	    # Need to preserve any licenses
	    #

	    d_preserve "/var/eq/licenses";

	    #
	    # Need to preserve any responders.
	    #

	    if (-d "/var/eq/responder") {
		sis "find /var/eq/responder -print | cpio -pdumL /mnt/.master/";
	    }
	} 

	#
	# Preserve license files only while upgrade with default configuration.
	#
	if ($opt_C && $opt_l) {
	    d_preserve "/var/eq/licenses";
	}

	if ($opt_S) {
	    note " ** install SNMP ";

	    sis "tar zxf ./snmp.tgz -C /mnt 2>/usr/l7up/tar.out";

	    if ($no_preserve == 0) {
		&chg_snmpd;
		preserve "/var/eq/snmpd.cnf";
		preserve "/var/eq/traps.cnf";
	    }
	}

	#
	# Execute any additional version-specific upgrade script
	#

	sis "./upgrade.$vers.pl -p 2" if -x "./upgrade.$vers.pl";

	#
	# If desired, attempt to migrate failover config
	# fo_migrate will leave the interface disable flag
	# set, so we use perl to yank it out. 
	#
	if ($no_preserve == 0) {
	    if((-f "/etc/eq.static") && (-f "/etc/eq.conf")) {
		if (ask_migrate_failover()) {
		    sis "./fo_migrate -i/etc/eq.static"
			. " -o/mnt/.master/var/eq/eq.conf -c/etc/eq.conf";
		    sis "perl -pi -e 's/^.*if_flag.*//' /mnt/.master/var/eq/eq.conf";
		}
	    }

	    #
	    # If desired, attempt to migrate user config
	    #

	    if((-f "/var/eq/users")) {
		&munge_users;
	    }
	}

	#
	# Last step here is to check for any EQ support hacks and
	# copy them and disable the execue bits on them
	#

	&support_hacks();

	#
	# save copies of debug and log to new filesystem.
	#

	if (-f $logfile && -f $dbgfile && -d "/mnt/usr/home/cpsup") {
	    select((select(DBG), $| = 1)[0]);
	    sis "cp $logfile $dbgfile /mnt/usr/home/cpsup";
	    sis "cd /mnt/usr/home/cpsup && tar zcvf upginfo.tgz upgrade.???";
	    sis "cd /mnt/usr/home/cpsup && rm upgrade.???";
	}

	sis "sync";
	sis "sync";
	sis "sync";
	sis "sync";
	sis "sync";

    }

#
# slice_new - perform the DOS type disk partitioning
#

    sub slice_new {
	local *FH;

	&cark("Partition $slice_new is in use.\n")
	    unless !defined($fdisk{$slice_new});

	#
	# create new slice
	#
	note "**** Create $size sized FreeBSD partition.";

	&dbg("slice_new slice=$slice_new size=$size");
	&dbg("fdisk -f -");

	open FH, "|fdisk -f -";

	if ($slice_new == 1) {
	    $size -= 63;

	    print FH "p 1 165 63 $size\n";
	} else {
	    my $prev = $slice_new - 1;
	    my $start;

	    if ($slice_xtra) {
		#
		# Adjust this so that it nestles up against slice 4.
		#

		$start  = $fdisk{4}->{'start'} - $size;
	    } else {
		$start = $fdisk{'cyl'} * $fdisk{'hd'} * $fdisk{'sec'} - $size;
	    }

	    #
	    # Except that it needs to be bootable with all the
	    # crufty BIOSes out there.
	    #

	    my $spc = $fdisk{'hd'} * $fdisk{'sec'};	# sectors per cylinder
	    my $low = $fdisk{$prev}->{'start'} + $fdisk{$prev}->{'size'};

	    $low = $spc * (int(($low - 1) / $spc) + 1); # round up

	    while ($start > $low && int($start / $spc) > 1023) {
		#
		# This is silly, but we are seeking back by cylinders
		# until we get to a bootable one or we overlap the
		# $prev.
		#

		$start -= $spc;
	    }

	    if ($start / $spc >= 1024) {
		&cark("******** MAY NOT BE BOOTABLE");
	    }

	    &dbg(    "p $slice_new 165 $start $size");
	    print FH "p $slice_new 165 $start $size\n";
	}
	close FH;

	&sync_disk($disk);

	#
	# Verify that slice was created, and record the actual size
	#

	&fdisk_reset($disk);

	note "Created FDISK partition $slice_new of size"
	    . " $fdisk{$slice_new}->{'size'}.";

	#
	# fdisk tends to make them smaller at times.  The root size is now the
	# entire size, so adjust.
	#

	$root_size = $fdisk{$slice_new}->{'size'};

	note "Created FDISK partition $slice_new of size"
	    . " $fdisk{$slice_new}->{'size'}.";
    }

    sub disklabel {
	local *FH;
	local *OUT;
	my %label = ();
	my $resid = 0;

	$label{'c:size'} = $fdisk{$slice_new}->{'size'};

	my $bpc = $fdisk{'hd'} * $fdisk{'sec'};

	unlink "/tmp/l7upd";
	open OUT, ">/tmp/l7upd";

	print OUT "type: ESDI\n";
	print OUT "disk: ${disk}s$slice_new\n";
	print OUT "label: l7upgrade\n";
	print OUT "flags:\n";
	print OUT "bytes/sector: 512\n";
	print OUT "sectors/track: $fdisk{'sec'}\n";
	print OUT "tracks/cylinder: $fdisk{'hd'}\n";
	print OUT "sectors/cylinder: $bpc\n";
	print OUT "cylinders: $fdisk{'hd'}\n";
	print OUT "sectors/unit: $fdisk{$slice_new}->{'size'}\n";
	print OUT "rpm: 3600\n";
	print OUT "interleave: 1\n";
	print OUT "trackskew: 0\n";
	print OUT "cylinderskew: 0\n";
	print OUT "headswitch: 0\n";
	print OUT "track-to-track seek: 0\n";
	print OUT "drivedata: 0 \n";

	print OUT "3 partitions:\n";

	#
	# layout the space
	#

	$resid = $label{'c:size'};

	$label{'a:size'} = $root_size;
	$label{'a:offs'} = 0;

	$resid -= $label{'a:size'};

	$label{'b:size'} = $swap_size;	# vestigeal
	$label{'b:offs'} = $label{'a:size'} + $label{'a:offs'};

	$resid -= $label{'b:size'};

	&dbg("disklabel: %label");
	&dbg_hash(\%label);

	print OUT "a: $label{'a:size'} $label{'a:offs'} 4.2BSD 0 0 0\n";
	print OUT "c: $label{'c:size'} 0                unused 0 0\n";

	close OUT;

	#
	# Install this disklabel
	#

	note "**** installing new disk label on ${disk}s$slice_new";
	sis "dd if=/dev/zero of=/dev/${disk}s$slice_new bs=1b count=1";
	sis "disklabel -R -r ${disk}s$slice_new /tmp/l7upd";

	#
	# create filesystems
	#

	sis "cd /dev; ./MAKEDEV ${disk}s${slice_new}a";

	foreach $mp (@mp) {
	    my $newfs_opts = "";
	    my $rdev = $slice_map{$mp};

	    $rdev =~ s:/dev/:/dev/r:;

	    # $newfs_opts = "-O " if $mp eq "/";

	    sis "rm -f /tmp/rstmode*";
	    sis "rm -f /tmp/rstdir*";
	    note "****";
	    note "**** create filesystem for $mp";
	    note "****";
	    sis "newfs $newfs_opts $rdev";
	    sis "mkdir -p /mnt/$mp";
	    sis "touch /mnt/$mp/failed";
	}
    }

    sub restore {

	note "****";

	#
	# it is much easier now.
	#

	system "fsck  $slice_map{'/'}";
	sis "mount -o async $slice_map{'/'} /mnt/";
	&cark("failed to mount  '/'\n") if -f "/mnt/failed";

	note "**** extract bits for '/'";

	vsis "gzcat $bits_map{'/'} | (cd /mnt/ && tar xvSpf -) 2>/usr/l7up/tar.out";

	sis "sync; sync; sync or swim;";

	#
	# Ensure we have correct /dev entries.
	#

	sis "cd /mnt/dev; ./MAKEDEV ${disk}s${slice_new}a";
	sis "cd /mnt/dev; ./MAKEDEV ${disk}s${slice_xtra}a" unless !$slice_xtra;

	#
	# Install boot blocks
	#

	note "**** install boot blocks";

	if (-x "/usr/sbin/chroot") {
	    sis "chroot /mnt boot0cfg  -B $disk";
	    sis "chroot /mnt disklabel -B ${disk}s$slice_new";
	} else {
	    sis "boot0cfg  -B $disk";
	    sis "disklabel -B ${disk}s$slice_new";
	}
    }

#
# md5_check - check md5 checksum
#

    sub md5_check {
	my $what    = shift or &cark("");
	my $desired = shift or &cark("");
	my $sum;

	chop($sum = `md5 $what`);

	$sum =~ s/.* = //;

	$sum eq $desired;
    }

#
# md5_verify - verify md5 checksum
#

    sub md5_verify {
	my $what    = shift or &cark("");
	my $desired = shift or &cark("");

	&cark("$what: md5 mismatch.\n") unless md5_check($what, $desired);
    }

#
# bom_check - verify files match the BoM
#

    sub bom_check {
	my $bom = shift;
	local *BOM;

	open BOM, "<$bom" or &cark("$!: $bom\n");

	while (<BOM>) {
	    chop;

	    if (/^\# EQVERS=([^:]+)/) {
		my $v = $1;

		&cark("Version mismatch: $vers != $v\n") unless $v eq $vers;

		next;
	    }

	    if (/^md5 /) {
		my ($ign, $md5, $uid, $gid, $mode, $path) = split;

		# note "verifying file $path";

		&cark("missing $path\n") unless -f $path;

		md5_verify($path, $md5);
	    } elsif (/^exe (.*)$/) {
		my $todo = $1;
	    } else {
		warn "unexpected BoM entry: $_\n";
	    }
	}

	close BOM;
    }

    sub badger {

	print "System reboot is required.";
        if ($opt_G) {
            print "\nReboot now? [y/n]\n";
	}    
	while (1) {
	    my $ans;
            if (!$opt_G) {
	        print "\nReboot now? [y/n]";
	    }	

	    if ($opt_b) {
		print "YES\n";
		return 1;
	    }
	    $ans = &get_answer;

	    return  if ($ans =~ /^[Yy]/);
	    if ($ans =~ /^[Nn]/) {
		printf "Aborting reboot.\n";
	        exit(0); 
	    }	
	}
    }

# 
# Check the version level to see if we have the version of switchd
# (8.5.0g or later) that supports quiesce
#
    sub switchd_verscheck {
	my $vers;
	my $rc;


	if (-f "/.version/eq") {
	    open IN, "< /.version/eq" or die;
	    while (<IN>) {
		chop;

		if (/^EQVERS=(\d.*)$/) {
		    $vers = $1;
		}
	    }
	} else {
	    $vers = "0";
	}

	if ($vers lt "8.5.0g") {
	    $rc = 1;
	}
	else {
	    $rc = 0;
	}

	close IN;
	return $rc;
    }

#
# switch_firmware
#    
    sub switch_firmware {
	my $reboot = 0;
	my $ans=0;
	 
	if ($opt_F) {
	    sleep 1;
            print "WARNING: THIS MAY TAKE UP TO 5 MINUTES. YOU\n";
            print "MUST NOT POWER CYCLE THE SYSTEM DURING THIS\n";
            print "TIME, OR INTERRUPT THIS PROCESS IN ANY\n";
            print "OTHER WAY!\n";
            print "[If you are using the GUI, click 'Show Details'\n";
            print "to follow the progress of the upgrade.]\n";
            print "\n";
	    print "Do you acknowledge this warning? [y/y]\n";
	    $ans = &get_answer;
	    system "/usr/local/sbin/switchd -Q";
	    sleep 5;
	    select(STDOUT); $| = 1;
	    system("/usr/l7up/swconfig -S -N -X 'fw guiflash ./cp301.bin '");
	    if (($? == 0)) {
	        $reboot = 1;
	    }
	    else {
	        print "Firmware remains at previous version.\n";
	        print "Software upgrade will continue.\n";
	        $reboot = 0;
	    }    
	}    
	return $reboot;
    }	

#
# cert_check - checking for sibd & GUI certificate file.
#
    sub cert_check {
	if ($opt_G) {
	    system "/bin/sh /usr/l7up/regen_sslkeys -G /var/tmp/eq_upgrade.in";
	} else {
	    system "/bin/sh /usr/l7up/regen_sslkeys";
	}
	if (($?)) {
	    print "********************************************************\n";
	    &note("**Certificate check failed\n");
	    &note("**** Please contact Coyote Point customer support.");
	    die ("Aborting upgrade");
	}
    }

#
# If this is a system running version < 8.6, check for 
# network misconfiguration. See bug #3275
# Subroutine returns zero if everything is cool and
# no corrective action is required. Returns 1 if we need
# to do a "-C" upgrade 
#

sub check_for_invalid_net_config {

    my $trouble = 0;

#
# Find the value of EQ_IF_0 & EQ_IF_1 in /etc/rc.conf
#

    open IN, "< /etc/rc.conf" or &cark("$!\n");

    while (<IN>) {
	if (/^EQ_IF_0=(.*)$/) {

	    $net0 = $1;
	    $net0 =~ s/^(["''"])(.*)\1/$2/;

	    &dbg("net0 = $net0");
	    
	} elsif (/^EQ_IF_1=(.*)$/) {

	    $net1 = $1;
	    $net1 =~ s/^(["''"])(.*)\1/$2/;

	    &dbg("net1 = $net1");
	}
    }

#
# Check to see if ifconfig_$net0 is populated
# but ifconfig_$net1 is blank
#

    open IN, "< /var/etc/rc.conf-eq" or &cark("$!\n");
    $ifcfg0 = " ";
    $ifcfg1 = " ";

    while (<IN>) {
	if (/^ifconfig_$net0=(.*)$/) {

	    $ifcfg0 = $1;
	    $ifcfg0 =~ s/^(["''"])(.*)\1/$2/;

	    &dbg("ifcfg0 = $ifcfg0");
	    
	} elsif (/^ifconfig_$net1=(.*)$/) {

	    $ifcfg1 = $1;
	    $ifcfg1 =~ s/^(["''"])(.*)\1/$2/;

	    &dbg("ifcfg1 = $ifcfg1");
	    
	}
    }

#
# It is possible to see ifconfig_xx0='' or ifconfig_xx0='inet  netmask  '
# we only want ifconfig_xx0='inet ###.###.###.### netmask ###.###.###.###'
#

    if ($ifcfg0 =~ m/^\s*inet\s*[\d\.]+\s*netmask\s*[\d\.]/) {
	&dbg("ifcfg0 was true\n");
    } else {
	$ifcfg0 = 0;
    } 

    if ($ifcfg1 =~ m/^\s*inet\s*[\d\.]+\s*netmask\s*[\d\.]/) {
	&dbg("ifcfg1 was true\n");
    } else {
	$ifcfg1 = 0;
    } 

    my $running_vers = &curr_vers();
    if (($running_vers lt "8.6.0") && ($ifcfg0 && !$ifcfg1)) {

	$trouble = 1;

	print "\n\n";
	print "*****************************************************************************\n";
	print "* You are upgrading this system to Version 8.6.\n"; 
	print "* The upgrade process has determined that you have an IP address configured\n";
	print "* on the Equalizer external interface, but none on the internal interface.\n";

	if ($opt_G) {
	    print "* This is not a supported configuration. You will need to correct\n";
	    print "* the network misconfiguration and retry the upgrade.\n";
	    print "*****************************************************************************\n";
	    die ("Upgrade terminated\n");
	}


	print "* This is not a supported configuration. You may either exit the upgrade and\n";
	print "* correct the network misconfiguration, or you may delete the entire existing\n";
	print "* configuration.\n";
	print "*****************************************************************************\n\n";

	$valid_ans = 0;
	while ($valid_ans == 0) {

	    print "Do you wish to abort(a) the upgrade or\n";
	    print "delete(d) the existing configuration? [a/d]: ";

	    my $ans;
	    $ans = &get_answer;
	    if ($ans =~ /^[Aa]/) {
		die ("Upgrade terminated");
	    } elsif ($ans =~ /^[Dd]/) {
		print "Continuing upgrade.\n";
		$valid_ans++;
	    } else {
		print "ERROR: You did not enter a valid response.\n\n";
	    }
	}
    }

    return $trouble;
}
  
#    
# main
#    
    sub main {
        my $reboot = 0;
	&Getopts('RbkeSCrGFl');

	if ($opt_C) {
	    my $rusure;
	    print "**************************************************************\n";
	    print "WARNING: You have specified for the upgrade script\n";
	    print "to clear the system configuration and revert to defaults\n";
	    print "Are you absolutely SURE that you would like to continue? [y/n]\n";
	    $rusure = &get_answer;
	    if ($rusure =~ /^[Nn]/) {
		die("Aborting upgrade.");
	    } else {
		print "****************************************************************\n";
		print "WARNING: Please re-confirm: your Equalizer will not be\n";
		print "accessible except by serial, and any configuration will be lost.\n";
		print "Are you sure you want to continue? [y/n]\n";
	        if ($opt_G) {
		    print "\n";
	        }	
	        $rusure = &get_answer;
		if ($rusure =~ /^[Yy]/) { 
		    $no_preserve = $opt_C;
		} else {
		    die("You did not type \"y\". Aborting upgrade.");
		}
	    }
	}

        #
        # Save STDOUT and STDERR before we "tee" up so we can restore
        # console output for the switch firmware update
        #
	open(OLDOUT, ">&STDOUT");
        open(OLDERR, ">&STDERR");
        #
        # This is just so we don't get any annoying "used only once" 
        # messages...
        #
	stat OLDOUT;
	stat OLDERR;
	
	&previous;
	&setup;
	&verscheck;
	&bom_check("./BoM");

	&lm_check(0);

	# sibd & gui Certificate checks
	&cert_check;

	# See comments at head if called function
        # no need to perform this check if -C was specified
	if ($no_preserve || &check_for_invalid_net_config) {
	    $no_preserve = 1;
	}

	#
	# Check whether the OLD configuration can be parsed with the
	# NEW parser.
	#
	if ($no_preserve == 0) {
	    if((-f "/.master/var/eq/eq.conf")) {

		my $running_vers = &curr_vers();
		if ($running_vers lt "8.6.0") {
		    #
		    # convert use_ssl_only flag for failover 
		    #
		    print "\n\nYou are upgrading this system to Version 8.6. If you will be using this\n"; 
		    print "system in a failover configuration with an Equalizer that is running\n";
		    print "Version 8.5, then you must disable the \"use ssl only\" flag on the 8.6\n";
		    print "system.\n";
		    print "Would you like the upgrade script to disable this option for you now? [y/n]\n";
		    my $ans;
		    $ans = &get_answer;
		    if ($ans =~ /^[Yy]/) {
			$set_use_ssl = 1;
		    }
		}

		print "\n\nConverting default for once_only...\n";
		system "/usr/l7up/convert_default -i/.master/var/eq/eq.conf -v ".
		    "once_only -d once_only -o /.master/var/eq/eq.conf";
		if (($? == 0)) {
		    print "\nConversion done.\n";
		}

		print "\n\nChecking configuration with new version's parser...\n";
		system "/usr/l7up/parse_config -i/.master/var/eq/eq.conf -U";
		if (($? == 512)) {
		    $cfgcount = 0;
                    while ($cfgcount == 0) {
                        print "Do you wish to continue(c) or abort(a) the upgrade? [c/a]:";
                        my $ans;
                        $ans = &get_answer;
                        if ($ans =~ /^[Aa]/) {
			    die ("Aborting upgrade");
                        } elsif ($ans =~ /^[Cc]/) {
                            print "Continue upgrade.\n";
                            $cfgcount++;
                        } else {
                            print "ERROR: You did not enter a valid response.  Please try again. \n";
                        }
                    }
		}
	 	elsif (($?)) {
		    print "***********************************************************************\n";
		    print "Configuration parse FAILED\n\n";
			print "You have the following options:\n".
			"  (k)eep the current configuration file\n".
			"     - Continue the upgrade using the current configuration file.\n".
			"     - Edit the file after the system boots to fix the above errors.\n".
			"  (d)elete the current configuration\n".
			"     - Delete all clusters, servers, VLANs, user logins, etc.\n".
			"     - After reboot, you must log in using the serial console and\n".
			"     - use the eqadmin utility to configure the network and logins.\n".
			"  (a)bort the upgrade\n".
			"     - Exit to the command line now to edit the configuration file\n".
			"       and restart the upgrade.\n\n".
			"If you need assistance editing your configuration file, please note the\n".
			"errors above and contact support\@coyotepoint.com.\n";
		    print "***********************************************************************\n";
		    $cfgcount = 0;
		    while ($cfgcount == 0) {
			print "\nDo you wish to keep(k) or delete(d) your configuration, or abort(a)\n".
		   	"the upgrade? [k/d/a]\n";
	                if ($opt_G) {
		            print "\n";
	                }	
			my $ans;
			if ($opt_b) {
			    print "k\n";
			    return 1;
			}
			
			$ans = &get_answer;
			if ($ans =~ /^[Dd]/) {
			    print "Are you sure you would like to delete the configuration file? [y/n] \n";
	                    if ($opt_G) {
		                print "\n";
	                    }	
			    $ans = &get_answer;
			    if ($ans =~ /^[Yy]/) {
				$pres_eq_conf = 0;
				$cfgcount++;
			    } else {
				print "ERROR: You did not Enter \"y\".  Try again.\n";
			    }
			} elsif ($ans =~ /^[Kk]/) {
			    print "Keeping configuration.\n";
			    $cfgcount++;
			} elsif ($ans =~ /^[Aa]/) {
			    die ("Aborting upgrade");
			} else {
			    print "ERROR: You did not enter a valid response.  Please try again. \n";
			}
			
		    }
		    
		} else {
		    print " SUCCESS\n\n";
		}   
		
	    } else {
		print "Configuration file not found -- not checking configuration.\n";
	    }

	    if((-f "/.master/var/eq/envoy.conf")) {
		print "\n\nChecking envoy configuration with new version's parser...\n";
		system "/usr/l7up/envoy_parse_config -i /.master/var/eq/envoy.conf";
		if (($?)) {
		    print "***********************************************************************\n";
		    print "Envoy configuration parse FAILED\n\n";
			print "You have the following options:\n".
			"  (k)eep the current configuration file\n".
			"     - Continue the upgrade using the current configuration file.\n".
			"     - Edit the file after the system boots to fix the above errors.\n".
			"  (d)elete the current Envoy configuration\n".
			"     - All geo clusters, sites, and Envoy parameters are deleted.\n".
			"     - The cluster, server and networking configuration is unchanged.\n".
			"  (a)bort the upgrade\n".
			"     - Exit to the command line now to edit the configuration file\n".
			"       and restart the upgrade.\n\n".
			"If you need assistance editing your configuration file, please note the\n".
			"errors above and contact support\@coyotepoint.com\n";
		    print "***********************************************************************\n";
		    $cfgcount = 0;
		    while ($cfgcount == 0) {
			print "\nDo you wish to keep(k) or delete(d) your Envoy configuration, or\n".
			"abort(a) the upgrade? [k/d/a]\n";
	                if ($opt_G) {
		            print "\n";
	                }	
			my $ans;
			if ($opt_b) {
			    print "k\n";
			    return 1;
			}
			
			$ans = &get_answer;
			if ($ans =~ /^[Dd]/) {
			    print "Are you sure you would like to delete the configuration file? [y/n] \n";
	                    if ($opt_G) {
		                print "\n";
	                    }	
			    $ans = &get_answer;
			    if ($ans =~ /^[Yy]/) {
				$pres_en_conf = 0;
				$cfgcount++;
			    } else {
				print "ERROR: You did not Enter \"y\".  Try again.\n";
			    }
			} elsif ($ans =~ /^[Kk]/) {
			    print "Keeping configuration.\n";
			    $cfgcount++;
			} elsif ($ans =~ /^[Aa]/) {
			    die ("Aborting upgrade");
			} else {
			    print "ERROR: You did not enter a valid response.  Please try again. \n";
			}
		    }
		    
		} else {
		    print " SUCCESS\n\n";
		}   
		
	    } else {
		print "Envoy configuration file not found -- not checking configuration.\n";
	    }

#
# In Version 8.6 and above, we no longer use switch.conf. 
# And, in that case, there is no reason to parse_config it	    
#
	}

	#
	# Ruthlessly delete non-active slices.
	#

	foreach $s (1..3) {
	    next unless ($s != $slice_old);
	    next unless defined($fdisk{$s});

	    &reclaim_space($disk, $s);
	}

	my $spc  = $fdisk{'hd'} * $fdisk{'sec'};	# sectors per cylinder
	my $i    = 0;
	my ($r, $v, $u) = ($root_size, $var_size, $usr_size);

	$slice_new = 0;

	while (!$slice_new) {
	    #
	    # Try hard to fit in.  Various buggy prior upgrades
	    # left strange partitions which are slightly "too small".
	    # Try reducing our demands.
	    #

	    $root_size = $spc * int($r / $spc) - $i * $spc;
	    $var_size  = 0;
	    $usr_size  = 0;
	    $swap_size = 0;
	    $tmp_size  = 0;

	    $size      = ($root_size + $swap_size + $var_size
			  + $tmp_size + $usr_size);

	    if ($root_size < (3 * $r) / 4) {
		&cark("Insufficient space.\n");
	    }

	    note "**** trying root $root_size";

	    $slice_new = next_slice($size);

	    ++$i;	# sic.
	}

	%slice_map = (
		      '/'	    => "/dev/${disk}s$slice_new" . "a",
		      );
	#
	# Ensure that there are /dev entries
	#

	sis "cd /dev; ./MAKEDEV ${disk}s$slice_new";
	sis "cd /dev; ./MAKEDEV ${disk}s$slice_new" . "c";

        # creating ntp.drift file
	sis "touch /var/etc/ntp.drift";

	&slice_new;
	&disklabel;
	&restore;
	&phase3;

	# if this is a kontron box, create a kernel configuration file
	# disabling syscons

	chop($systype = `./lm -h -U`);

	if ($systype =~ /^classic-1U/) {
	    note "disabling syscons device";
	    open FH, "| cat > /mnt/boot/kernel.conf" or die;
	    print FH "disable sc0\n";
	    print FH "quit\n";
	    close FH;
	} 

	# Fix up timezone stuff.
	&zoneupdate;

	# Fix up boot time tunables
	system "/bin/sh /usr/l7up/tune.sh /mnt";

	&undo_mounts;
	note "";

	#
	# Assuming this all worked, arrange to boot to new partition
	#

	sis "boot0cfg -o packet -s$slice_new $disk";
	&dbg_save;

        if (!$opt_G) {
            #
            # Restore output to console because we want to see the progress 
            # of the switch firmware update
            #	
	    close(STDERR);
            close(STDOUT);
            open(STDOUT, ">&OLDOUT");
            open(STDERR, ">&OLDERR");
            select(STDOUT); $| = 1;
            select(STDERR); $| = 1;
	}
	
	#
	# Check switch firmware level and update if necessary
	#
	if ($opt_F) {
	    $reboot = &switch_firmware;
	}    

	if (($opt_r) or ($reboot)) {
	    print "Performing automatic reboot.\n";
	    print "System will reboot with new version.\n";
	    sleep 1;
	} else {
	    &badger;
	}

	#
	# Now redo what we undid
	#
	if ($opt_G) {
            open(STDOUT, '>>'.$outputfile ) or die $!;
            open(STDERR, '>>'.$outputfile ) or die $!;
	}    
        else {	
  	    open(STDOUT, "| tee -a $logfile") or &cark("$!\n");  
	    open(STDERR, ">&STDOUT") or &cark("$!\n");
        }
        select(STDOUT); $| = 1;
        select(STDERR); $| = 1;

	sis "shutdown -r now";

	1;
	if ($opt_G) {
            close STDOUT;
	}    
    }

#
# kluges for migration from 7.1.0 f/s layout to 7.2.0 f/s layout.
#

    sub kluges {
	sis "mkdir -p /.master/var/eq/licenses";
	sis "touch /.master/.LOCK";
    }

    &main;
