CfgTie::TieRCService - A module to manage Unix services


CfgTie documentation Contained in the CfgTie distribution.

Index


Code Index:

NAME

Top

CfgTie::TieRCService -- A module to manage Unix services

SYNOPSIS

Top

   my %RC;
   tie %RC, 'CfgTie::TieRCService';

DESCRIPTION

Top

This is a straightforward interface to the control scripts in @file{/etc/rc?.d} This package helps manage these system services. The tie hash is structured like so:

   {
       $Service_Name => $Service_Ref,
   }

$Service_Ref is a hash reference; the details will be covered in the next section. (tied $Service_Ref) can also be treated as an object to control the service. That is covered in the Service Methods section.

While fetching from the structure, and deleting services is supported (and reflected to the system), directly storing new services is not. Currently the method to do this is:

	(tied %RC)->add('mynewservice');

This will add the new service (to be managed as well as available) to the run-levels. The start and kill scripts will be linked into each appropriate run-level. The script should already exist (in the proper format) in /etc/rc.d/init.d or equivalent.

The Service hash reference

   {
      levels   => [], 
      defaults => [],
      category => [],
      pid  => $pid,
      path =>,
      description =>,
      start_priority=>,
      stop_priority =>,
   }

levels refers to a list used to determine if the service is present for a given run-level. The scope of changes this list is system-wide. It is persistent across boots. Example:

    my $listref = (tied %{$RC{'atd'}})->levels();

    if ($L < scalar @{$listref} && $listref->[$L])
      {print "present at run level $L\n";}




Service Methods

new($service_name,$path) path is optional, and may refer either to the folder containing the relevant control script, or may refer to the control script itself.

start will start the service (if not already started). The scope of this action is system-wide. Example: $Obj->start();

stop will stop the service (if running). The scope of this action is system-wide. Example: $Obj->stop();

restart will restart the service, effectively stopping it (if it is running) and then starting it. The scope of this action is system-wide. Example: $Obj->restart();

status The scope of this action is limited to a single session.

reload The scope of this action is system-wide.

Caveats

Top

This can not create a new service start/kill script. At best this can only modify an existing one, or link it into the init folders.

BUGS

Top

Requires /sbin/chkconfig to work.

Author

Top

Randall Maas (randym@acm.org)


CfgTie documentation Contained in the CfgTie distribution.

#Copyright 1998-2001, Randall Maas.  All rights reserved.  This program is free
#software; you can redistribute it and/or modify it under the same terms as
#PERL itself.


package CfgTie::TieRCServ_rec;
use POSIX;
use CfgTie::filever;
use Secure::File;
my %ServScript;
1;

package CfgTie::TieRCService;

# Still need a tiehash, ability to add it to a run level, change its order
# in the runlevel, remove it from the runlevel

#All of the service name to information is stored in

sub scan ($)
{
   my $T= shift;
   my $F =CfgTie::filever::open("/sbin/chkconfig --list|");
   return unless $F;
   while (<$F>)
    {
	#We do this in the most flexible way we can think of... to allow for
	# future changes to chkconfig

	#Split the line up into its parts
	my ($Serv,@R) = split /\s+/;
	if (!$Serv) {next;}
	my @B=(0,0,0,0,0,0,0,0,0,0);
	
	#Scan each part into an explicit run-level (used as an index) and the
	#off/on which will be implicitly ignored 
	foreach my $I (@R)
        {if ($I=~/^(\d+):on/i) {$B[$1]=1;}}
	
	#Store
	tie %{$T->{$Serv}}, 'CfgTie::TieRCServ_rec', $Serv, \@B;
    }
   $F->close;
}

sub TIEHASH
{
   my $self=shift;
   my $Node={};
   #Currently requires chkconfig to work
   if (!-e "/sbin/chkconfig") {return;}
   return bless $Node, $self;
}

sub DELETE
{
   my $self=shift;
   my $key=shift;
   if (!exists $Services{$key}) {return;}
   CfgTie::Cfgfile::system("/sbin/chkconfig --del $key");
}

sub add
{
   my $self=shift;
   my $key =shift;
   CfgTie::filever::system("/sbin/chkconfig --add $key");
   scan(\%Services);
}

sub EXISTS ($$)
{
   my ($self,$key)=@_;
   return exists $Services{$key};
}

sub FETCH
{
   my $self=shift;
   my $key = shift;
   return $Services{$key};
}

sub trx {CfgTie::Cfgfile::trx(@_);}
sub table {CfgTie::Cfgfile::table(@_);}
sub num2chk($)
{
   my @Ret;
   foreach my $I (@{$_[0]})
    {
       if ($I) 
         {push @Ret, "<font face=\"Wingdings\">x</font>";}
        else
         {push @Ret, "<font face=\"Wingdings\"> </font>";}
    }
}

