#-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-

# Functions for manipulating system services, like daemons and network.
#
# Copyright (C) 2002 Ximian, Inc.
#
# Authors: Carlos Garnacho Parro <garparr@teleline.es>,
#          Hans Petter Jansson <hpj@ximian.com>,
#          Arturo Espinosa <arturo@ximian.com>,
#          Milan Bouchet-Valat <nalimilan@club.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published
# by the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library 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.

package Init::Services;

my $SERVICE_START = 0;
my $SERVICE_STOP  = 1;

use Init::ServicesList;
use Utils::Report;

sub get_runlevels
{
  my (%dist_map, %runlevels);
  my ($desc, $distro);

  %dist_map =
    (
     "redhat-6.2"       => "redhat-6.2",
     "redhat-7.0"       => "redhat-6.2",
     "redhat-7.1"       => "redhat-6.2",
     "redhat-7.2"       => "redhat-6.2",
     "redhat-7.3"       => "redhat-6.2",
     "redhat-8.0"       => "redhat-6.2",
     "mandrake-9.0"     => "redhat-6.2",
     "conectiva-9"      => "redhat-6.2",
     "debian"           => "redhat-6.2",
     "suse-9.0"         => "redhat-6.2",
     "pld-1.0"          => "redhat-6.2",
     "vine-3.0"         => "redhat-6.2",
     "slackware-9.1.0"  => "freebsd-5",
     "gentoo"           => "gentoo",
     "archlinux"        => "freebsd-5",
     "freebsd-5"        => "freebsd-5",
     "solaris-2.11"     => "freebsd-5",
    );

  %runlevels=
    (
     "redhat-6.2"      => [ "0", "1", "2", "3", "4", "5", "6" ],
     "gentoo"          => &get_gentoo_runlevels (),
     "freebsd-5"       => [ "default" ],
    );

  $distro = $dist_map{$Utils::Backend::tool{"platform"}};
  $desc = $runlevels{$distro};

  return $desc;
}

# This function gets the runlevel that is in use
sub get_sysv_default_runlevel
{
	my (@arr);
	@arr = split / /, `/sbin/runlevel` ;
  chomp $arr[1];

	return $arr[1];
}

sub get_default_runlevel
{
  my $type = &get_init_type ();

  return "default" if ($type eq "gentoo" || $type eq "rcng" || $type eq "bsd" || $type eq "smf");
  return &get_sysv_default_runlevel ();
}

# Upstart support
# TODO: Handle Upstart jobs, and not only traditional SystemV scripts inside an upstart system
sub get_upstart_paths
{
  my %dist_map =
    (
     # gst_dist => [rc.X dirs location, init.d scripts location, relative path, upstart init jobs location]
     "debian"   => ["$gst_prefix/etc",   "$gst_prefix/etc/init.d",   "../init.d",   "$gst_prefix/etc/init"],
     );
  my $res;

  $res = $dist_map{$Utils::Backend::tool{"platform"}};
  &Utils::Report::do_report ("service_upstart_unsupported", $Utils::Backend::tool{"platform"}) if ($res eq undef);
  return @$res;
}

# we are going to extract the name of the script
sub get_upstart_service_name
{
	my ($service) = @_;

	$service =~ s/$initd_path\///;

	return $service;
}

# This function gets the state of the service along the runlevels,
# it also returns the average priority
sub get_upstart_runlevels_status
{
	my ($service) = @_;
	my ($link);
	my ($runlevel, $action, $priority);
	my (@arr, @ret);

	foreach $link (<$rcd_path/rc[0-6].d/[SK][0-9][0-9]$service>)
	{
		$link =~ s/$rcd_path\///;
		$link =~ /rc([0-6])\.d\/([SK])([0-9][0-9]).*/;
		($runlevel,$action,$priority)=($1,$2,$3);

                if ($action eq "S")
		{
                        push @arr, [ $runlevel, $SERVICE_START, $priority ];
                }
		elsif ($action eq "K")
		{
                        push @arr, [ $runlevel, $SERVICE_STOP, $priority ];
		}
	}

	return \@arr;
}

# We are going to extract the information of the service
sub get_upstart_service_info
{
	my ($service) = @_;
	my ($script, @actions, @runlevels, $role);

	# Return if it's a directory
	return if (-d $service);

	# We have to check if the service is executable
	return unless (-x $service);

	$script = &get_upstart_service_name ($service);
	$runlevels = &get_upstart_runlevels_status($script);

  return ($script, $runlevels);
}

