#!/usr/bin/perl -w
###############################################################################
# $Id$
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################

=head1 NAME

VCL::Module::OS::Linux::init::Upstart.pm

=head1 DESCRIPTION

 This module provides VCL support for the Upstart Linux init daemon used in
 distributions including:
 Ubuntu 6.10+

=cut

###############################################################################
package VCL::Module::OS::Linux::init::Upstart;

# Specify the lib path using FindBin
use FindBin;
use lib "$FindBin::Bin/../../../../..";

# Configure inheritance
use base qw(VCL::Module::OS::Linux::init);

# Specify the version of this module
our $VERSION = '2.5.1';

# Specify the version of Perl to use
use 5.008000;

use strict;
use warnings;
use diagnostics;

use VCL::utils;

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

=head1 CLASS VARIABLES

=cut

=head2 $INIT_DAEMON_ORDER

 Data type   : integer
 Value       : 10
 Description : Determines the order in which Linux init daemon modules are used.
               Lower values are used first.

=cut

our $INIT_DAEMON_ORDER = 10;

=head2 @REQUIRED_COMMANDS

 Data type   : array
 Values      : initctl
 Description : List of commands used within this module to configure and control
               Upstart services. This module will not be used if any of these
               commands are unavailable on the computer.

=cut

our @REQUIRED_COMMANDS = ('initctl');

=head2 @PROHIBITED_COMMANDS

 Data type   : array
 Values      : initctl
 Description : List of commands that must not exist on the computer if the
					Upstart.pm module is to be used. This array contains:
					'chkconfig'.

=cut

our @PROHIBITED_COMMANDS = ('chkconfig');

=head2 $SERVICE_NAME_MAPPINGS

 Data type   : hash reference
 Description : Contains a mapping of common service names to the names used by
               Upstart distibutions. Example, sshd is called ssh on Ubuntu.

=cut

our $SERVICE_NAME_MAPPINGS = {
	'sshd' => 'ssh',
	'ext_sshd' => 'ext_ssh',
};

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

=head1 OBJECT METHODS

=cut

#//////////////////////////////////////////////////////////////////////////////

=head2 get_service_names

 Parameters  : none
 Returns     : array
 Description : Calls 'initctl list' to retrieve the list of services controlled
               by Upstart on the computer.

=cut