sub HTML ($)
{
    my $A="";
    foreach my $I (sort keys %Services)
     {$A.= trx("<a href=\"runlevel/$I\">$I</a>",@{$Services{$I}->{levels}});}

   table("Run levels",
   "<tr><th>Service</th><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th>".
   "<th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr>\n$A",11);
}

package CfgTie::TieRCServ_rec;

sub proc_sig($$)
{
   my $pid = proc_id($_[0]);

   if (defined $pid)
     {
        kill $_[1], $pid;
        return 1;
     }
    return -1;
}

sub scan_for_script($$)
{
   my ($serv, $path) = @_;
   $FNum++;
   opendir FNum, $path;
   my @RCfiles = grep {/^$serv$/} (readdir(FNum));
   closedir FNum;
   if (!scalar @RCfiles) {return;}
   my ($R) = @RCfiles;
   $R;
}

sub scan_rcscript($)
{
   my $self=shift;
   my $F = new Secure::File "<".$self->{'path'};
   while (<$F>)
    {
       if (/^\s*\#\s*chkconfig\s*:\s*(\d+)/i) 
         {
	    my @A=(0,0,0,0,0,0,0,0,0,0);
            for (my $I=0; $I< length $1; $I++)
             {$A[substr($1,$I,1)]=1;}
	    $self->{defaults}=\@A;
         }
       elsif (/^\s*\#\s*description\s*:\s*([^\s].*)$/i)
         {
	    my $A=$1;
	    #read the continuation in as neccessary
	    while ($A=~/\\\s*$/)
	     {
		$A=~s/\s*\\\s*$//;
	        my $B =<$F>;
	        if ($B=~/#\s*([^\s].*)$/i) {$A.=' '.$1;}
	     }
	    $self->{description}=$A;
         }
    }
   $F->close();
}

sub TIEHASH
{
   my ($self,$name,$levels)=@_;
   my $Node =
 	{
	    levels=>$levels,'name'=>$name,
	};

   return bless $Node, $self;
}

sub proc_id($)
{
   my $key = shift;

   if (!defined $key || !-e "/var/run/$key.pid") {return undef;}

   #use the canonically process ID
   my $F = new Secure::File "</var/run/$key.pid" or return undef;
   my ($I,$pid);
   while (<$F>)
    {
       if (/^(\d+)$/) {$pid=$1;}
    }
   $F->close;
   return $pid;
}

sub _stat($)
{
   my $self=shift;
   if (!defined $self) {return undef;}
   return stat("/var/run/".$self->{'name'}.".pid");
}

sub EXISTS ($$)
{
   my ($self,$key)=@_;
   my $lkey=lc($key);
   if (exists $self->{$lkey}) {return 1;}
   if ($lkey eq 'pid')
     {
        my $pid = proc_id($self->{'name'});
        if (defined $pid)
	  {
	      $self->{pid} =$pid;
	      return 1;
          }
     }
   elsif ($lkey eq 'path')
    {
       my $Path="/etc/rc.d/init.d/";
       my $A;
       if (-e $Path) {$A = scan_for_script($self->{'name'},$Path);}
       if (!defined $A)
	 {
	    $Path="/etc/init.d/";
            if (-e $Path) {$A = scan_for_script($self->{'name'},$Path);}
            if (!defined $A) {return 0;}
         }
       $self->{path}=$Path.'/'.$A;
       return 1;
    }
   elsif ($lkey eq 'description' || $lkey eq 'defaults')
    {
       if (!exists $self->{path}) {EXISTS($self,'path');}
       if (!exists $self->{path}) {return 0;}
       scan_rcscript($self);
       return exists $self->{$lkey};
    }
   elsif ($lkey eq 'stat')
     {
        my @A= _stat($self);
        if (scalar @A) {$self->{'stat'}=[@A]; return 1;}
     }
   return 0;
}

sub FIRSTKEY
{
    EXISTS($_[0],'pid');
   my $a = keys %{$_[0]};
   return scalar each %{$_[0]};
}

sub NEXTKEY
{return scalar each %{$_[0]};}

sub FETCH
{
   my ($self,$key)=@_;
   my $lkey =lc($key);
   if (EXISTS($self,$lkey)) {return $self->{$lkey};}
   undef;
}

sub trx   {CfgTie::Cfgfile::trx(@_);}
sub table {CfgTie::Cfgfile::table(@_);}
sub HTML ($)
{
   my $self=shift;
   #Levels it currently is in
   #Description
    EXISTS($self, 'description');
   #Defaults
    EXISTS($self, 'defaults');
   #Name
   #Process Id
    EXISTS($self, 'pid');
    EXISTS($self, 'stat');

   #Set up the current run levels and their default settings:
   my $A= table(undef,
		trx('Levels:',@{$self->{levels}}).
		trx('Defaults:', @{$self->{defaults}}),
		10);

   table("Run levels",
   "<tr><th>Service</th><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th>".
   "<th>5</th><th>6</th><th>7</th><th>8</th></tr>\n$A",10);

   my $B='';
   if (exists $self->{'pid'}) {$B.= trx("Process Id:", $self->{'pid'});}
   if (exists $self->{'stat'})
     {
	$B.= trx("Started on:",
		POSIX::strftime("%a %b %e %H:%M:%S %Y",
				localtime($self->{'stat'}->[9])));
     }
   
   table($self->{'name'},
	 trx("Name:",       $self->{'name'}).
	 trx("Description:",$self->{'description'}).
	 trx("Run Levels",  $A). #Insert both tables there 
	 $B
	 );
}

sub STORE ($$)
{
   my ($self,$key,$value)=@_;

   if (lc($key) ne 'level') {return;}

   #Identify the run levels that were enabled
   my $A;
   my $J=0;
   foreach my $I (@{$value})
   {
      if ($I == 0 || $self->{levels}->[$J] != 0 || $J >9) {$J++; next;}
      $A.=$J;
      $J++;
   }

   #Identify the run levels that were disabled
   my $B;
   $J=0;
   foreach my $I (@{$value})
   {
      if ($I != 0 || $self->{levels}->[$J] == 0|| $J >9) {$J++; next;}
      $B.=$J;
      $J++;
   }

   #Now commit theses.. First add it to some levels.
   if (defined $A && $A && length $A)
   {
     CfgTie::Cfgfile::system("/sbin/chkconfig --level $A ".$self->{'name'}.
			     " on");
   }

   #Next remove it from some levels.
   if (defined $B && $B && length $B)
   {
     CfgTie::Cfgfile::system("/sbin/chkconfig --level $B ".$self->{'name'}.
			     " off");
   }
}

sub newt
{
   my ($self,$serv,$path) = @_;

   if (!defined $serv) {return bless $node, $self;}
   if (defined $path)
     {
        #if the path is bad, just let it be there, but invalid..
        #Or if it points to a real file...
        if (!-e $path || !-d $path)
          {
             #The user specified a file; take the user at his word
             $node->{Path}=$path;
             return bless $node, $self;
          }

        if (-e "$path/$serv")
          {
             $node->{Path}="$path/$serv";
             return bless $node, $self;
          }

        if (-e "$path$serv")
          {
             $node->{Path}="$path$serv";
             return bless $node, $self;
          }

        my $R = scan_for_script $serv, $path;
        if (defined $R) {$node->{Path}=$R;}

        return bless $node, $self;
     }

   if (!exists $ServScript{$serv})
     {
        if (-e "/etc/rc.d/init.d/$serv")
          {$ServScript{$serv} = "/etc/rc.d/init.d/$serv";}
         else
          {
             my $RCBase;
                if (-d "/etc/rc2.d/")
                  {$RCBase = "/etc/rc2.d/";}
             elsif (-d "/etc/rc.d/rc2.d")
                  {$RCBase = "/etc/rc.d/rc2.d/";}
             else {return bless $node, $self;}

             my $R = scan_for_script $serv, $RCBase;
             if ($R) {$ServScript{$serv} = $R;}
          }
      }

   if (exists $ServScript{$serv}) {$node->{Path} = $ServScript{$serv};}
   return bless $node,$self;
}

BEGIN
{
   #basically create the various verbs... 
   my $X="package CfgTie::TieRCServ_rec;\n";

   foreach my $I ('start','stop','restart', 'status')
    {
       $X.= "sub $I(\$)\n{\n".
            "   my \$self = shift;\n".
	    "   delete \$self->{pid};\n".
	    "   delete \$self->{'stat'};\n".
            "   if (exists \$self->{path})\n".
            "     {return system(\$self->{path}.\" $I\");}\n".
            "}\n\n";
    }
   eval $X;
}

sub reload($)
{
   my $self=shift;
   delete $self->{pid};
   delete $self->{status};
   if (-x "/usr/sbin/".$self->{'name'}.".reload")
     {
        return system("/usr/sbin/".$self->{'name'}.".reload");
     }
    else
     {
        #Get its pid
        if (proc_sig($self->{'name'}, 'HUP') == -1)
          {
             restart $self;
          }
     }
}

package CfgTie::TieRCService;
use CfgTie::filever;
use Secure::File;
my %Services;
scan (\%Services);
1;