# This function gets an ordered array of the available services from a upstart system
sub get_upstart_services
{
	my ($service);
	my (@arr);

	($rcd_path, $initd_path, $relative_path, $init_path) = &get_upstart_paths ();
        return undef unless ($rcd_path && $initd_path && $init_path);

	foreach $service (<$initd_path/*>)
	{
		my (@info, $script);

		@info = &get_upstart_service_info ($service);
		# Only manage traditional init.d scripts, ignore services with jobs installed
		$script = $info[0];

		if (!&Init::ServicesList::is_forbidden ($script)
		    && !-e "$init_path/$script.conf")
		{
                        push @arr, \@info  if (scalar (@info));
                }
	}

	return \@arr;
}

# These are the functions for storing the service settings in upstart
sub remove_upstart_link
{
  my ($rcd_path, $runlevel, $script) = @_;

  foreach $link (<$rcd_path/rc$runlevel.d/[SK][0-9][0-9]$script>)
  {
    &Utils::Report::enter ();
    &Utils::Report::do_report ("service_upstart_remove_link", "$link");
    unlink ($link);
    &Utils::Report::leave ();
  }
}

sub add_upstart_link
{
  my ($rcd_path, $relative_path, $runlevel, $action, $priority, $service) = @_;
  my ($prio) = sprintf ("%0.2d",$priority);

  symlink ("$relative_path/$service", "$rcd_path/rc$runlevel.d/$action$prio$service");

  &Utils::Report::enter ();
  &Utils::Report::do_report ("service_upstart_add_link", "$rcd_path/rc$runlevel.d/$action$prio$service");
  &Utils::Report::leave ();
}

sub run_upstart_initd_script
{
  my ($service, $arg) = @_;
  my ($rc_path, $initd_path);
  my $str;

  &Utils::Report::enter ();

  if (&Utils::File::run ("service", $service, $arg) == 0)
  {
      &Utils::Report::do_report ("service_upstart_op_success", $service, $arg);
      &Utils::Report::leave ();
      return 0;
  }

  &Utils::Report::do_report ("service_upstart_op_failed", $service, $arg);
  &Utils::Report::leave ();
  return -1;
}

sub set_upstart_service
{
  my ($service) = @_;
  my ($script, $priority, $runlevels, $default_runlevel);
  my ($runlevel, $action, %configured_runlevels);

  ($rcd_path, $initd_path, $relative_path) = &get_upstart_paths ();
  return unless ($rcd_path && $initd_path && $relative_path);

  $script = $$service[0];
  $runlevels = $$service[1];
  $default_runlevel = &get_default_runlevel ();

  foreach $r (@$runlevels)
  {
    $runlevel = $$r[0];
    $action   = ($$r[1] == $SERVICE_START) ? "S" : "K";
    $priority = sprintf ("%0.2d", $$r[2]);
    $priority = "50" if ($$r[2] <= 0);

    $configured_runlevels{$runlevel} = 1;

    if (!-f "$rcd_path/rc$runlevel.d/$action$priority$script")
    {
      &remove_upstart_link ($rcd_path, $runlevel, $script);
      &add_upstart_link ($rcd_path, $relative_path, $runlevel, $action, $priority, $script);

      if ($runlevel eq $default_runlevel)
      {
        &run_upstart_initd_script ($script, ($$r[1] == $SERVICE_START) ? "start" : "stop");
      }
    }
  }

  # remove unneeded links
  foreach $link (<$rcd_path/rc[0-6].d/[SK][0-9][0-9]$script>)
	{
    $link =~ /rc([0-6])\.d/;
    $runlevel = $1;

    if (!exists $configured_runlevels{$runlevel})
    {
      &remove_upstart_link ($rcd_path, $runlevel, $script);

      if ($runlevel eq $default_runlevel)
      {
        &run_upstart_initd_script ($script, "stop");
      }
    }
  }
}

sub set_upstart_services
{
	my ($services) = @_;

	foreach $i (@$services)
	{
		&set_upstart_service($i);
	}
}

# This function gets the runlevel that is in use
sub get_sysv_default_runlevel
{
	my (@arr);
	@arr = split / /, `/sbin/runlevel` ;
  chomp $arr[1];

	return $arr[1];
}

sub get_default_runlevel
{
  my $type = &get_init_type ();

  return "default" if ($type eq "gentoo" || $type eq "rcng" || $type eq "bsd" || $type eq "smf");
  return &get_sysv_default_runlevel ();
}

sub get_sysv_paths
{
  my %dist_map =
    (
     # gst_dist => [rc.X dirs location, init.d scripts location, relative path location]
     "redhat-6.2"     => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "redhat-7.0"     => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "redhat-7.1"     => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "redhat-7.2"     => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "redhat-7.3"     => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "redhat-8.0"     => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "mandrake-9.0"   => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "yoper-2.2"      => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "conectiva-9"    => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "debian"         => ["$gst_prefix/etc",      "$gst_prefix/etc/init.d",      "../init.d"],
     "suse-9.0"       => ["$gst_prefix/etc/init.d", "$gst_prefix/etc/init.d",    "../"],
     "pld-1.0"        => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "vine-3.0"       => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "ark"            => ["$gst_prefix/etc/rc.d", "$gst_prefix/etc/rc.d/init.d", "../init.d"],
     "solaris-2.11"   => ["$gst_prefix/etc",      "$gst_prefix/etc/init.d",      "../init.d"],
     );
  my $res;

  $res = $dist_map{$Utils::Backend::tool{"platform"}};
  &Utils::Report::do_report ("service_sysv_unsupported", $Utils::Backend::tool{"platform"}) if ($res eq undef);
  return @$res;
}

# we are going to extract the name of the script
sub get_sysv_service_name
{
	my ($service) = @_;
	
	$service =~ s/$initd_path\///;
  
	return $service;
}

# This function gets the state of the service along the runlevels,
# it also returns the average priority
sub get_sysv_runlevels_status
{
	my ($service) = @_;
	my ($link);
	my ($runlevel, $action, $priority);
	my (@arr, @ret);
	
	foreach $link (<$rcd_path/rc[0-6].d/[SK][0-9][0-9]$service>)
	{
		$link =~ s/$rcd_path\///;
		$link =~ /rc([0-6])\.d\/([SK])([0-9][0-9]).*/;
		($runlevel,$action,$priority)=($1,$2,$3);

    if ($action eq "S")
		{
      push @arr, [ $runlevel, $SERVICE_START, $priority ];
    }
		elsif ($action eq "K")
		{
      push @arr, [ $runlevel, $SERVICE_STOP, $priority ];
		}
	}
	
	return \@arr;
}

