#!/usr/bin/perl

#use diagnostics -v;
use strict;
no strict 'subs';

#   sysvconfig: a text menu based utility for configuring sysvint.
#
#   Copyright (C) 2004 John G. Hasler (john@dhh.gt.org)
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License Version 2 as 
#   published by the Free Software Foundation.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#   or contact the author.
#
#   On Debian systems the GNU General Public License can be found in
#   /usr/share/common-licenses/GPL.

# This hack exists so that the program can run in the absence of gettext
# or POSIX.
my $no_gettext;
BEGIN {
    eval "use Locale::gettext";
    $no_gettext = $@;
    eval "use POSIX";
    if ($no_gettext || $@) {
	eval 'sub gettext($) { return $_[0]; }';
    }
    else {
	setlocale($ENV{LC_MESSAGES}, "");
	textdomain("sysvconfig");
    }
}
my $version = "0.70";
my $initdir ="init.d";
my $etcdir = "/etc";
my $initpath ="$etcdir/init.d";
my $backupfile = "/var/lib/sysvconfig/backup";
my $guidefile = "/usr/share/sysvconfig/guide";
my %guide = ();
my %enabled = ();
my %working_enabled = ();
my %scripts = ();
my %working_scripts = ();
my %backup = ();
my %backup_enabled = ();
my %servicechecksums;
my $backupflag = ''; # If set don't do (another) backup.
my $backtitle = gettext("\"Debian Sysv Config Utility\"");
my $ui = '';
my $debug = ''; # If set we are in debug mode.
my $listlinks = ''; # If set print a table of links and exit.
my $noninteractive = '';
my $restorelinks = '';
my $result = 0;
my $scriptschecksum = 0;
my $backupexists = -s $backupfile;
my $guideexists = -f $guidefile;
my $enable_daemon = '';
my $disable_daemon = '';

sub version() {
  die("sysvconfig $version\n");
}

sub help() {
  print "sysvconfig $version\n" ;
  print gettext("Sysvconfig is an interactive, menu driven utility to help automate setting \
up sysv symlinks. \n");
  usage();
}

