# -*-perl-*-

# $Id: Inittab.plm,v 1.2 2003/12/15 16:28:35 thomas Exp $

# What package to handle inittab files

# $Log: Inittab.plm,v $
# Revision 1.2  2003/12/15 16:28:35  thomas
# check-in for 0.1.1
#
# Revision 1.1.1.1  2003/12/02 14:47:35  thomas
# Initial version before export to 0.1.1
#

=head1 NAME

What::Node::Inittab - manage /etc/inittab

=head1 DESCRIPTION

This B<What> configuration package controls the content of the file
B</etc/inittab>.

From the inittab(5) man page, the format of inittab records is

id:runlevels:action:process

This is mapped into the B<What> configuration space as

<node path='/Inittab/B<id>' runlevels='B<runlevels>' action='B<action>' process='B<process>' />

Any missing attributes will be mapped with empty values in the
corresponding B<inittab> entry.  An optional B<comment> attribute can
also be specified, which will translate into a one-line comment above
the entry (the B<apply> method will supply the leading '#' character
for the comment).

An additional attribute B<priority> can be specified to force a sort
order on the entries in /etc/inittab.  Higher-priority nodes will be
ordered before lower-priority nodes.  Nodes with no B<priority> attribute will be placed at the end of the inittab file.

The root B<Inittab> node can also have several attributes.  These are:

=over 4

B<minimum_size> -- provides a sanity-check for new
inittabs.  If the generated inittab is smaller than the value of the
B<minimum_size> attribute, the new inittab will not be installed.

B<inittab_path> -- This attribute provides the name of the inittab
file to be generated.  It defaults to B</etc/inittab>.

=back

In addition, any triggers associated with the root B<Inittab> node
will be exected whenever the configuration is updated.  The typical
trigger would execute B<telinit Q>, B<kill -1 1>, or the like, to
cause the inittab to be re-examined by the init process.

=head1 EXAMPLES

Run xdm at run level 5

<node path='/Inittab/x' runlevels='5' action='respawn'
process='/usr/bin/X11/xdm -nodaemon' comment='Run xdm in runlevel 5' />

Run a getty on the serial console

<node path='/Inittab/c2' runlevels='12345' action='respawn'
process='/sbin/mingetty ttyS0 DT9600 ansi'
comment='Secondary console for servers' />


=cut

package What::Node::Inittab;

use strict;
use FileHandle;
use Data::Dumper;
use File::stat;
use File::MkTemp;

use base qw(What::Node);

our %opts = ();

# Set the priority for this module in $::
$::module_priority{'Inittab'} = 20;

###############################################################################
# Apply the current configuration

sub apply {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $sname = shift;
  my $cname = shift;

  my ($children, $child, $newtab, $attrs, $attr, $id);
  my ($st, $fh, $oldtab, $newname, $dir, $base);

  my ($minimum_size, $inittab_path) = (800, '/etc/inittab');
  my $rootattrs = $class->getattrs ($section);
  exists ($rootattrs->{minimum_size}) and
    $minimum_size = $rootattrs->{minimum_size};
  exists ($rootattrs->{inittab_path}) and
    $inittab_path = $rootattrs->{inittab_path};

  $newtab = <<__Eof__
###
# This inittab was generated automatically by What::Node::Inittab
# Hand-made changes will be lost!!
###


__Eof__
  ;

  $children = $class->getchildren($section);
  my %priority;
  foreach $child (sort keys %$children) {
    $priority{$child} = $children->{$child}->getattrs ($section, attr => 'priority');
    defined ($priority{$child}) or $priority{$child} = 0;
  }
  foreach $child (sort {$priority{$b} <=> $priority{$a}} keys %$children) {
    $attrs = $children->{$child}->getattrs ($section);
    exists ($attrs->{comment}) && defined ($attrs->{comment})
      and $newtab .= "# $attrs->{comment}\n";
    $id = $child;
    if (length ($id)>2) {
      # The id was non-unique.  Put it back the way we found it.
      $id = substr ($id,0,2);
      $id =~ s/\.$//;
    }
    $newtab .= $id;
    foreach $attr ('runlevels', 'action', 'process') {
      $newtab .= ':';
      defined ($attrs->{$attr}) and $newtab .= $attrs->{$attr};
    }
    $newtab .= "\n\n";
  }

  # Now check it agains the existing inittab file

  if ($class->check_and_replace_content ($inittab_path,
					 $newtab,
					 $minimum_size,
					 1
					)) {
    $class->run_triggers ($section, $what, $cname);
    return 1;
  }
  return 0;
}

###############################################################################

sub generate {
  my ($class, $section, $what) = @_;

  my ($fh, $id, $runlevels, $action, $process);
  my ($priority) = (999);
  my $file = (defined ($::opts{root}) ? $::opts{root} : '') . '/etc/inittab';
  my $ids = {};

  defined ($fh = new FileHandle $file, "r") or return 0;
  while (<$fh>) {
    /^\s*\#/ and next;
    if (/^(\S\S?):(\S*):(\S+):(.*)$/) {
      ($id, $runlevels, $action, $process) = ($1, $2, $3, $4);
      $id = make_unique_id ($id, $ids);
      if (!defined $id) {
	$class->err ("Can't find unique id for inittab entry:\n\t%s", $_);
	next;
      }
      $class->setattrs (
			$section,
			{
			 runlevels => $runlevels,
			 action    => $action,
			 process   => $process,
			 priority  => $priority--,
			},
			$id,
			1
		       );
      $ids->{id} = 1;
    }
  }
  return $1;
}


###############################################################################
# Make a unique id.  If I have to substitute, make the length longer than 2,
# and then undo it in the apply step.

sub make_unique_id {
  my $id = shift;
  my $ids = shift;

  my $i=1;
  exists ($ids->{$id}) or return $id;

  (length ($id) < 2) and $id="$id.";
  while (exists $ids->{"$id$i"}) {$i++};
  return "$id$i";

}

###############################################################################

1;