# We are going to extract the information of the service
sub get_sysv_service_info
{
	my ($service) = @_;
	my ($script, @actions, @runlevels, $role);

	# Return if it's a directory
	return if (-d $service);
	
	# We have to check if the service is executable	
	return unless (-x $service);

	$script = &get_sysv_service_name ($service);
	$runlevels = &get_sysv_runlevels_status($script);

  return ($script, $runlevels);
}

# This function gets an ordered array of the available services from a SysV system
sub get_sysv_services
{
	my ($service);
	my (@arr);

	($rcd_path, $initd_path) = &get_sysv_paths ();
  return undef unless ($rcd_path && $initd_path);

	foreach $service (<$initd_path/*>)
	{
		my (@info);

		@info = &get_sysv_service_info ($service);
		push @arr, \@info  if (scalar (@info) && !&Init::ServicesList::is_forbidden ($info[0]));
	}

	return \@arr;
}

# These are the functions for storing the service settings in SysV
sub remove_sysv_link
{
  my ($rcd_path, $runlevel, $script) = @_;
	
  foreach $link (<$rcd_path/rc$runlevel.d/[SK][0-9][0-9]$script>)
  {
    &Utils::Report::enter ();
    &Utils::Report::do_report ("service_sysv_remove_link", "$link");
    unlink ($link);
    &Utils::Report::leave ();
  }
}

sub add_sysv_link
{
  my ($rcd_path, $relative_path, $runlevel, $action, $priority, $service) = @_;
  my ($prio) = sprintf ("%0.2d",$priority);

  symlink ("$relative_path/$service", "$rcd_path/rc$runlevel.d/$action$prio$service");

  &Utils::Report::enter ();
  &Utils::Report::do_report ("service_sysv_add_link", "$rcd_path/rc$runlevel.d/$action$prio$service");
  &Utils::Report::leave ();
}

sub run_sysv_initd_script
{
  my ($service, $arg) = @_;
  my ($rc_path, $initd_path);
  my $str;

  &Utils::Report::enter ();
  
  ($rcd_path, $initd_path) = &get_sysv_paths ();
  return -1 unless ($rcd_path && $initd_path);

  if (-f "$initd_path/$service")
  {
    if (&Utils::File::run ("$initd_path/$service", $arg) == 0)
    {
      &Utils::Report::do_report ("service_sysv_op_success", $service, $arg);
      &Utils::Report::leave ();
      return 0;
    }
  }
  
  &Utils::Report::do_report ("service_sysv_op_failed", $service, $arg);
  &Utils::Report::leave ();
  return -1;
}

sub set_sysv_service
{
  my ($service) = @_;
  my ($script, $priority, $runlevels, $default_runlevel);
  my ($runlevel, $action, %configured_runlevels);

  ($rcd_path, $initd_path, $relative_path) = &get_sysv_paths ();
  return unless ($rcd_path && $initd_path && $relative_path);

  $script = $$service[0];
  $runlevels = $$service[1];
  $default_runlevel = &get_sysv_default_runlevel ();

  foreach $r (@$runlevels)
  {
    $runlevel = $$r[0];
    $action   = ($$r[1] == $SERVICE_START) ? "S" : "K";
    $priority = sprintf ("%0.2d", $$r[2]);
    $priority = "50" if ($$r[2] <= 0);

    $configured_runlevels{$runlevel} = 1;

    if (!-f "$rcd_path/rc$runlevel.d/$action$priority$script")
    {
      &remove_sysv_link ($rcd_path, $runlevel, $script);
      &add_sysv_link ($rcd_path, $relative_path, $runlevel, $action, $priority, $script);

      if ($runlevel eq $default_runlevel)
      {
        &run_sysv_initd_script ($script, ($$r[1] == $SERVICE_START) ? "start" : "stop");
      }
    }
  }

  # remove unneeded links
  foreach $link (<$rcd_path/rc[0-6].d/[SK][0-9][0-9]$script>)
	{
    $link =~ /rc([0-6])\.d/;
    $runlevel = $1;

    if (!exists $configured_runlevels{$runlevel})
    {
      &remove_sysv_link ($rcd_path, $runlevel, $script);

      if ($runlevel eq $default_runlevel)
      {
        &run_sysv_initd_script ($script, "stop");
      }
    }
  }
}

sub set_sysv_services
{
	my ($services) = @_;

	foreach $i (@$services)
	{
		&set_sysv_service($i);
	}
}

# This functions get an ordered array of the available services from a file-rc system
sub get_filerc_runlevels_status
{
  my ($start_service, $stop_service, $priority) = @_;
  my (@arr);

  # we start with the runlevels in which the service starts
  if ($start_service !~ /-/) {
    my (@runlevels);

    @runlevels = split /,/, $start_service;

    foreach $runlevel (@runlevels)
    {
      push @arr, [ $runlevel, $SERVICE_START, $priority ];
    }
  }

  # now let's go with the runlevels in which the service stops
  if ($stop_service !~ /-/) {
    my (@runlevels);

    @runlevels = split /,/, $stop_service;

    foreach $runlevel (@runlevels)
    {
      push @arr, [ $runlevel, $SERVICE_STOP, $priority ];
    }
  }

  return \@arr;
}

sub get_filerc_service_info
{
  my ($line, %ret) = @_;
  my (@runlevels, $role);

  if ($line =~ /^([0-9][0-9])[\t ]+([0-9\-S,]+)[\t ]+([0-9\-S,]+)[\t ]+\/etc\/init\.d\/(.*)/)
  {
    $priority = $1;
    $stop_service = $2;
    $start_service = $3;
    $script = $4;

    $runlevels = &get_filerc_runlevels_status ($start_service, $stop_service, $priority);

    return ($script, $runlevels);
  }

  return;
}

sub get_filerc_services
{
	my ($script);
  my ($script, @arr, %hash);

  open FILE, "$gst_prefix/etc/runlevel.conf" or return undef;
  while ($line = <FILE>)
  {
    next if ($line =~ /^\#.*/);

    my (@info);
    my ($start_service, $stop_service);

    @info = &get_filerc_service_info ($line);
    next if (!scalar (@info));

    $script = $info[0];

    if (!$hash{$script})
    {
      $hash{$script} = \@info;
    }
    else
    {
      # We need to mix the runlevels
      foreach $runlevel (@{$info[2]})
      {
        push @{$hash{$script}[2]}, $runlevel;
      }
    }
  }

  foreach $key (keys %hash)
  {
    push @arr, $hash{$key};
  }

  return \@arr;
}