sub get_service_names {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_info = $self->_get_service_info() || {};
	return sort keys %$service_info;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 _get_service_info

 Parameters  : $use_cache (optional)
 Returns     : hash reference
 Description : Calls 'initctl list' to retrieve the list of services controlled
               by Upstart on the computer. Also calls 'service --status-all' to
               determine SysV-style services controlled by Upstart. A hash
               reference is returned. Hash keys are service names. Values are
               either 'initctl' or 'service' indicating if Upstart's initctl
               command can be used to control the service or the service command
               must be used:
                  {
                    "acpid" => "initctl",
                    "open-vm-tools" => "service",
                    "ssh" => "initctl",
                    "sshd" => "initctl",
                    "xrdp" => "service"
                  }
               By default, the service info is retrieved every time this
               subroutine is called. To use cached info, the $use_cache argument
               must be explicitely set to true.

=cut

sub _get_service_info {
	my $self = shift;
	if (ref($self) !~ /VCL::/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $use_cache = shift;
	if ($use_cache && defined($self->{service_info})) {
		return $self->{service_info};
	}
	else {
		$self->{service_info} = {};
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my %service_name_mappings_reversed = reverse %$SERVICE_NAME_MAPPINGS;
	
	my $initctl_command = "initctl list";
	my ($initctl_exit_status, $initctl_output) = $self->os->execute($initctl_command, 0);
	if (!defined($initctl_output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to list Upstart services on $computer_node_name");
		return;
	}
	elsif (grep(/Connection refused/i, @$initctl_output)) {
		# Ubuntu 16 and later display the following:
		#    initctl: Unable to connect to Upstart: Failed to connect to socket /com/ubuntu/upstart: Connection refused
		notify($ERRORS{'DEBUG'}, 0, "initctl command cannot be used to retrieve list of all services on $computer_node_name, command: '$initctl_command', output:\n" . join("\n", @$initctl_output));
	}
	elsif ($initctl_exit_status ne '0') {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve list of all services on $computer_node_name using the initctl command, exit status: $initctl_exit_status, command:\n$initctl_command\noutput:\n" . join("\n", @$initctl_output));
		return;
	}
	else {
		# Format of initctl list output lines:
		#    splash-manager stop/waiting
		#    network-interface-security (network-interface/eth1) start/running
		#    tty1 start/running, process 1400
		for my $line (@$initctl_output) {
			my ($service_name) = $line =~ /^([^\s\t]+)/;
			if ($service_name) {
				#notify($ERRORS{'DEBUG'}, 0, "found '$service_name' service via '$initctl_command', line: '$line'");
				$self->{service_info}{$service_name} = 'initctl';
			}
			else {
				notify($ERRORS{'WARNING'}, 0, "failed to parse service name on $computer_node_name, command: '$initctl_command', line: '$line', output:\n" . join("\n", @$initctl_output));
			}
		}
	}
	
	# VCL-966
	# Legacy SysV-style services are not reported by 'initctl list'
	# The SysV.pm module cannot control these services becuase the chkconfig command is not available on Ubuntu
	
	my $service_command = "service --status-all";
	my ($service_exit_status, $service_output) = $self->os->execute($service_command);
	if (!defined($service_output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve list of all services on $computer_node_name using the service command: $service_command");
		return;
	}
	elsif ($service_exit_status ne '0') {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve list of all services on $computer_node_name using the service command, exit status: $service_exit_status, command:\n$service_command\noutput:\n" . join("\n", @$service_output));
	}
	else {
		# Lines should be formatted as:
		#    [ + ]  acpid
		#    [ ? ]  apport
		#    [ - ]  dbus
		for my $line (@$service_output) {
			my ($service_name) = $line =~ /\]\s*(\S+)\s*$/;
			if ($service_name) {
				# The service utility method of controlling services is a fallback
				# Don't add if previously found by initctl list
				if (!defined($self->{service_info}{$service_name})) {
					#notify($ERRORS{'DEBUG'}, 0, "found '$service_name' service via '$service_command', line: '$line'");
					$self->{service_info}{$service_name} = 'service';
				}
			}
			else {
				#notify($ERRORS{'WARNING'}, 0, "failed to parse service name on $computer_node_name, command: '$service_command', line: '" . string_to_ascii($line) . "'\noutput:\n" . join("\n", @$service_output));
			}
		}
	}
	
	for my $service_name (keys %{$self->{service_info}}) {
		my $service_name_mapping = $service_name_mappings_reversed{$service_name};
		if ($service_name_mapping) {
			$self->{service_info}{$service_name_mapping} = $self->{service_info}{$service_name};
		}
	}
	
	notify($ERRORS{'DEBUG'}, 0, "retrieved services info from $computer_node_name:\n" . format_data($self->{service_info}));
	return $self->{service_info};
}

#//////////////////////////////////////////////////////////////////////////////

=head2 delete_service

 Parameters  : $service_name
 Returns     : boolean
 Description : Stops the service if it is running. Deletes the
               '/etc/init/<$service_name>.conf' file.

=cut

sub delete_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name_argument = shift;
	if (!$service_name_argument) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	
	# Need to attempt to delete both the service with a name matching the argument as well as the mapped service name
	my @service_names = ($service_name_argument);
	
	# If a mapped service name also exists, attempt to delete it as well
	if ($SERVICE_NAME_MAPPINGS->{$service_name_argument}) {
		push @service_names, $SERVICE_NAME_MAPPINGS->{$service_name_argument};
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	for my $service_name (@service_names) {
		$self->stop_service($service_name) || return;
		
		# Delete the service configuration file
		my $service_file_path = "/etc/init/$service_name.conf";
		$self->os->delete_file($service_file_path) || return;
		
		notify($ERRORS{'DEBUG'}, 0, "deleted '$service_name' service on $computer_node_name");
	}
	
	return 1;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 start_service

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'initctl start <$service_name>' to start the service.

=cut

sub start_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	# Check if initctl cannot be used to control service
	if ($self->_controlled_by_service_command($service_name)) {
		return $self->_call_service_start($service_name);
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $command = "initctl start $service_name";
	my ($exit_status, $output) = $self->os->execute($command);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to start '$service_name' service on $computer_node_name");
		return;
	}
	elsif (grep(/Unknown job/i, @$output)) {
		# Output if the service doesn't exist: 'initctl: Unknown job: <service name>'
		notify($ERRORS{'WARNING'}, 0, "'$service_name' service does not exist on $computer_node_name");
		return;
	}
	elsif (grep(/already running/i, @$output)) {
		# Output if the service is already running: 'initctl: Job is already running: <service name>'
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' is already running on $computer_node_name");
		return 1;
	}
	elsif (grep(/running/i, @$output)) {
		# Output if the service was started: '<service name> start/running, process <PID>'
		notify($ERRORS{'DEBUG'}, 0, "started '$service_name' service on $computer_node_name");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to start '$service_name' service on $computer_node_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 stop_service

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'initctl stop <$service_name>' to stop the service.

=cut

sub stop_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	# Check if initctl cannot be used to control service
	if ($self->_controlled_by_service_command($service_name)) {
		return $self->_call_service_stop($service_name);
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $command = "initctl stop $service_name";
	my ($exit_status, $output) = $self->os->execute($command);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to stop '$service_name' service on $computer_node_name");
		return;
	}
	elsif (grep(/Unknown job/i, @$output)) {
		# Output if the service doesn't exist: 'initctl: Unknown job: <service name>'
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service does not exist on $computer_node_name");
	}
	elsif (grep(/Unknown instance/i, @$output)) {
		# Output if the service is not running: 'initctl: Unknown instance:'
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' is already stopped on $computer_node_name");
		return 1;
	}
	elsif (grep(/ stop\//i, @$output)) {
		# Output if the service was stopped: '<service name> stop/waiting'
		notify($ERRORS{'DEBUG'}, 0, "stopped '$service_name' service on $computer_node_name");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to stop '$service_name' service on $computer_node_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output));
		return;
	}
	
	return 1;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 restart_service

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'initctl restart <$service_name>' to restart the service.

=cut

sub restart_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	# Check if initctl cannot be used to control service
	if ($self->_controlled_by_service_command($service_name)) {
		return $self->_call_service_restart($service_name);
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $command = "initctl restart $service_name";
	my ($exit_status, $output) = $self->os->execute($command);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to restart '$service_name' service on $computer_node_name");
		return;
	}
	elsif (grep(/Unknown job/i, @$output)) {
		# Output if the service doesn't exist: 'initctl: Unknown job: <service name>'
		notify($ERRORS{'WARNING'}, 0, "'$service_name' service does not exist on $computer_node_name");
		return;
	}
	elsif (grep(/Unknown instance/i, @$output)) {
		# Output if the service is not running: 'initctl: Unknown instance:'
		return $self->start_service($service_name);
	}
	elsif (grep(/process \d+/i, @$output)) {
		# Output if the service was restarted: '<service name> start/running, process <PID>'
		notify($ERRORS{'DEBUG'}, 0, "restarted '$service_name' service on $computer_node_name");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to restart '$service_name' service on $computer_node_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 add_ext_sshd_service

 Parameters  : none
 Returns     : boolean
 Description : Generates and configures '/etc/init/ext_ssh.conf' based off of
               the existing '/etc/init/ext_ssh.conf' file. Adds the ext_ssh
               service to the computer.

=cut

sub add_ext_sshd_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $sshd_service_file_path     = '/etc/init/ssh.conf';
	my $ext_sshd_service_file_path = '/etc/init/ext_ssh.conf';
	my $ext_sshd_config_file_path = '/etc/ssh/external_sshd_config';
	
	# Get the contents of the sshd service startup file already on the computer
	my @sshd_service_file_contents = $self->os->get_file_contents($sshd_service_file_path);
	if (!@sshd_service_file_contents) {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of $sshd_service_file_path from $computer_node_name");
		return;
	}
	
	my $ext_sshd_service_file_contents = join("\n", @sshd_service_file_contents);
	
	# Replace: OpenSSH --> external OpenSSH
	$ext_sshd_service_file_contents =~ s|(OpenSSH)|external $1|g;
	
	# Replace: ' ssh ' --> ' ext_ssh '
	$ext_sshd_service_file_contents =~ s| ssh | ext_ssh |g;
	
	# Add config file path argument
	$ext_sshd_service_file_contents =~ s|(exec.*/sshd .*)|$1 -f $ext_sshd_config_file_path|g;
	
	# Replace /var/run/sshd --> /var/run/ext_sshd
	$ext_sshd_service_file_contents =~ s|(/var/run/)sshd|$1ext_sshd|g;
	
	if (!$self->os->create_text_file($ext_sshd_service_file_path, $ext_sshd_service_file_contents)) {
		notify($ERRORS{'WARNING'}, 0, "failed to create ext_sshd service file on $computer_node_name: $ext_sshd_service_file_path");
		return;
	}
	
	if (!$self->os->set_file_permissions($ext_sshd_service_file_path, '644')) {
		notify($ERRORS{'WARNING'}, 0, "failed to set permissions on ext_sshd service file to 644 on $computer_node_name: $ext_sshd_service_file_path");
		return;
	}
	
	return 1;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 service_running

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'initctl status <$service_name>' to determine if the
               service is running.

=cut

sub service_running {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	# Check if initctl cannot be used to control service
	if ($self->_controlled_by_service_command($service_name)) {
		return $self->_call_service_status($service_name);
	}
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $command = "initctl status $service_name";
	my ($exit_status, $output) = $self->os->execute($command);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine if '$service_name' service is enabled on $computer_node_name");
		return;
	}
	elsif (grep(/Unknown job/i, @$output)) {
		# Output if the service doesn't exist: 'initctl: Unknown job: <service name>'
		notify($ERRORS{'WARNING'}, 0, "'$service_name' service does not exist on $computer_node_name");
		return;
	}
	elsif (grep(/running/i, @$output)) {
		# Output if the service is running: '<service name> start/running, process <PID>'
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is running on $computer_node_name");
		return 1;
	}
	elsif (grep(/stop/i, @$output)) {
		# Output if the service is not running: '<service name>stop/waiting'
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is not running $computer_node_name");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to determine if '$service_name' service is running on $computer_node_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 service_enabled

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'initctl show-config <$service_name>' to determine if the
               service is enabled.

=cut

sub service_enabled {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	# Check if an override file exists and contains 'manual'
	my $service_override_file_path = "/etc/init/$service_name.override";
	if ($self->os->file_exists($service_override_file_path)) {
		my @override_file_contents = $self->os->get_file_contents($service_override_file_path);
		if (!@override_file_contents) {
			notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of $service_override_file_path from $computer_node_name");
		}
		else {
			if (grep(/manual/i, @override_file_contents)) {
				notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is not enabled on $computer_node_name, $service_override_file_path exists and contains 'manual'");
				return 0;
			}
		}
	}
	
	my $command = "initctl show-config $service_name";
	my ($exit_status, $output) = $self->os->execute($command);
	if (!defined($output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine if '$service_name' service is enabled on $computer_node_name");
		return;
	}
	elsif (grep(/Unknown job/i, @$output)) {
		# Output if the service doesn't exist: 'initctl: Unknown job: <service name>'
		notify($ERRORS{'WARNING'}, 0, "'$service_name' service does not exist on $computer_node_name");
		return;
	}
	elsif (grep(/start on/i, @$output)) {
		# Output if the service is enabled:
		# <service name>
		#   start on (filesystem or runlevel [2345])
		#   stop on runlevel [!2345]
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is enabled on $computer_node_name");
		return 1;
	}
	elsif (!grep(/^initctl:/, @$output)) {
		# Output if the service is not enabled:
		# <service name>
		#   stop on runlevel [06]
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service is not enabled $computer_node_name");
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to determine if '$service_name' service is enabled on $computer_node_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 enable_service

 Parameters  : $service_name
 Returns     : boolean
 Description : 

=cut

sub enable_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $service_override_file_path = "/etc/init/$service_name.override";
	
	if (!$self->os->file_exists($service_override_file_path)) {
		return 1;
	}
	if ($self->os->delete_file($service_override_file_path)) {
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to enable '$service_name' service, unable to delete override file: $service_override_file_path");
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 disable_service

 Parameters  : $service_name
 Returns     : boolean
 Description : 

=cut

sub disable_service {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return 0;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} || $service_name;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $service_override_file_path = "/etc/init/$service_name.override";
	
	if ($self->os->create_text_file($service_override_file_path, "manual\n")) {
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "unable to disable '$service_name' service, failed to create override file: $service_override_file_path");
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 _controlled_by_service_command

 Parameters  : $service_name
 Returns     : boolean
 Description : Returns true if the service exists but cannot be controlled by
               the 'initctl' command. The 'service' command must be used for
               basic service control.

=cut

sub _controlled_by_service_command {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	if (!$service_name) {
		notify($ERRORS{'WARNING'}, 0, "service name argument was not supplied");
		return;
	}
	
	my $service_info = $self->_get_service_info(1) || return;
	if ($service_info->{$service_name} && $service_info->{$service_name} eq 'service') {
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service cannot be controlled by the initctl command, the service command will be used");
		return 1;
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' service will be controlled by the initctl command");
		return 0;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 _call_service_start

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'service <$service_name> start' to start a service that
               can't be controlled by initctl.

=cut

sub _call_service_start {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $service_command = "service $service_name start";
	my ($service_exit_status, $service_output) = $self->os->execute($service_command);
	if (!defined($service_output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to start '$service_name' service on $computer_node_name using the service command: $service_command");
		return;
	}
	elsif ($service_exit_status eq '0' || grep(/done/, @$service_output)) {
		notify($ERRORS{'OK'}, 0, "started '$service_name' service on $computer_node_name using the service command: '$service_command', output:\n" . join("\n", @$service_output));
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to start '$service_name' service on $computer_node_name using the service command, exit status: $service_exit_status, command:\n$service_command\noutput:\n" . join("\n", @$service_output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 _call_service_stop

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'service <$service_name> stop' to stop a service that
               can't be controlled by initctl.

=cut

sub _call_service_stop {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $service_command = "service $service_name stop";
	my ($service_exit_status, $service_output) = $self->os->execute($service_command);
	if (!defined($service_output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to stop '$service_name' service on $computer_node_name using the service command: $service_command");
		return;
	}
	elsif ($service_exit_status eq '0' || grep(/done/, @$service_output)) {
		notify($ERRORS{'OK'}, 0, "stopped '$service_name' service on $computer_node_name using the service command: '$service_command', output:\n" . join("\n", @$service_output));
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to stop '$service_name' service on $computer_node_name using the service command, exit status: $service_exit_status, command:\n$service_command\noutput:\n" . join("\n", @$service_output));
		return;
	}
	
	# Try to fix common xRDP bug on Ubuntu, service start/stop/restart often leaves behind the .pid file
	# Service can't be completly restarted until file is manually deleted
	my @pid_files = $self->os->find_files('/var/run', "$service_name.pid", 1, 'f');
	if (scalar(@pid_files) == 1) {
		my $pid_file = $pid_files[0];
		notify($ERRORS{'DEBUG'}, 0, "'$service_name' may not have cleaned up .pid file when service was stopped, attempting to delete file: $pid_file");
		$self->os->delete_file($pid_file)
	}
	
	return 1;
}

#//////////////////////////////////////////////////////////////////////////////

=head2 _call_service_restart

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'service <$service_name> stop' and then
					'service <$service_name> start' to restart a service that can't
					be controlled by initctl.

=cut

sub _call_service_restart {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $service_command = "service $service_name restart";
	my ($service_exit_status, $service_output) = $self->os->execute($service_command);
	if (!defined($service_output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to restart '$service_name' service on $computer_node_name using the service command: $service_command");
		return;
	}
	elsif ($service_exit_status eq '0' || grep(/done/, @$service_output)) {
		notify($ERRORS{'OK'}, 0, "restarted '$service_name' service on $computer_node_name using the service command: '$service_command', output:\n" . join("\n", @$service_output));
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to restart '$service_name' service on $computer_node_name using the service command, exit status: $service_exit_status, command:\n$service_command\noutput:\n" . join("\n", @$service_output));
		return;
	}
	
	if ($self->_call_service_status($service_name)) {
		return 1;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "'$service_name' service does not seem to be running after restarting it, attempting to stop and start service");
		$self->_call_service_stop($service_name);
		$self->_call_service_start($service_name);
		return $self->_call_service_status($service_name);
	}
}

#//////////////////////////////////////////////////////////////////////////////

=head2 _call_service_status

 Parameters  : $service_name
 Returns     : boolean
 Description : Calls 'service <$service_name> status' to get the status of a
               service that can't be controlled by initctl.

=cut

sub _call_service_status {
	my $self = shift;
	if (ref($self) !~ /linux/i) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
		return;
	}
	
	my $service_name = shift;
	
	my $computer_node_name = $self->data->get_computer_node_name();
	
	my $service_command = "service $service_name status";
	my ($service_exit_status, $service_output) = $self->os->execute($service_command);
	if (!defined($service_output)) {
		notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve status of '$service_name' service on $computer_node_name using the service command: $service_command");
		return;
	}
	
	# Output if service is running:
	# * Checking status of Remote Desktop Protocol server xrdp
	#   ...done.
	# * Checking status of RDP Session Manager sesman
	#   ...done.
	
	# Output if service is stopped:
	# * Checking status of Remote Desktop Protocol server xrdp
	#   ...fail!
	# * Checking status of RDP Session Manager sesman
	#   ...fail!
	
	if (grep(/done/, @$service_output) && !grep(/fail/, @$service_output)) {
		notify($ERRORS{'OK'}, 0, "'$service_name' service is running on $computer_node_name, output of '$service_command':\n" . join("\n", @$service_output));
		return 1;
	}
	elsif (grep(/fail/, @$service_output)) {
		notify($ERRORS{'OK'}, 0, "'$service_name' service is NOT running on $computer_node_name, output of '$service_command':\n" . join("\n", @$service_output));
		return 0;
	}
	else {
		notify($ERRORS{'WARNING'}, 0, "failed to determine if '$service_name' service is running on $computer_node_name using the service command, output does not contain 'done' or 'fail', exit status: $service_exit_status, command:\n$service_command\noutput:\n" . join("\n", @$service_output));
		return;
	}
}

#//////////////////////////////////////////////////////////////////////////////

1;
__END__

=head1 SEE ALSO

L<http://cwiki.apache.org/VCL/>

=cut