sub usage() {
  die(gettext("Usage: sysvconfig [--version] | [--help] | [--listlinks] |\
[--restore] | [--enable daemon] | [--disable daemon] [--debug [directory]]\
\'--version | -v\' prints the version. \'--help | -h\' prints a help message.\
\'--listlinks\' prints a table of the links. \'--restore\' restores the backup.\
\'--debug\' prints debugging information at the end of the run.\
\'--debug directory\' also uses \'directory\' instead of \'etc\'.
\'--debug\' must be the last option.\n"));
}

sub getoptions {
    my $opt;
  LOOP: while ($#ARGV >= 0) {
      $opt = shift @ARGV;
      $opt =~ /^\-\-version$|\^-v$/ && do {
	  usage() if $#ARGV > 0;
	  version();
      };
      $opt =~  /^\-\-help$|\^-h$/ && do {
	  usage() if $#ARGV > 0;
	  help();
      };
#      $opt =~  /^\-\-whiptail$/ && do {
#	  usage() if $ui;
#	  $ui = "whiptail";
#	  next LOOP;
#      };
      $opt =~  /^\-\-dialog$/ && do {
	  usage() if $ui;
	  $ui = "dialog";
	  next LOOP;
      };
      $opt =~  /^\-\-gdialog$/ && do {
	  usage() if $ui;
	  $ui = "gdialog";
	  next LOOP;
      };
      $opt =~  /^\-\-kdialog$/ && do {
	  usage() if $ui;
	  $ui = "kdialog";
	  next LOOP;
      };
      $opt =~  /^\-\-xdialog$/ && do {
	  usage() if $ui;
	  $ui = "Xdialog";
	  next LOOP;
      };
      $opt =~  /^\-\-listlinks$/ && do {
	  $noninteractive = 1;
	  $listlinks = 1;
	  next LOOP;
      };
      $opt =~  /^\-\-restore$/ && do {
	  $noninteractive = 1;
	  $restorelinks = 1;
	  next LOOP;
      };
      $opt =~  /^\-\-enable$/ && do {
	  usage() if $disable_daemon;
	  $noninteractive = 1;
	  $enable_daemon = shift @ARGV;
	  next LOOP;
      };
      $opt =~  /^\-\-disable$/ && do {
	  usage() if $enable_daemon;
	  $noninteractive = 1;
	  $disable_daemon = shift @ARGV;
	  next LOOP;
      };
      $opt =~ /^\-\-debug$/ && do {
	  usage() if $debug;
	  $debug = 1;
	  if ($#ARGV >= 0) {
	      $etcdir = shift @ARGV;
	      # We've been told to use an alternate "etc" directory.
	      # Point backupfile into the alternate so we don't clobber the real backupfile.
	      $backupfile = "backup";
	      $backupexists = -s $backupfile;
	  }
	  next LOOP;
      };
     usage(); 
  }
    my $initpath ="$etcdir/init.d";

  SWITCH: {
      if (`which $ui`)      {last SWITCH; }
#     if (`gdialog --version 2>&1` =~ /^gdialog.*debian/)  {$ui = "gdialog"; last SWITCH; }
#      if (`which whiptail`) {$ui = "whiptail"; last SWITCH; }
      if (`which dialog`)   {$ui = "dialog"; last SWITCH; }
      die gettext("No UI");
  } 
}

# Make a dialog box.
sub dialogbox(@) {
    my $type;
    my @options = ();
    my @vars;
    my $text;
    my $title;
    my @uilist;
    my $pid;
    my $temp = '';
    my $item = '';
    @vars=@_;
# On option is allowed, and must follow the type.  Pull the type 
# and the option out of @vars and put them in @options in the correct order.
    @options=(shift(@vars));
    unshift @options, (shift @vars) if $vars[0] =~/--/;
    $text=shift( @vars );
    $title=shift( @vars ) if( $#vars >= 0 );
    @uilist = ($ui, "--title", $title, "--backtitle", $backtitle, @options, $text, "25", "80", @vars);

# Using fork()/exec() and a pipe instead of system() or backticks avoids
# the shell and consequently simplifies quoting.
    
    pipe(RDR,  WTR);
    if ($pid = fork) { # Parent
	close WTR or die(gettext("Can't close WTR in parent: "), $!, "\n");
	# By undefining $/ we can suck all the output of the child
	# into $item, newlines and all.
	undef $/;
	$item = <RDR>;
	$/ = "\n";
	close RDR or die(gettext("Can't close RDR in parent: "), $!, "\n");
	waitpid($pid, 0);
    } 
    else { # Child or failed fork()
	die (gettext("cannot fork: "), $!, "\n") unless defined $pid;
	close RDR or die(gettext("Can't close RDR in child: "), $!, "\n");
	open STDERR, ">&WTR" or die(gettext("Can't redirect stderr: "), $!, "\n");
	exec @uilist or die(gettext("Exec failed: "), $!, "\n");
    }
    $result = ($? >> 8);
    quit() if ($result == 255);
    die(gettext("Internal error: "), $!, "\n") unless($result == 0 || $result == 1);
    return $item;
}

sub msgbox(@) {
    dialogbox( "--msgbox", @_ );
}

sub infobox(@) {
    dialogbox( "--infobox", @_ );
}

sub yesnobox(@) {
    dialogbox( "--yesno", @_ );
}

# A yesnobox that defaults to no.
sub noyesbox(@) { 
    if ($ui =~ /whiptail|dialog/) {
	dialogbox("--yesno", "--defaultno", @_ );
    }
    else {
	dialogbox( "--yesno", @_ );
    }
}

sub inputbox(@) {
    dialogbox( "--inputbox", @_ );
}

sub menu(@) {
    my $text;
    my $title;
    my $menu_height;
    $text=shift( @_ );
    $title=shift( @_ );
    $menu_height=shift( @_ );
    dialogbox( '--menu', $text, $title, $menu_height, @_ );
}

sub checklist(@) {
    my $text;
    my $title;
    my $menu_height;
    $text=shift( @_ );
    $title=shift( @_ );
    $menu_height=shift( @_ );
    dialogbox( '--checklist', '--separate-output', $text, $title, $menu_height, @_);
}

sub radiolist(@) {
    my $text;
    my $title;
    my $menu_height;
    $text=shift( @_ );
    $title=shift( @_ );
    $menu_height=shift( @_ );
    dialogbox( "--radiolist", $text, $title, $menu_height, @_ );
}

sub form(@) {
    my $text;
    my $title;
    my $menu_height;
    $text = shift(@_);
    $title=shift( @_ );
    $menu_height=shift( @_ );
    dialogbox( "--form", $text, $title, $menu_height, @_ );
}
# end of interface to dialog

sub mkmenu {
    my ($a, $b, $c, $d, $e, $f, $h, $i, $j) = '';
    my $action = '';
    my @menuvar = '';
    my $menu = $_[0];
    
  SWITCH: for( $menu ) {
      /^main/ && do {
	  # This section sets up the main menu.
	  
	  @menuvar = (gettext("This is the sysvinit runlevel editor.  Use the up and down arrow keys to move around the menu.  Hit ENTER to select an item.  Use the TAB key to move from the menu to <OK> to <CANCEL> and back.  When you are ready to move on to the next menu go to <OK> and hit ENTER.  To go back to the previous menu go to <CANCEL> and hit enter."),
		      gettext("Main Menu"), 5,
		      
		      "Enable/Disable", gettext("Enable or disable a service"),
		      "Edit", gettext("Edit runlevels"),
		      "Restore", gettext("Restore backup."));
	  push @menuvar, "Finished", gettext("Finish and save files") if checkchanges("CHECK");
	  unshift @menuvar, "--nocancel";
	  last SWITCH;
      };
      
      /^edit/ && do {
	  return;
      };
      # end of edit block
      
      /^CANCEL/ && do {
	  return "CANCEL";
	  last SWITCH; };
      
  } # End of SWITCH
    
    push @menuvar, "Previous", ( gettext("Return to previous menu")) unless $menu eq "main";
    
    push @menuvar, ( "Quit", gettext("Exit this utility"));
    
    do { $action = menu @menuvar } while $action eq ' ' ;
    # Put up the menu that we just constructed.
    # ' ' means that a blank line has been selected, so just loop.
    return "CANCEL" if ($result != 0); # He hit cancel: return to main menu.
    return $action;
} # end of mkmenu

sub do_action() {
    my $action;
    my $menu = "main";
    my @previous_action_stack;
    my @previous_menu_stack = "main";
    while (1) {
	push @previous_menu_stack, $menu if $menu ne $previous_menu_stack[-1];
	$action = mkmenu( $menu );
      ACTION: for( $action ) {
	  /^Previous|^CANCEL/ && do {
	      pop @previous_menu_stack;
	      $menu = @previous_menu_stack ? pop @previous_menu_stack : "main";
	      last ACTION;
	  };
	  /^Enable\/Disable/ && do {
	      $menu = enable( $menu );
	      last ACTION;
	  };
	  /^Edit/ && do {
	      $menu = edit( $menu );
	      last ACTION;
	  };
	  /^Restore/ && do {
	      $menu = restore( $menu );
	      last ACTION;
	  };
	  /^Finished/ && do {
	      $menu = finish( $menu );
	      last ACTION;
	  };
	  /^Quit/ && do {
	      $menu = quit( $menu );
	      last ACTION;
	  };
	  /.*/ && do {
	      die (gettext("Internal error: no such thing as $action, "));
	  };
      }
    } # End of while(1)
} # End of do_action

sub printlinks {
# Print a table of the services in the structure to the specified file or to STDOUT. 
    my $daemon = '';
    my $level = '';
    my @a = ();
    my ($ref, $file) = @_;
    my $buffer = '';
    foreach $daemon (keys %$ref) {
	for ($level=0; $level < 7; $level++) {
	    if ($$ref{$daemon}[$level]{'S'}) {
		$a[$level] = 'S' . $$ref{$daemon}[$level]{'S'};
	    }
	    else {
		if ($$ref{$daemon}[$level]{'K'}) {
		    $a[$level] = 'K' . $$ref{$daemon}[$level]{'K'};
		}
		else {
		    $a[$level] = ' -';
		}
	    }
	}
	$buffer .= sprintf "%-20s%-4s%-4s%-4s%-4s%-4s%-4s%-4s\n", $daemon, @a;
    }
    if ($file) {
	open HANDLE, ">", "$file" or die (gettext("Open failed"));
	printf HANDLE $buffer;
	close HANDLE or die(gettext("Can't close file: "), $!, "\n");
    }
    else {
	printf $buffer;
    }
}

sub getlinks {
    my @services;
    my $level;
    my $daemon;
    my $dir;
# Get a list of the scripts in /etc/init.d.  Filter out files that aren't scripts.
    opendir INITD, $initpath or die(gettext("Couldn't open $initpath: "), $!, "\n");
    @services = grep !/^\.\.?$|^skeleton$|^README$|.*\.dpkg-dist$|.*\.dpkg-old$|^rc$|^rcS$/, readdir INITD;
    closedir INITD or die(gettext("Couldn't close $initpath: "), $!, "\n");
# Create a hash with the script names for keys.
    %scripts = map {$_, ''} @services;
    
# Get all the links in the /etc/rc*.d directories into a structure.  The
# structure is a hash of arrays of hashes.  The keys of the top-level hash
# are script names, the values arrays with the runlevels as indices.  The
# elements of the arrays are hashes with the keys S and K.  The values of the
# bottom level hash are the sequence numbers of the scripts.
    foreach $daemon (keys %scripts) {
	$enabled{$daemon} = 0;
	my @rc = ();
	for ($level=0; $level < 7; $level++) {
	    my @links = ();
	    my $link = ();
	    my $K = ();
	    my $S = ();
	    $dir= "rc".$level.".d/";
	    opendir RC, $dir or die (gettext("Couldn't open $dir: "), $!, "\n");
	    while ($link = readdir RC) {
		# Save the link only if it really is a link and points to the correct
		# script.  Allow for the possibility of more than one link.
		if (readlink $dir.$link eq "../$initdir/$daemon") {
		    push @links, $link;
		}
	    }
	    closedir RC or die (gettext("Couldn't close $dir: "), $!, "\n");;
	    next unless @links;
	    # Make sure the link name has the right form, and pick it apart.  Allow
	    # for the possibility of an S and a K link.  Ignore the possibility of
	    # two S's or two K's.
	    ($K) = grep /^K[[:digit:]][[:digit:]]$daemon$/, @links;
	    ($rc[$level]{'K'}) = $K =~ /([[:digit:]][[:digit:]])/ if $K;
	    ($S) = grep /^S[[:digit:]][[:digit:]]$daemon$/, @links;
	    ($rc[$level]{'S'}) = $S =~ /([[:digit:]][[:digit:]])/ if $S;
	    $enabled{$daemon} ||= $rc[$level]{'S'} ? 1 : 0 ;
	}
	$scripts{$daemon} = [ @rc ];
	for ($level=0; $level < 7; $level += 1) {
	    $working_scripts{$daemon}[$level]{'K'} = $scripts{$daemon}[$level]{'K'};
	    $working_scripts{$daemon}[$level]{'S'} = $scripts{$daemon}[$level]{'S'};
	}
    }    
    %working_enabled = %enabled;
    if($debug) {
	print "\n        Links as read in at startup\n";
	printlinks(\%scripts);
    }	    
}

sub getbackup {
# Read in the backup file and store it in the %backup structure.
    my @buffer = ();
    my $daemon = '';
    my $level;
    unless ($backupexists) {
	return; # The file may not exist.  That's ok.
    }
    open BACKUP, "<$backupfile" or die (gettext("Couldn't open $backupfile: "), $!, "\n");
    while (@buffer = split ' ', <BACKUP>) {
	$daemon = $buffer[0];
	$backup_enabled{$daemon} = 0;
	for ($level = 1; $level < 8; $level++) {
	    $backup{$daemon}[$level - 1]{'K'} = $backup{$daemon}[$level -1]{'S'} = '';
	    if ($buffer[$level] =~ /(K|S)([[:digit:]][[:digit:]])/) {
		$backup{$daemon}[$level - 1]{$1} = $2;
	    }
	    $backup_enabled{$daemon} ||= $backup{$daemon}[$level]{'S'} ? 1 : 0 ;
	}						   
    }
    close BACKUP or die(gettext("Couldn't close $backupfile: "), $!, "\n");
    if($debug) {
	print "\n        Backup file as read in at startup\n";
	printlinks(\%scripts);
    }
}

sub getguide {
    # Read in the guidefile and store it in the guide hash.
    my $buffer = '';
    my $daemon = '';
    my $description = '';
    unless ($guideexists) {
	warn (gettext("Couldn't find $guidefile: "), $!, "\n");
	return;
    }
    open GUIDE, "< $guidefile" or die (gettext("Couldn't open guidefile: "), $!, "\n");
    while ($buffer = <GUIDE>) {
	# Skip blank lines and comments.
	next if(grep /^ |^#/, $buffer);
	# Service name is the first field, left-justified.  Everything after
	# the first space is the description.
	($daemon, $description) = $buffer =~ /(^[[:graph:]]+)[[:space:]]+(.*$)/;
	$guide{$daemon} = $description;
    }
    close GUIDE or die(gettext("Could't close file: "), $!, "\n");
}

sub edit($) {
    my @inlist;
    my @outlist;
    my $temp;
    my $row = 1;
    my $daemon = '';
    my $level = '';
    my $col;
    my $item;
    my $flen = 3;
    my $ilen = 0;
    my $ks;
    my $nn;
    my $error = '';

    # Head each column with the level.
    for ($level=0; $level < 7; $level++) {
	$col = 23 + 6 * $level;
	push @inlist, ("$level", $row, $col, $level, $row, $col, "0", "0");
    }

    foreach $daemon (sort keys %working_scripts) {
	$row += 1;
	for ($level=0; $level < 7; $level++) {
	    $col = 22 + 6 * $level;
	    if($level == 0) {
		push @inlist, ($daemon, $row, "1");
	    } else {
		push @inlist, (" ", $row, $col);
	    }
	    if ($working_scripts{$daemon}[$level]{'K'}) {
		$item = 'K' . $working_scripts{$daemon}[$level]{'K'};
	    }
	    elsif ($working_scripts{$daemon}[$level]{'S'}) {
		$item = 'S' . $working_scripts{$daemon}[$level]{'S'};
	    }
	    else {
		$item = " - ";
	    }
	    push @inlist, ($item, $row, $col, $flen, $ilen);    
	}
    }

  LOOP: $temp = form(gettext("\
Use the up and down arrows to move between fields and the left and right arrows to move within a field.  You must delete characters with the backspace before entering new ones.  Links must consist of the letter K or S followed by two digits.  To delete a link just leave the field blank.  When you are finished, use TAB to select <OK> and ENTER to move on to the next item.") . $error,
		 gettext("Edit links for a Service"), 13, @inlist);
    return  "CANCEL" if ( $result != 0 );
    # Replace fields containing zero or more spaces with ' - '.
    $temp =~ s/\n *\n/\n - \n/gs;
    # If the first character is a newline then then first field is empty
    # If contains only spaces it is also empty.  In either case, insert ' - '.
    $temp =~ s/^ *\n/ - \n/s;
    @outlist = split '\n', $temp;

    foreach $daemon (sort keys %working_scripts) {
	for ($level=0; $level < 7; $level++) {
	    foreach $item (@outlist) {
		if ($item !~ /[KSks][[:digit:]][[:digit:]]| - /) {
		    $error = gettext("\n\n***ERROR***  One of your entries was not in the correct form.");
		    goto LOOP;
		}
	    }
	}
    }
    foreach $daemon (sort keys %working_scripts) {
	$working_enabled{$daemon} = 0;
	for ($level=0; $level < 7; $level++) {
	    $item = shift @outlist;
	    ($ks, $nn) = $item =~ /([KSks])([[:digit:]][[:digit:]])/;
	    $ks = uc($ks);
	    $working_scripts{$daemon}[$level]{'K'} = $working_scripts{$daemon}[$level]{'S'} = '';
	    if($ks) {
		$working_scripts{$daemon}[$level]{$ks} = $nn;
		$working_enabled{$daemon} ||= $working_scripts{$daemon}[$level]{'S'} ? 1 : 0 ;
	    }
	    #print "$daemon $working_scripts{$daemon}[$level]{$ks}\n" if $ks;
	}
    }
    
    return $_[0];
}

sub enable($) {
    my @list;
    my $temp;
    my $daemon;
    my $level;
    my $ok;

    unless ($noninteractive) {
    @list = map{sprintf("%-20s %-45s", $_, $guide{$_}), ' ', $working_enabled{$_} ? "on" : "off"} (sort keys %working_enabled) ;
    undef $/;
    $temp=checklist (gettext("\
Services marked with a X are enabled.  Use the up and down arrow keys to move among the selections, and press  the spacebar to change the status of the selected one.  When you are finished, use TAB to select <OK> and ENTER to move on to the next item."),
		     gettext("Enable or Disable a Service"), 10, 
		     @list);
        $/ = "\n";
        $temp = "\n$temp";
        return  "CANCEL" if ( $result != 0 );
    } else {
	print "entering noninteractive enable\n";
	$daemon = $enable_daemon | $disable_daemon;
	die "No such service\n" unless grep $daemon, (keys %scripts);

	if ($disable_daemon) {
	    exit 0 if ($enabled{$daemon} == 0); # It's already disabled.
	    # We are disabling a service that was enabled.  Change all S's to K's.
	    # There must be at least one.
	    for ($level=0; $level < 7; $level += 1) {
		if ($working_scripts{$daemon}[$level]{'S'}) {
		    $working_scripts{$daemon}[$level]{'K'} = $working_scripts{$daemon}[$level]{'S'};
		    $working_scripts{$daemon}[$level]{'S'} = '';
		}
	    }
	    return;
	}

	exit 0 if ($enabled{$daemon} == 1); # It's already enabled.

	if (($backup_enabled{$daemon})) {
	    # It is enabled in the backup.  Restore it.
	    for ($level=0; $level < 7; $level += 1) {
		$working_scripts{$daemon} = $backup{$daemon};
		$working_scripts{$daemon}[$level]{'K'} = $backup{$daemon}[$level]{'K'};
		$working_scripts{$daemon}[$level]{'S'} = $backup{$daemon}[$level]{'S'};
	    }
	    return;   
	}

	$ok = 0;
	# We can't restore it so use what information we can find.
	for ($level=2; $level < 6; $level += 1) {
	    # If there are any K's in 2 through 5 change them to S's.
	    if ($working_scripts{$daemon}[$level]{'K'}) {
		$ok = 1;
		$working_scripts{$daemon}[$level]{'S'} = $working_scripts{$daemon}[$level]{'K'};
		$working_scripts{$daemon}[$level]{'K'} = '';
	    }
	}
	return if $ok;
        # All else has failed so install defaults.
	for ($level=2; $level < 6; $level += 1) {
	    $working_scripts{$daemon}[$level]{'S'} = 20;
	    $working_scripts{$daemon}[$level]{'K'} = '';
	}
	$working_scripts{$daemon}[0]{'K'} = 20;
	$working_scripts{$daemon}[1]{'K'} = 20;
	$working_scripts{$daemon}[6]{'K'} = 20;
	return;
    }

    foreach $daemon (keys %working_enabled) {
	$working_enabled{$daemon} = (grep /\n$daemon /, $temp) ? 1 : 0;
        if ($enabled{$daemon} == $working_enabled{$daemon}) {
	    # We are returning a service to the state it was in when we started.
	    # Just restore it.
	    for ($level=0; $level < 7; $level += 1) {
		$working_scripts{$daemon}[$level]{'K'} = $scripts{$daemon}[$level]{'K'};
		$working_scripts{$daemon}[$level]{'S'} = $scripts{$daemon}[$level]{'S'};
	    }
	    next;
	}
	if ($working_enabled{$daemon} == 0) {
	    # We are disabling a service that was enabled.  Change all S's to K's.
	    # There must be at least one.
	    for ($level=0; $level < 7; $level += 1) {
		if ($working_scripts{$daemon}[$level]{'S'}) {
		    $working_scripts{$daemon}[$level]{'K'} = $working_scripts{$daemon}[$level]{'S'};
		    $working_scripts{$daemon}[$level]{'S'} = '';
		}
	    }
	    next;
	}
	# We are enabling a disabled service that was disabled when we started up.
	if (($backup_enabled{$daemon})) {
	    # It is enabled in the backup.  Restore it.
	    for ($level=0; $level < 7; $level += 1) {
		$working_scripts{$daemon} = $backup{$daemon};
		$working_scripts{$daemon}[$level]{'K'} = $backup{$daemon}[$level]{'K'};
		$working_scripts{$daemon}[$level]{'S'} = $backup{$daemon}[$level]{'S'};
	    }
	    next;
	}
	$ok = 0;
	# We can't restore it so use what information we can find.
	for ($level=2; $level < 6; $level += 1) {
	    # If there are any K's in 2 through 5 change them to S's.
	    if ($working_scripts{$daemon}[$level]{'K'}) {
		$ok = 1;
		$working_scripts{$daemon}[$level]{'S'} = $working_scripts{$daemon}[$level]{'K'};
		$working_scripts{$daemon}[$level]{'K'} = '';
	    }
	}
	next if $ok;
	# All else has failed so install defaults.
	for ($level=2; $level < 6; $level += 1) {
	    $working_scripts{$daemon}[$level]{'S'} = 20;
	    $working_scripts{$daemon}[$level]{'K'} = '';
	}
	$working_scripts{$daemon}[0]{'K'} = 20;
	$working_scripts{$daemon}[1]{'K'} = 20;
	$working_scripts{$daemon}[6]{'K'} = 20;
    }
    if($debug) {
	print "\n        Results of enable\n";
	printlinks(\%working_scripts);
    }
    return $_[0];
}

sub quit($) {
    if (checkchanges("CHECK")) {
	noyesbox (gettext("\
Do you wish to quit without saving your changes?"), gettext("Quit"));
	if( $result ) {
	    # true result means no so go back to menu.
	    return $_[0];
	}
    }
    exit(0);
}

sub checkchanges(@) {
# If called with CHECK, compare checksums for the arrays with those
# computed when the files were read in.  Return true if any have changed.
# If called with SET initialize the checksums.
  SWITCH: for( $_[0] ) {
      /CHECK/ && do {
	  return ($scriptschecksum != checksumscripts(\%working_scripts));
	  last SWITCH;
      };
      /SET/ && do {
	  $scriptschecksum = checksumscripts(\%scripts);
	  return;
	  last SWITCH;
      };
  }
}

sub checksumscripts {
# Compute a 32 bit checksum of a scripts structure.
    my $ref;
    my $string;
    my $level;
    my $daemon;
    ($ref) = @_;
    my @temp;
    # unwind the structure into a string so we can get a checksum for it.
    foreach $daemon (keys %$ref) {
	$string .= $daemon;
	for($level = 0; $level < 7; $level++) {
	    $string .= $$ref{$daemon}[$level]{'S'} ? $$ref{$daemon}[$level]{'S'}.'S' : $$ref{$daemon}[$level]{'K'}.'K';
	}
    }
    return (checksum ($string));
}

sub daemonchecksum {
# Compute a 32 bit checksum for one service in a scripts structure.
    my @array;
    my $string = " "; # Don't want to pass checksum a null string.
    my $level;
    @array = @_;
    for($level = 0; $level < 7; $level++) {
	$string .= $array[$level]{'S'} ? $array[$level]{'S'}.'S' : $array[$level]{'K'}.'K';
	print "string = $string\n";
    }
    return checksum ($string);
}

sub checksum {
# Compute a 32 bit checksum of a string.
    return (unpack ("%32C*", $_[0]) % 32767);
}

sub finish($) {
# Write out the backup file and update the links.
    my $temp;
    my $daemon;
    my $level;
    my $rcdir;
    my $oldlink;
    my $oldlinkpath;
    my $newlinkpath;
    my $newlink;
    # Write out the links we read in at startup to the backup file
    # if that has not already been done.
    printlinks(\%scripts, $backupfile) unless $backupflag;
    $backupflag = 1;

    foreach $daemon (keys %working_scripts) {
	for ($level = 0; $level < 7; $level++) {
	    $rcdir = "rc$level.d";
	    if (($scripts{$daemon}[$level]{'K'} == $working_scripts{$daemon}[$level]{'K'})
		&& $scripts{$daemon}[$level]{'S'} == $working_scripts{$daemon}[$level]{'S'}) {
		next; # Nothing here needs changing.
	    }
	    
	    # Initialize oldlink.
	    if ($scripts{$daemon}[$level]{'K'}) {
		$oldlink = 'K'.$scripts{$daemon}[$level]{'K'}.$daemon;
	    } elsif ($scripts{$daemon}[$level]{'S'}) {
		$oldlink = 'S'.$scripts{$daemon}[$level]{'S'}.$daemon;
	    } else {
		$oldlink = '';
	    }
	    
	    # Initialize newlink.
	    if ($working_scripts{$daemon}[$level]{'K'}) {
		$newlink = 'K'.$working_scripts{$daemon}[$level]{'K'}.$daemon;
	    } elsif ($working_scripts{$daemon}[$level]{'S'}) {
		$newlink = 'S'.$working_scripts{$daemon}[$level]{'S'}.$daemon;
	    } else {
		$newlink = '';
	    }
	    
	    $newlinkpath = "$rcdir\/$newlink";
	    $oldlinkpath = "$rcdir\/$oldlink";
	    if ($newlink && $oldlink) {
		# Oldlink exists and a new one is wanted: rename.
		rename $oldlinkpath, $newlinkpath;
	    }
	    elsif ($oldlink) {
		# Oldlink exists and no newlink is wanted: unlink.
		unlink $oldlinkpath;
	    } else {
		# Oldlink does not exist.  Create the desired link.
		symlink "\.\.\/$initdir/$daemon", $newlinkpath;
	    }
	    
	    $scripts{$daemon}[$level]{'K'} = $working_scripts{$daemon}[$level]{'K'};
	    $scripts{$daemon}[$level]{'S'} = $working_scripts{$daemon}[$level]{'S'};
	}
	$enabled{$daemon} = $working_enabled{$daemon};
    }
    if($debug) {
	print "\n        Links as written in Finish\n";
	printlinks(\%scripts);
    }    
    return if($noninteractive);
    checkchanges("SET");
    msgbox(gettext("\
Finished writing changes.  You will now have an opportunity \
to exit the program or make more changes."),
	   gettext("Finished"));
    $result = 0;
    return "main"; # JGH debug
}

sub restore($) {
# Restore the links to the state they were in at the beginning of the previous
# run of this program.  If a script has been removed that service will be skipped.
    my $level;
    my $newlinkpath;
    my $oldlinkpath;
    my $temp;
    my $daemon;
    my $rcdir;
    my $oldlink;
    my $newlink;

    unless ($noninteractive) {
	yesnobox(gettext("\
Answer \'yes\' to restore the links to the state they were in at the beginning if the previous run of this program.  If a script has been removed it will be skipped."),
		 gettext("Restore Previous Links"));
	    return "main" if ($result); # true $result means no.
    }

    unless ($backupexists) {
	if ($noninteractive) {
	    print "No backup\n";
	    exit 1;
	} else {
	    msgbox(gettext("\
The backup file is empty or does not exist."),
		   gettext("Restoration Failed"));
	    $result = 0;
	    return "main"; # JGH debug  
	}
    }

    foreach $daemon (keys %backup) {
	for ($level = 0; $level < 7; $level++) {
	    $rcdir = "rc$level.d";

	    $scripts{$daemon}[$level]{'K'} = $working_scripts{$daemon}[$level]{'K'} = 
		$backup{$daemon}[$level]{'K'};
	    $scripts{$daemon}[$level]{'S'} = $working_scripts{$daemon}[$level]{'S'} = 
		$backup{$daemon}[$level]{'S'};

	    if (($scripts{$daemon}[$level]{'K'} == $backup{$daemon}[$level]{'K'})
		&& $scripts{$daemon}[$level]{'S'} == $backup{$daemon}[$level]{'S'}) {
		next; # Nothing here needs changing.
	    }
	    
	    if ($scripts{$daemon}[$level]{'K'}) {
		$oldlink = 'K'.$scripts{$daemon}[$level]{'K'}.$daemon;
	    } elsif ($scripts{$daemon}[$level]{'S'}) {
		$oldlink = 'S'.$scripts{$daemon}[$level]{'S'}.$daemon;
	    } else {
		$oldlink = '';
	    }
	    
	    if ($backup{$daemon}[$level]{'K'}) {
		$newlink = 'K'.$backup{$daemon}[$level]{'K'}.$daemon;
	    } elsif ($backup{$daemon}[$level]{'S'}) {
		$newlink = 'S'.$backup{$daemon}[$level]{'S'}.$daemon;
	    } else {
		$newlink = '';
	    }
	    
	    $newlinkpath = "$rcdir\/$newlink";
	    $oldlinkpath = "$rcdir\/$oldlink";
	    if ($newlink && $oldlink) {
		rename $oldlinkpath, $newlinkpath;
	    }
	    elsif ($oldlink) {
		unlink $oldlinkpath;
		print "unlink\n";
		exit 0;
	    } else { 
		symlink "\.\.\/$initdir/$daemon", $newlinkpath;
	    }
	    
	}
	$enabled{$daemon} = $working_enabled{$daemon};
    }


    if($debug) {
	print "\n        Links as written by Restore\n";
	printlinks(\%scripts);
    }
    return if($noninteractive);
    checkchanges("SET");
    msgbox(gettext("\
Finished restoring links.  You will now have an opportunity to exit the program or make more changes."),
	   gettext("Restored"));
    $result = 0;
    return "main"; # JGH debug
}

getoptions();
die (gettext("You must be root to run this program.\n")) if(( $> != 0 ) && !($debug || $listlinks));
chdir $etcdir or die gettext("Can't cd to $etcdir: $!\n");
getlinks();
if ($listlinks) {
    printlinks(\%scripts);
    exit 0;
}
getbackup();
if ($restorelinks) {
    restore('dummy');
    exit 0;
}
if ($enable_daemon || $disable_daemon) {
    enable('dummy');
    finish('dummy');
    exit 0;
}
getguide();
checkchanges("SET");
do_action (); # This is the main loop.