# These are the functions for storing the service settings in file-rc
sub concat_filerc_runlevels
{
  my (@runlevels) = @_;

  $str = join (",", sort (@runlevels));
  return ($str) ? $str : "-";
}

sub set_filerc_service
{
  my ($buff, $initd_path, $service) = @_;
  my (%hash, $priority, $line, $str);
  my ($script, $default_runlevel, %configured_runlevels);

  $script = $$service[0];
  $runlevels = $$service[1];
  $default_runlevel = &get_sysv_default_runlevel ();

  foreach $i (@$runlevels)
  {
    $priority = 0 + $$i[2];
    $priority = 50 if ($priority == 0); #very rough guess
    $configured_runlevels {$$i[0]} = 1;

    if ($$i[1] == $SERVICE_START)
    {
      $hash{$priority}{$SERVICE_START} = [] if (!$hash{$priority}{$SERVICE_START});
      push @{$hash{$priority}{$SERVICE_START}}, $$i[0];
    }
    else
    {
      $hash{$priority}{$SERVICE_STOP} = [] if (!$hash{$priority}{$SERVICE_STOP});
      push @{$hash{$priority}{$SERVICE_STOP}}, $$i[0];
    }

    if ($$i[0] eq $default_runlevel)
    {
      &run_sysv_initd_script ($script, ($$i[1] == $SERVICE_START) ? "start" : "stop");
    }
  }

  foreach $priority (keys %hash)
  {
    $line  = sprintf ("%0.2d", $priority) . "\t";
    $line .= &concat_filerc_runlevels (@{$hash{$priority}{$SERVICE_STOP}}) . "\t";
    $line .= &concat_filerc_runlevels (@{$hash{$priority}{$SERVICE_START}}) . "\t";
    $line .= $initd_path . "/" . $script . "\n";

    push @$buff, $line;
  }

  # stop the service if it's not configured
  if (!$configured_runlevels {$default_runlevel})
  {
    &run_sysv_initd_script ($script, "stop");
  }
}

