#!/usr/bin/perl
#
# Author: Petter Reinholdtsen
# Date:   2005-08-21
#
# Read LSM init.d headers in SysV init.d scripts, and verify correct
# start order for all runlevels.  It can also provide a graph.
#
# To generate a graph, run it like this
#
#   check-initd-order -g > initorder.dotty && dotty initorder.dotty 

use strict;
use warnings;
use Getopt::Std;
use File::Basename;

my $rcbase = "/etc";
#$rcbase = "/opt/ltsp/i386/etc";

my $debug = 0;

my %rcmap =
    (
     'B' => 'rc.boot',
     'S' => 'rcS.d',
     '1' => 'rc1.d',
     '2' => 'rc2.d',
     '3' => 'rc3.d',
     '4' => 'rc4.d',
     '5' => 'rc5.d',
     '6' => 'rc6.d',
     );

# Map packages to system metapackages.  These dependencies should
# probably be more complex
my %sysmap =
    (
     'network'      => '$network',
     'networking'   => '$network',
     'syslog'       => '$syslog',
     'sysklogd'     => '$syslog',
     'klogd'        => '$syslog',
     'mountall'     => '$local_fs',
     'mountnfs'     => '$remote_fs',
     'hwclock'      => '$time',
     'ntpdate'      => '$time',
     'bind9'        => '$named',
     'portmap'      => '$portmap',
     );

my %scriptorder;
my %opts;

getopts('dg', \%opts);

$debug = $opts{'d'};

if ($opts{'g'}) {
    graph_generate();
    exit 0;
}

check_bootorder();

sub graph_addnode {
    my %lsbinfo = @_;

    my @provides = split(/\s+/, $lsbinfo{'provides'});
    for my $name (@provides) {
	if (exists $sysmap{$name}) {
	    graph_addnode('provides'       => $sysmap{$name},
			  'required-start' => $name);
	}
    }

    if (1 < @provides) {
	print STDERR "Unable to properly handle multiple provides: @provides\n";
    }

    if ($lsbinfo{'required-start'}) {
	my @depends = split(/\s+/, $lsbinfo{'required-start'});
	for my $pkg (@depends) {
	    print "\"$pkg\" -> \"$provides[0]\"[color=blue];\n";
	}
    }
    if ($lsbinfo{'should-start'}) {
	my @depends = split(/\s+/, $lsbinfo{'should-start'});
	for my $pkg (@depends) {
	    print "\"$pkg\" -> \"$provides[0]\"[color=springgreen] ;\n";
	}
    }
    print "\"$provides[0]\" [shape=box];\n";
}

sub graph_generate {
    print "# Generating graph\n";
    print <<EOF;
digraph packages {
concentrate=true;
EOF
    for my $rcdir ($rcmap{S}, $rcmap{2}) {
	chdir "$rcbase/$rcdir/.";
	for my $script (<[SK]*>) {
	    my $lsbinforef = load_lsb_tags("$rcbase/$rcdir/$script");
	    
	    unless (defined $lsbinforef) {
		print STDERR "LSB header missing in $rcbase/$rcdir/$script\n";
		next;
	    }
	    my %lsbinfo = %{$lsbinforef};
	    graph_addnode %lsbinfo;
	}
    }
    print <<EOF;
}
EOF
}

sub check_bootorder {
    my $bootorder = 0;
    for my $rcdir ($rcmap{S}, $rcmap{2}) {
	chdir "$rcbase/$rcdir/.";
	for my $script (<[SK]*>) {
	    $bootorder++;
	    my ($tag, $order, $name) = $script =~ m/^(.)(\d{2})(.+)$/;

	    $scriptorder{$tag}{$name} = $bootorder;
	    $scriptorder{$tag}{$sysmap{$name}} = $bootorder
		if (exists $sysmap{$name});

#	    print "$script\n";
#	    print "T: $tag O: $order N: $name\n";
	    my $lsbinforef = load_lsb_tags("$rcbase/$rcdir/$script");

	    unless (defined $lsbinforef) {
		print STDERR "LSB header missing in $rcbase/$rcdir/$script\n";
		next;
	    }
	    my %lsbinfo = %{$lsbinforef};

	    for my $provide (split(/\s+/, $lsbinfo{'provides'})) {
		$scriptorder{$tag}{$provide} = $bootorder;
		$scriptorder{$tag}{$sysmap{$provide}} = $bootorder
		    if (exists $sysmap{$provide});
	    }

	    if ('S' eq $tag) {
		if ($lsbinfo{'required-start'}) {
		    my @depends = split(/\s+/, $lsbinfo{'required-start'});
		    for my $dep (@depends) {
			unless (exists $scriptorder{$tag}{$dep}
				and $scriptorder{$tag}{$dep} < $bootorder) {
			    print "Incorrect order " .
				"$dep\@". $scriptorder{$tag}{$dep} .
				" > $name\@$order\n";
			}
		    }
		}
	    }
	    if ('K' eq $tag) {
	    }
	}
    }
}

sub load_lsb_tags {
    my ($initfile, $override) = @_;
    print STDERR "Loading $initfile\n" if $debug;
    ### BEGIN INIT INFO
    # Provides:          xdebconfigurator
    # Required-Start:    $syslog
    # Required-Stop:     $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      1 6
    # Short-Description: Genererate xfree86 configuration at boot time
    # Description:       Preseed X configuration and use dexconf to
    #                    genereate a new configuration file.
    ### END INIT INFO
    open(FILE, "<$initfile") or die "Unable to read $initfile";
    my $found = 0;
    my ($provides, $requiredstart, $requiredstop, $shouldstart, $shouldstop);
    while (<FILE>) {
	chomp;
	$found = 1 if (m/\#\#\# BEGIN INIT INFO/);
	next unless $found;
	last if (m/\#\#\# END INIT INFO/);

	$provides = $1      if (m/^\# provides:\s+(\S*.*\S+)\s*$/i);
	$requiredstart = $1 if (m/^\# required-start:\s+(\S*.*\S+)\s*$/i);
	$requiredstop = $1  if (m/^\# required-stop:\s+(\S*.*\S+)\s*$/i);
	$shouldstart = $1   if (m/^\# should-start:\s+(\S*.*\S+)\s*$/i);
	$shouldstop = $1    if (m/^\# should-stop:\s+(\S*.*\S+)\s*$/i);
    }
    close(FILE);

    # Try override file
    $initfile = readlink($initfile) if (-l $initfile);
    my $basename = basename($initfile);
    
    if (!$found) {
	print STDERR "Override /usr/share/insserv/overrides/$basename\n"
	    if $debug;
	if (!$override && -f "/usr/share/insserv/overrides/$basename") {
	    return load_lsb_tags("/usr/share/insserv/overrides/$basename", 1);
	}
    }
    return undef unless ($found);

#    print "Provides: $provides\n" if $provides;
    return {
	    'provides'       => $provides,
	    'required-start' => $requiredstart,
	    'required-stop'  => $requiredstop,
	    'should-start'   => $shouldstart,
	    'should-stop'    => $shouldstop,
	    };
}