sub set_filerc_services
{
  my ($services) = @_;
  my ($buff, $lineno, $line, $file);
  my ($rcd_path, $initd_path, $relative_path) = &get_sysv_paths ();
  return unless ($rcd_path && $initd_path && $relative_path);

  $file = "/etc/runlevel.conf";

  $buff = &Utils::File::load_buffer ($file);
  &Utils::File::join_buffer_lines ($buff);

  $lineno = 0;

  # We prepare the file for storing the configuration, save the initial comments
  # and delete the rest
  while ($$buff[$lineno] =~ /^#.*/)
  {
    $lineno++;
  }

  for ($i = $lineno; $i < scalar (@$buff); $i++)
  {
    $$buff[$i] =~ /.*\/etc\/init\.d\/(.*)/;

    # we need to keep the forbidden services and the services that only start in rcS.d
    # FIXME: need to remove this call to is_forbidden
    if (!&Init::ServicesList::is_forbidden ($1))
    {
      delete $$buff[$i];
    }
  }

  # Now we append the services
  foreach $service (@$services)
  {
    &set_filerc_service ($buff, $initd_path, $service);
  }

  @$buff = sort @$buff;

  push @$buff, "\n";
  &Utils::File::clean_buffer ($buff);
  &Utils::File::save_buffer ($buff, $file);
}

# this functions get a list of the services that run on a bsd init
sub get_bsd_scripts_list
{
  my ($files) = [ "rc.M", "rc.inet2", "rc.4" ];
  my ($file, $i, %scripts);
  my ($service, $name);

  foreach $i (@$files)
  {
    $file = "/etc/rc.d/" . $i;
    $fd = &Utils::File::open_read_from_names ($file);

    if (!$fd) {
      &Utils::Report::do_report ("rc_file_read_failed", $file);
      next;
    }

    while (<$fd>)
    {
      $line = $_;

      if ($line =~ /^if[ \t]+\[[ \t]+\-x[ \t]([0-9a-zA-Z\/\.\-_]+) .*\]/)
      {
        $service = $1;
        $name = $service;
        $name =~ s/^.*\///;
        $name =~ s/^rc\.//;

        $scripts{$name} = $service;
      }
    }

    &Utils::File::close_file ($fd);
  }

  return \%scripts;
}

sub get_bsd_service_status
{
  my ($service) = @_;
  return (-x $service) ? $SERVICE_START : $SERVICE_STOP;
}

sub get_bsd_service_info
{
  my ($service, $name) = @_;
  my (@runlevels, $status);

  return if (! Utils::File::exists ($service));

  $status = &get_bsd_service_status ($service);
  push @runlevels, [ "default", $status, 0 ];

  return ($name, \@runlevels);
}

sub get_bsd_services
{
  my (@arr, %scripts, $name);

  $scripts = &get_bsd_scripts_list ();

  foreach $name (keys %$scripts)
  {
    my (@info);

    @info = &get_bsd_service_info ($$scripts{$name}, $name);
    push @arr, \@info if (scalar (@info) && !&Init::ServicesList::is_forbidden ($info[0]));
  }

  return \@arr;
}

sub run_bsd_script
{
  my ($service, $arg) = @_;
  my ($chmod) = 0;

  return if (!&Utils::File::exists ($service));

  # if it's not executable then chmod it
  if (! -x $service)
  {
    $chmod = 1;
    &Utils::File::run ("chmod", "ugo+x", $service);
  }

  &Utils::File::run_bg ($service, $arg);

  # return it to it's normal state
  if ($chmod)
  {
    &Utils::File::run ("chmod", "ugo-x", $service);
  }
}

sub set_bsd_service
{
  my ($service) = @_;
  my ($script, $runlevels, $status, %scripts);

  $scripts = &get_bsd_scripts_list ();
  $script = $$scripts{$$service[0]};
  $runlevels = $$service[1];
  $runlevel  = $$runlevels[0];

  next if ($script eq undef);

  $status = $$runlevel[1];
  $status = $SERVICE_STOP if ($status eq undef);

  next if ($status == &get_bsd_service_status ($script));

  if ($status == $SERVICE_START)
  {
    &Utils::File::run ("chmod", "ugo+x", $script);
    &run_bsd_script ($script, "start");
  }
  else
  {
    &run_bsd_script ($script, "stop");
    &Utils::File::run ("chmod", "ugo-x", $script);
  }
}

# This function stores the configuration in a bsd init
sub set_bsd_services
{
  my ($services) = @_;

  foreach $service (@$services)
  {
    &set_bsd_service ($service);
  }
}

# these functions get a list of the services that run on a gentoo init
sub get_gentoo_service_status
{
  my ($script, $runlevel) = @_;
  my ($services) = &get_gentoo_services_for_runlevel ($runlevel);

  return ($$services {$script});
}

sub get_gentoo_runlevels
{
  my($raw_output) = Utils::File::run_backtick("rc-status --nocolor -l");
  my(@runlevels);

  return undef if (!$raw_output);
  @runlevels = split(/\n/,$raw_output);

  return \@runlevels;
}

sub get_gentoo_services_for_runlevel
{
  my($runlevel) = @_;
  my($raw_output) = Utils::File::run_backtick("rc-status --nocolor $runlevel");
  my(@raw_lines) = split(/\n/,$raw_output);
  my($line, $service);
  my(%services);

  foreach $line (@raw_lines)
  {
    if ($line !~ /^Runlevel/)
    {
      $service = (split(" ",$line))[0];
      $services{$service} = 1;
	  }
  }

  return \%services
}

sub get_gentoo_runlevels_services
{
  my (%runlevels_services, $runlevels);

  $runlevels = &get_gentoo_runlevels ();
  return undef if (!$runlevels);

  foreach $runlevel (@$runlevels)
  {
    $runlevels_services{$runlevel} = &get_gentoo_services_for_runlevel ($runlevel);
  }

  return \%runlevels_services;
}

sub get_gentoo_services_list
{
  my ($service, @services);

  foreach $service (<$gst_prefix/etc/init.d/*>)
  {
    if (-x $service)
    {
      $service =~ s/.*\///;
      push @services, $service;
    }
  }

  return \@services;
}

sub gentoo_service_exists
{
  my($service) = @_;
  my($services) = &get_gentoo_services_list();

  foreach $i (@$services)
  {
    return 1 if ($i =~ /$service/);
  }

  return 0;
}

sub get_gentoo_runlevels_status
{
  my ($service, $runlevels_services) = @_;
  my (@arr, $services_in_runlevel);

  foreach $runlevel (keys %$runlevels_services)
  {
    $services_in_runlevel = $$runlevels_services {$runlevel};

    if ($$services_in_runlevel{$service})
    {
      push @arr, [ $runlevel, $SERVICE_START, 0 ];
    }
    else
    {
      push @arr, [ $runlevel, $SERVICE_STOP, 0 ];
    }
  }

  return \@arr;
}

sub get_gentoo_service_info
{
  my ($service, $runlevels_services) = @_;
  my (@runlevels_info);

  $runlevels_info = &get_gentoo_runlevels_status ($service, $runlevels_services);

  return ($service, $runlevels_info);
}

sub get_gentoo_services
{
  my ($service, @arr);
  my ($service_list) = &get_gentoo_services_list ();
  my ($runlevels_services) = &get_gentoo_runlevels_services ();

  foreach $service (@$service_list)
  {
    my (@info);

    @info = &get_gentoo_service_info ($service, $runlevels_services);
    push @arr, \@info if (scalar (@info) && !&Init::ServicesList::is_forbidden ($info[0]));
  }

  return \@arr;
}

#FIXME: almost equal to the sysv equivalent
sub run_gentoo_script
{
  my ($service, $arg) = @_;

  &Utils::Report::enter ();

  if (&gentoo_service_exists ($service))
  {
    if (!&Utils::File::run ("/etc/init.d/$service", $arg))
    {
      &Utils::Report::do_report ("service_sysv_op_success", $service, $arg);
      &Utils::Report::leave ();
	    return 0;
	  }
  }

  &Utils::Report::do_report ("service_sysv_op_failed", $service, $arg);
  &Utils::Report::leave ();
  return -1;
}

sub set_gentoo_service_status
{
  my ($script, $rl, $status, $runlevels_services) = @_;
  my ($services_in_runlevel, $old_status);

  $services_in_runlevel = $$runlevels_services {$rl};
  $old_status = ($$services_in_runlevel{$script}) ?
      $SERVICE_START : $SERVICE_STOP;

  return if ($status == $old_status);

  if ($status == $SERVICE_START)
  {
    &Utils::File::run ("rc-update", "add", $script, $rl);
    &run_gentoo_script ($script, "start");
  }
  else
  {
    &run_gentoo_script ($script, "stop");
    &Utils::File::run ("rc-update", "del", $script, $rl);
  }
}

sub set_gentoo_service
{
  my ($service) = @_;
  my ($action, $rl, $script, $arr);
  my ($runlevels_services) = &get_gentoo_runlevels_services ();

  return if (!$runlevels_services);

  $script = $$service[0];
  $arr = $$service[1];

  foreach $i (@$arr)
  {
    $action = $$i[1];
    $rl = $$i[0];
    &set_gentoo_service_status ($script, $rl, $action,
                                $runlevels_services);
  }
}

# This function stores the configuration in gentoo init
sub set_gentoo_services
{
  my ($services) = @_;

  foreach $service (@$services)
  {
    &set_gentoo_service ($service);
  }
}

# rcNG functions, mostly for FreeBSD
sub get_rcng_status_by_service
{
  my ($service) = @_;
  my ($fd, $line, $active);

  # This is the only difference between rcNG and archlinux
  if ($Utils::Backend::tool{"platform"} eq "archlinux")
  {
    return &Utils::File::exists ("/var/run/daemons/$service");
  }
  else
  {
    $fd = &Utils::File::run_pipe_read ("/etc/rc.d/$service rcvar");

    while (<$fd>)
    {
      $line = $_;

      if ($line =~ /^\$.*=YES$/)
      {
        $active = 1;
        last;
      }
    }

    &Utils::File::close_file ($fd);
    return $active;
  }
}

sub get_rcng_service_info
{
  my ($script) = @_;
  my (@runlevels);

  if (get_rcng_status_by_service ($script))
  {
    push @runlevels, [ "default", $SERVICE_START, 0 ];
  }
  else
  {
    push @runlevels, [ "default", $SERVICE_STOP, 0 ];
  }

  return ($script, \@runlevels);
}

sub get_rcng_services
{
  my ($service);
  my (@arr);

  foreach $service (<$gst_prefix/etc/rc.d/*>)
  {
    my (@info);

    $service =~ s/.*\///;
    @info = &get_rcng_service_info ($service);
    push @arr, \@info if (scalar (@info) && !&Init::ServicesList::is_forbidden ($info[0]));
  }

  return \@arr;
}

sub run_rcng_script
{
  my ($service, $arg) = @_;

  &Utils::Report::enter ();

  if (!&Utils::File::run ("/etc/rc.d/$service", $arg))
  {
    &Utils::Report::do_report ("service_sysv_op_success", $service, $arg);
    &Utils::Report::leave ();
    return 0;
  }

  &Utils::Report::do_report ("service_sysv_op_failed", $service, $arg);
  &Utils::Report::leave ();
  return -1;
}

# These functions store the configuration of a rcng init
sub set_rcng_service_status
{
  my ($service, $action) = @_;
  my ($fd, $key, $res);
  my ($default_rcconf) = "/etc/defaults/rc.conf";
  my ($rcconf) = "/etc/rc.conf";

  if (&Utils::File::exists ("/etc/rc.d/$service"))
  {
    $fd = &Utils::File::run_pipe_read ("/etc/rc.d/$service rcvar");

    while (<$fd>)
    {
      if (/^\$(.*)=.*$/)
      {
        # to avoid cluttering rc.conf with duplicated data,
        # we first look in the defaults/rc.conf for the key
        $key = $1;
        $res = &Utils::Parse::get_sh_bool ($default_rcconf, $key);

        if ($res == $action)
        {
          &Utils::Replace::set_sh ($rcconf, $key);
        }
        else
        {
          &Utils::Replace::set_sh_bool ($rcconf, $key, "YES", "NO", $action);
        }

        &run_rcng_script ($service, ($action) ? "forcestart" : "forcestop");
      }
    }

    &Utils::File::close_file ($fd);
  }
  elsif (&Utils::File::exists ("/usr/local/etc/rc.d/$service.sh"))
  {
    if ($action)
    {
      &Utils::File::copy_file ("/usr/local/etc/rc.d/$service.sh.sample",
                              "/usr/local/etc/rc.d/$service.sh");
      &run_rcng_script ($service, "forcestart");
    }
    else
    {
      &run_rcng_script ($service, "forcestop");
      Utils::File::remove ("/usr/local/etc/rc.d/$service.sh");
    }
  }
}

sub set_archlinux_service_status
{
  my ($script, $active) = @_;
  my $rcconf = '/etc/rc.conf';
  my ($daemons);

  $daemons = &Utils::Parse::get_sh ($rcconf, "DAEMONS");
  $daemons =~ s/[\(\)]//g;

  # escape these chars
  $script =~ s/([\\\.\^\$\*\+\?\{\}\[\]\(\)\|])/\\\1/g;
  $notscript = "\!" . $script;

  if (($daemons =~ m/$notscript/) && $active)
  {
    # It was disabled, enable it
    $daemons =~ s/$notscript/$script/g;
  }
  elsif (($daemons =~ m/$script/) && !$active)
  {
    # It was enabled, disable it
    $daemons =~ s/$script/$notscript/g;
  }
  elsif (($daemons !~ m/$script/) && $active)
  {
    $daemons .= " ".$script;
  }

  $daemons = "(" . $daemons . ")";
  &Utils::Replace::set_sh ($rcconf, "DAEMONS", $daemons, 1);
  &run_rcng_script ($service, ($active) ? "start" : "stop");
}

sub set_rcng_service
{
  my ($service) = @_;
  my ($action, $runlevels, $script, $func);

  # archlinux stores services differently
  if ($Utils::Backend::tool{"platform"} eq "archlinux")
  {
    $func = \&set_archlinux_service_status;
  }
  else
  {
    $func = \&set_rcng_service_status;
  }

  $script    = $$service[0];
  $runlevels = $$service[1];
  $runlevel  = $$runlevels[0];
  $action    = ($$runlevel[1] == $SERVICE_START)? 1 : 0;

  &$func ($script, $action);
}

sub set_rcng_services
{
  my ($services) = @_;

  foreach $service (@$services)
  {
    &set_rcng_services ($service);
  }
}

# SuSE functions, quite similar to SysV, but not equal...
sub get_suse_service_info ($service)
{
  my ($service) = @_;
  my (@runlevels, $link, $runlevel);

  foreach $link (<$rcd_path/rc[0-9S].d/S[0-9][0-9]$service>)
  {
    $link =~ s/$rcd_path\///;
    $link =~ /rc([0-6])\.d\/S[0-9][0-9].*/;
    $runlevel = $1;

    push @runlevels, [ $runlevel, $SERVICE_START, 0 ];
  }

  foreach $link (<$rcd_path/boot.d/S[0-9][0-9]$service>)
  {
    push @runlevels, [ "B", $SERVICE_START, 0 ];
  }

  return ($service, $runlevels);
}

sub get_suse_services
{
  my ($service, @arr);

  ($rcd_path, $initd_path) = &get_sysv_paths ();
  return undef unless ($rcd_path && $initd_path);

  foreach $service (<$gst_prefix/etc/init.d/*>)
  {
    my (@info);

    next if (-d $service || ! -x $service);

    $service =~ s/.*\///;
    @info = &get_suse_service_info ($service);
    push @arr, \@info  if (scalar (@info) && !&Init::ServicesList::is_forbidden ($info[0]));
  }

  return \@arr;
}

sub set_suse_service
{
  my ($service) = @_;
  my ($action, $runlevels, $script, $rllist);
  my (%configured_runlevelsl);

  $script = $$service[0];
  $runlevels = $$service[1];
  $rllist = "";
  %configured_runlevels = {};

  &Utils::File::run ("insserv", "-r", $script);

  foreach $rl (@$runlevels)
  {
    $configured_runlevels{$$rl[0]} = 1;
     if ($$rl[1] == $SERVICE_START)
    {
      $rllist .= $$rl[0] . ",";
    }
     &run_sysv_initd_script ($script, ($$rl[1] == $SERVICE_START) ? "start" : "stop");
  }

  if ($rllist ne "")
  {
    $rllist =~ s/,$//;
     &Utils::File::run ("insserv", $script, "start=$rllist");
  }

  if (!$configured_runlevels{$default_runlevel})
  {
    &run_sysv_initd_script ($script, $$rl[1]);
  }
}

# This function stores the configuration in suse init
sub set_suse_services
{
  my ($services) = @_;
  my ($default_runlevel);

  $default_runlevel = &get_sysv_default_runlevel ();

  foreach $service (@$services)
  {
    &set_suse_service ($service);
  }
}

# functions to get/set services info in smf
sub smf_service_exists
{
  my($service) = @_;
  my($services) = &get_smf_services_list ();

  foreach $i (@$services)
  {
    return 1 if ($i =~ /$service/);
  }

  return 0;
}

sub run_smf_svcadm
{
  my ($service, $arg) = @_;
  my ($option);

  my %op =
    ("stop" => "disable",
     "start" => "enable"
    );

  if (&smf_service_exists ($service))
  {
    if (!&Utils::File::run ("svcadm", $op{$arg}, $service))
    {
      &Utils::Report::do_report ("service_sysv_op_success", $service, $arg);
      &Utils::Report::leave ();
      return 0;
    }
  }

  &Utils::Report::do_report ("service_sysv_op_failed", $service, $arg);
  &Utils::Report::leave ();
  return -1;
}

sub get_smf_runlevel_status_by_service
{
  my ($service, $status) = @_;
  my (@arr);

  if ($status)
  {
    push @arr, [ "default", $SERVICE_START, 0 ];
  }
  else
  {
    push @arr, [ "default", $SERVICE_STOP, 0 ];
  }

  return \@arr;
}

sub get_smf_service_info
{
  my ($service) = @_;
  my ($fd, @runlevels);
  my $status = 0;

  $fd = &Utils::File::run_pipe_read ("svcs -l $service");

  while (<$fd>)
  {
    $status = 1 if (/^state.*online/);
  }

  &Utils::File::close_file ($fd);

  $runlevels = &get_smf_runlevel_status_by_service ($service, $status);
  $service =~ m/.*\/(.*)$/;

  return ($service, $runlevels);
}

sub get_smf_services_list
{
  my ($fd, @list);

  $fd = &Utils::File::run_pipe_read ("svcs -H -a");

  while (<$fd>)
  {
    next if (/svc:\/milestone/);

    if (/^.*\s*.*\s*svc:\/(.*):.*/)
    {
      push @list, $1;
    }
  }

  &Utils::File::close_file ($fd);
  return \@list;
}

sub get_smf_services
{
  my ($service, @arr);
  my ($service_list) = &get_smf_services_list ();

  foreach $service (@$service_list)
  {
    my (@info);

    @info = &get_smf_service_info ($service);
    push @arr, \@info if scalar (@info && !&Init::ServicesList::is_forbidden ($info[0]));
  }

  return \@arr;
}

sub set_smf_service_status
{
  my ($service, $rl, $active) = @_;
  my ($info);

  $info = &get_smf_service_info ($service);

  #return if service has not changed
  return if ($active == @{@$info[0]}[1]);

  if ($active == $SERVICE_START)
  {
    &Utils::File::run ("svcadm", "enable", "-s", $service);
  }
  else
  {
    &Utils::File::run ("svcadm", "disable", "-s", $service);
  }
}

sub set_smf_service
{
  my ($service) = @_;
  my ($action, $rl, $script, $arr);

  $script = $$service[0];
  $arr = $$service[1];

  foreach $i (@$arr)
  {
    $action = $$i[1];
    $rl = $$i[0];
    &set_smf_service_status ($script, $rl, $action);
  }
}

sub set_smf_services
{
  my ($services) = @_;

  foreach $service (@$services)
  {
    &set_smf_service ($service);
  }
}

# generic functions to get the available services
sub get_init_type
{
  my $gst_dist;

  $gst_dist = $Utils::Backend::tool{"platform"};

  if (($gst_dist =~ /debian/))
  {
    return "upstart";
  }
  elsif ($gst_dist =~ /slackware/)
  {
    return "bsd";
  }
  elsif (($gst_dist =~ /freebsd/) || ($gst_dist =~ /archlinux/))
  {
    return "rcng";
  }
  elsif ($gst_dist =~ /gentoo/)
  {
    return "gentoo";
  }
  elsif ($gst_dist =~ /suse/)
  {
    return "suse";
  }
  elsif ($gst_dist =~ /solaris/)
  {
    return "smf";
  }
  else
  {
    return "sysv";
  }
}

sub run_script
{
  my ($service, $arg) = @_;
  my ($proc, $type);
  my %map =
    (
     "upstart" => \&run_upstart_initd_script,
     "sysv"    => \&run_sysv_initd_script,
     "file-rc" => \&run_sysv_initd_script,
     "bsd"     => \&run_bsd_script,
     "gentoo"  => \&run_gentoo_script,
     "rcng"    => \&run_rcng_script,
     "suse"    => \&run_sysv_initd_script,
     "smf"     => \&run_smf_svcadm,
    );

  $type = &get_init_type ();
  $proc = $map {$type};
  &$proc ($service, $arg);
}

sub get
{
  $type = &get_init_type ();

  return &get_upstart_services () if ($type eq "upstart");
  return &get_sysv_services ()    if ($type eq "sysv");
  return &get_filerc_services ()  if ($type eq "file-rc");
  return &get_bsd_services ()     if ($type eq "bsd");
  return &get_gentoo_services ()  if ($type eq "gentoo");
  return &get_rcng_services ()    if ($type eq "rcng");
  return &get_suse_services ()    if ($type eq "suse");
  return &get_smf_services ()     if ($type eq "smf");

  return undef;
}

sub set
{
  my ($services) = @_;

  $type = &get_init_type ();

  &set_upstart_services ($services) if ($type eq "upstart");
  &set_sysv_services    ($services) if ($type eq "sysv");
  &set_filerc_services  ($services) if ($type eq "file-rc");
  &set_bsd_services     ($services) if ($type eq "bsd");
  &set_gentoo_services  ($services) if ($type eq "gentoo");
  &set_rcng_services    ($services) if ($type eq "rcng");
  &set_suse_services    ($services) if ($type eq "suse");
  &set_smf_services     ($services) if ($type eq "smf");
}

sub get_service
{
  my ($name) = @_;
  my ($services) = &get ();

  foreach $service (@$services)
  {
    return $service if ($service[0] eq $name);
  }

  return undef;
}

sub set_service
{
  my ($service) = @_;

  $type = &get_init_type ();

  &set_upstart_service ($service) if ($type eq "upstart");
  &set_sysv_service    ($service) if ($type eq "sysv");
  &set_filerc_service  ($service) if ($type eq "file-rc");
  &set_bsd_service     ($service) if ($type eq "bsd");
  &set_gentoo_service  ($service) if ($type eq "gentoo");
  &set_rcng_service    ($service) if ($type eq "rcng");
  &set_suse_service    ($service) if ($type eq "suse");
  &set_smf_service     ($service) if ($type eq "smf");
}

1;
