#!/usr/bin/perl
#
# This tools parses /etc/tgt/targets.conf file and configures tgt
#
# You can find more info on http://wpkg.org/TGT-admin and download the 
# source code by pointing wget to http://wpkg.org/tgt-admin
#
# Author:  Tomasz Chmielewski
# License: GPLv2
#

use strict;
use Config::General qw(ParseConfig);
use Data::Dumper;
use Getopt::Long;

# Our config file
my $configfile = "/etc/tgt/targets.conf";

sub usage {
	print <<EOF;
Usage:
tgt-admin [OPTION]...
This tool configures tgt targets.

  -e, --execute			read $configfile and execute tgtadm commands
      --delete <value>		delete all or selected targets
				(see "--delete help" for more info)
      --offline <value>		put all or selected targets in offline state
				(see "--offline help" for more info)
      --ready <value>		put all or selected targets in ready state
				(see "--ready help" for more info)
      --update <value>		update configuration for all or selected targets
				(see "--update help" for more info)
  -s, --show			show all the targets
  -c, --conf <conf file>	specify an alternative configuration file
      --ignore-errors		continue even if tgtadm exits with non-zero code
  -f, --force			force some operations even if the target is in use
  -p, --pretend			only print tgtadm options
      --dump			dump current tgtd configuration
  -v, --verbose			increase verbosity (show tgtadm commands)
  -h, --help			show this help

EOF
	exit;
}

my %conf;
my $param = $ARGV[0];
my $execute = 0;
my $delete = 0;
my $offline = 0;
my $ready = 0;
my $update = 0;
my $show = 0;
my $alternate_conf="0";
my $ignore_errors = 0;
my $force = 0;
my $pretend = 0;
my $dump = 0;
my $verbose = 0;
my $help = 0;
my $result = GetOptions (
	"e|execute"     => \$execute,
	"delete=s"      => \$delete,
	"offline=s"     => \$offline,
	"ready=s"       => \$ready,
	"update=s"      => \$update,
	"s|show"        => \$show,
	"c|conf=s"      => \$alternate_conf,
	"ignore-errors" => \$ignore_errors,
	"f|force"       => \$force,
	"p|pretend"     => \$pretend,
	"dump"          => \$dump,
	"v|verbose"     => \$verbose,
	"h|help"        => \$help,
);

if (($help == 1) || ($param eq undef)) {
	&usage
}

# Show all the targets and exit
if ($show == 1) {
	execute("tgtadm --op show --mode target");
	exit;
}

# Some variables/arrays/hashes we will use globally
my %tgtadm_output;
my %tgtadm_output_tid;
my %tgtadm_output_name;
my @largest_tid;
my $next_tid;

# Look up which targets are configured
sub process_targets {
	# We need to run as root
	if ( $> ) {
		die("You must be root to run this program.\n");
	}

	my @show_target = `tgtadm --op show --mode target`;
	my $tid;
	my $targetname;

	# Here, we create hashes of target names (all target data) and target tids
	foreach my $show_target_line (@show_target) {
		if ( $show_target_line =~ m/^Target (\d*): (.+)/ ) {
			$tid = $1;
			$targetname = $2;
			$tgtadm_output{$targetname} = $show_target_line;
			$tgtadm_output_tid{$targetname} = $tid;
			$tgtadm_output_name{$tid} = $targetname;
		} else {
			$tgtadm_output{$targetname} .= $show_target_line;
		}
	}
	# What is the largest tid?
	my @tids = values %tgtadm_output_tid;
	@largest_tid = sort { $a <=> $b } @tids;
	$next_tid = $largest_tid[$#largest_tid];
}

# Parse config file(s)
sub parse_configs {
	# Parse the config
	if ($alternate_conf ne 0) {
		# Check if alternative configuration file exist
		if (-e "$alternate_conf") {
			execute("# Using $alternate_conf as configuration file\n");
			%conf = ParseConfig(-ConfigFile => "$alternate_conf", -UseApacheInclude => 1, -IncludeGlob => 1,);
		}
		else {
			die("file $alternate_conf not found. Exiting...\n");
		}
	} else {
		# Parse the config file with Config::General
		if (-e "$configfile") {
			%conf = ParseConfig(-ConfigFile => "$configfile", -UseApacheInclude => 1, -IncludeGlob => 1,);
		} else {
			die("Config file $configfile not found. Exiting...\n");
		}
	}
}

# Add targets, if they are not configured already
my $default_driver;
my $target;
my $option;
my $value;

sub add_targets {
	my $single_target = $_[0];
	my $configured = $_[1];
	my $connected = $_[2];
	my $in_configfile = $_[3];
	foreach my $k (sort keys %conf) {

		if ( $k eq "default-driver" ) {
			if ( not length ref($conf{$k}) ) {
				$default_driver = $conf{$k};
			} else {
				print "Multiple default-driver definitions are not allowed!\n";
				print "Check your config file for errors.\n";
				exit 1;
			}
		}
	}

	# If $default_driver is empty, default to iscsi
	if ( not defined $default_driver ) {
		execute("# default-driver not defined, defaulting to iscsi.\n");
		$default_driver = "iscsi";
	}

	foreach my $k (sort keys %conf) {
		if ( $k eq "target" ) {
			foreach my $k2 (sort keys %{$conf{$k}}) {
				# Do we run update or execute?
				if (length $single_target) {
					if ($single_target ne $k2) {
						next;
					} else {
						$target = $single_target;
					}
				} else {
					$target = $k2;
				}

				my $in_use = 0;
				if (length $single_target) {
					$in_use = &main_delete($target);
				}
				my $allowall = 1;
				if ((not defined $tgtadm_output{$k2}) ||
					($update ne 0 && $in_use == 0) ||
					($update ne 0 && $in_use == 1 && $pretend == 1 && $force == 1))
					 {
					# We have to find available tid
					if (($in_configfile == 1) && ($configured == 0)) {
						my $maxtid = &find_max_tid;
						$next_tid = $maxtid + 1;
					} elsif (length $single_target) {
						$next_tid = $tgtadm_output_tid{$target};
					} else {
						$next_tid = $next_tid + 1;
					}
					# Before we add a target, we need to know its type
					my $driver;
					foreach my $k3 (sort keys %{$conf{$k}{$k2}}) {
						$option = $k3;
						$value = $conf{$k}{$k2}{$k3};
						&check_value($value);
						if ($option eq "driver") {
							if (ref($value) eq "ARRAY") {
							print "Multiple driver definitions not allowed!\n";
							print "Check your config file for errors (target: $target).\n";
							exit 1;
							}
						$driver = $value;
						}
					}

					if (not defined $driver) {
						$driver = $default_driver;
					}

					execute("# Adding target: $target");
					execute("tgtadm --lld $driver --op new --mode target --tid $next_tid -T $target");
					foreach my $k3 (sort keys %{$conf{$k}{$k2}}) {
						$option = $k3;
						$value = $conf{$k}{$k2}{$k3};
						&check_value($value);
						&process_options($driver);
						# If there was no option called "initiator-address", it means
						# we want to allow ALL initiators for this target
						if ($option eq "initiator-address") {
							$allowall = 0;
						}
					}

					if ($allowall == 1) {
						execute("tgtadm --lld $driver --op bind --mode target --tid $next_tid -I ALL");
					}

				} else {
					if (not length $configured || $in_use eq 1) {
						execute("# Target $target already exists!");
					}
				}
			}
			if (length $single_target && $in_configfile == 0 && $configured == 0) {
				print "Target $single_target is $connected currently not configured\n";
				print "and does not exist in the config file - can't continue!\n";
				exit 1;
			}
			execute();
		}
	}
}

# Process options from the config file
sub process_options {
	my $driver = $_[0];
	if ( $option eq "backing-store" ) {
        # if we have one command, force it to be an array anyway
		unless (ref($value) eq 'ARRAY') {
			$value = [ $value ];
		}
		my @value_arr = @$value;
		my $i = 1;

		foreach my $backing_store (@value_arr) {
			# Check if device exists
			if ( -e $backing_store) {
				execute("tgtadm --lld $driver --op new --mode logicalunit --tid $next_tid --lun $i -b $backing_store");
				$i += 1;
			}
			else {
				print("skipping device $backing_store\n");
				print("$backing_store does not exist - please check the configuration file\n");
			}
		}
	}

	if ( $option eq "direct-store" ) {
		my $inq;
		my $vendor_id="";
		my $prod_id="";
		my $prod_rev="";
		my $scsi_serial="";
	        # if we have one command, force it to be an array anyway
		unless (ref($value) eq 'ARRAY') {
			$value = [ $value ];
		}
		my @value_arr = @$value;
		my $i = 1;
		foreach my $direct_store (@value_arr) {
			# Check if device exists
			if ( -e $direct_store) {
				$inq=`sg_inq $direct_store`;
				if ($inq=~/Vendor identification:\s*(\w+)\s*\n*Product identification:\s*([\w\s\/\-]+)\n\s*\n*Product revision level:\s*(\w*)\s*\n*Unit serial number:\s*(\w+)/)
				{
					$vendor_id="$1";
					$prod_id="$2";
					$prod_rev="$3";
					$scsi_serial="$4";
				}
				$vendor_id =~ s/\s+$//;
				$prod_id =~ s/\s+$//;
				$prod_rev =~ s/\s+$//;
				$scsi_serial =~ s/\s+$//;

				execute("tgtadm --lld $driver --op new --mode logicalunit --tid $next_tid --lun 1 -b $direct_store");
				execute("tgtadm --lld $driver --op update --mode logicalunit --tid  $next_tid --lun 1 --params vendor_id=\"$vendor_id\",product_id=\"$prod_id\",product_rev=\"$prod_rev\",scsi_sn=\"$scsi_serial\"");
				$i += 1;
			}
			else {
				print("skipping device $direct_store\n");
				print("$direct_store does not exist - please check the configuration file\n");
			}
		}
	}

	if ( $option eq "incominguser" ) {
	        # if we have one command, force it to be an array anyway
		unless (ref($value) eq 'ARRAY') {
			$value = [ $value ];
		}
		my @value_arr = @$value;
		foreach my $incominguser (@value_arr) {
			my @userpass = split(/ /, $incominguser);
			&check_value($userpass[1]);
			execute("tgtadm --lld $driver --mode account --op delete --user=$userpass[0]");
			execute("tgtadm --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]");
			execute("tgtadm --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0]");
		}
	}

	if ( $option eq "outgoinguser" ) {
	        # if we have one command, force it to be an array anyway
		unless (ref($value) eq 'ARRAY') {
			$value = [ $value ];
		}
		execute("# Warning: only one outgoinguser is allowed. Will only use the first one.");
		my @userpass = split(/ /, @$value[0]);
		&check_value($userpass[1]);
		execute("tgtadm --lld $driver --mode account --op delete --user=$userpass[0]");
		execute("tgtadm --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]");
		execute("tgtadm --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0] --outgoing");
	}

	if ( $option eq "initiator-address" ) {
	        # if we have one command, force it to be an array anyway
		unless (ref($value) eq 'ARRAY') {
			$value = [ $value ];
		}
		my @value_arr = @$value;
		foreach my $initiator_address (@value_arr) {
			execute("tgtadm --lld $driver --op bind --mode target --tid $next_tid -I $initiator_address");
		}
	}
}

# If the target is configured, but not present in the config file,
# try to remove it
sub remove_targets {

	&process_targets;
	my @all_targets = keys %tgtadm_output_tid;

	foreach my $existing_target (@all_targets) {
		my $dontremove = 0;
		my $k2;
		foreach my $k (sort keys %conf) {
			if ( $k eq "target" ) {
				foreach $k2 (sort keys %{$conf{$k}}) {
					if ( $k2 eq $existing_target ) {
						$dontremove = 1;
					}
				}

				if ( $dontremove == 0 ) {
					# Remove the target
					&main_delete($existing_target);
				}
			}
		}
	}
}

# Dump current tgtd configuration
sub dump_config {

	&process_targets;

	my @all_targets = keys %tgtadm_output_tid;

	# If all targets use the same driver, us it only once in the config
	my $skip_driver = 0;
	my @drivers_combined;
	foreach my $current_target (@all_targets) {
		my $driver = &show_target_info($current_target, "driver");
		push (@drivers_combined, $driver);
	}

	my %drivers_uniq;
	@drivers_uniq{@drivers_combined} = ();
	my @drivers_combined_uniq = sort keys %drivers_uniq;

	if (scalar @drivers_combined_uniq == 1) {
		print "default-driver $drivers_combined_uniq[0]\n\n";
	}

	# Print everything else in the config
	foreach my $current_target (@all_targets) {
		my $target_name = &show_target_info($current_target, "target_name");
		print "<target $target_name>\n";

		if (scalar @drivers_combined_uniq gt 1) {
			my $driver = &show_target_info($current_target, "driver");
			print "\tdriver $driver\n";
		}

		my @backing_stores = &show_target_info($current_target, "backing_stores");
		foreach my $backing_store (@backing_stores) {
			print "\tbacking-store $backing_store\n";
		}

		my @account_information = &show_target_info($current_target, "account_information");
		foreach my $account (@account_information) {
			if ($account =~ /(.+)\ \(outgoing\)/) {
				print "\toutgoinguser $1 PLEASE_CORRECT_THE_PASSWORD\n";
			} elsif (length $account) {
				print "\tincominguser $account PLEASE_CORRECT_THE_PASSWORD\n";
			}
		}

		my @acl_information = &show_target_info($current_target, "acl_information");
		if (scalar(@acl_information) != 1 || $acl_information[0] ne "ALL") {
			foreach my $ini_address (@acl_information) {
				print "\tinitiator-address $ini_address\n";
			}
		}
		print "</target>\n\n";
	}
}

# Offline or ready targets
sub ready_offline_targets {
	my $var = $_[0]; # This variable is either "offline" or "ready"
	my $off_ready;
	if ($ready eq 0) {
		$off_ready = $offline
	} elsif ($offline eq 0) {
		$off_ready = $ready
	} else {
		print "Invalid value (you can't use both ready and offline)!\n";
		exit 1;
	}
	if ($off_ready eq "help") {
		print <<EOF;
      --$var <value>		$var all or selected targets

Example usage:
      --$var help	      - display this help
      --$var ALL	      - $var all targets
      --$var tid=4	      - $var target 4 (target with tid 4)
      --$var iqn.2008-08.com.example:some.target - $var this target

EOF
	} elsif ($off_ready eq "ALL") {
		&process_targets;
		# Run over all targets and offline/ready them
		my @all_targets = keys %tgtadm_output_tid;
		foreach my $existing_target (@all_targets) {
			execute("tgtadm --op update --mode target --tid=$tgtadm_output_tid{$existing_target} -n state -v $var");
		}
	} elsif ($off_ready =~ m/tid=(.+)/) {
		&process_targets;
		execute("tgtadm --op update --mode target --tid=$1 -n state -v $var");
	} else {
		&process_targets;
		if (length $tgtadm_output_tid{$off_ready}) {
			execute("tgtadm --op update --mode target --tid=$tgtadm_output_tid{$off_ready} --name=\"$off_ready\" -n state -v $var");
		} else {
			print "There is no target with name \"$off_ready\", can't $var it!\n";
			exit 1;
		}
	}
}

# Show info for a given target
sub show_target_info {
	my $existing_target = $_[0];
	my $task = $_[1];
	# Returns target information
	if ($task eq "target_name") {
		if ($tgtadm_output{$existing_target} =~ m/^Target (\d*): (.+)/ ) {
			return $2;
		}
	# Returns driver information
	} elsif ($task eq "driver") {
		if ($tgtadm_output{$existing_target} =~ m/\s+Driver: (.+)/ ) {
			return $1;
		}
	# Returns backing store
	} elsif ($task eq "backing_stores") {
		if ($tgtadm_output{$existing_target} =~ m/\s+Backing store: (?!No backing store)(.+)/ ) {
			my @backing_stores = $tgtadm_output{$existing_target} =~ m{\s+Backing store: (?!No backing store\n)(.+)}g;
			return @backing_stores;
		}
		return;
	# Returns account information:
	} elsif ($task eq "account_information") {
		if ($tgtadm_output{$existing_target} =~ m{
			\s+Account\ information:\n(.*)\n\s+ACL\ information:
			     }xs
		      ) {
			my @accounts = split(/\n/, $1);
			my @account_information;
			foreach my $user (@accounts) {
				my @var = split(/^\s+/, $user);
				push(@account_information, $var[1]);
			}
			return @account_information;
		}
	# Returns ACL information
	} elsif ($task eq "acl_information") {
		if ($tgtadm_output{$existing_target} =~ m{
			\s+ACL\ information:\n(.*)
				}xs
			) {
			my @ini_addresses = split(/\n/, $1);
			my @acls;
			foreach my $ini_address (@ini_addresses) {
				my @var = split(/^\s+/, $ini_address);
				push(@acls, $var[1]);
			}
			return @acls;
		}
	# Returns sessions
	} elsif ($task eq "sessions") {
		my %sessions;
		if ($tgtadm_output{$existing_target} =~ m{
			\s+I_T\ nexus\ information:\n(.*)LUN\ information:
				}xs
			) {
			my @var = split(/\n/, $1);
			my $sid;
			my $cid;

			foreach my $line (@var) {
			if ($line =~ m/\s+I_T nexus:\ (.+)/) {
					$sid = $1;
				} else {
					if ($line =~ m/\s+Connection:\ (.+)/) {
						$cid = $1;
						$sessions{$sid} = $cid;
					}
				}
			}
		}
		return %sessions;
	}
}

# Main subroutine for deleting targets
sub main_delete {
	my $current_target = $_[0];
	my $current_tid = $_[1];
	my $configured = &check_configured($current_target);
	my $del_upd_text;
	# Check if the target has initiators connected
	if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) {
		if ($force == 1) {
			execute("# Removing target: $current_target");
			# Remove ACLs first
			my @acl_info = &show_target_info($current_target, "acl_information");
			foreach my $acl (@acl_info) {
				execute("tgtadm --op unbind --mode target --tid $tgtadm_output_tid{$current_target} -I $acl");
			}
			# Now, remove all sessions / connections from that tid
			my %sessions = &show_target_info($current_target, "sessions");
			foreach my $sid (keys %sessions) {
				foreach my $cid ($sessions{$sid}) {
					execute("tgtadm --op delete --mode conn --tid $tgtadm_output_tid{$current_target} --sid $sid --cid $cid");
				}
			}
			execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$current_target}");
		} else {
			if ($update ne 0) {
				$del_upd_text = "updated";
			} else {
				$del_upd_text = "deleted";
			}
			execute("# Target with tid $tgtadm_output_tid{$current_target} ($current_target) is in use, it won't be $del_upd_text.");
			return 1;
		}
	} elsif (length $tgtadm_output_tid{$current_target}) {
		execute("# Removing target: $current_target");
		execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$current_target}");
	} else {
		if (length $current_tid) {
			execute("# Target with tid $current_tid does not exist!");
		} else {
			execute("# Target with name $current_target does not exist!");
		}
	}
	if ($configured ne 0) {
		execute();
	}
	return 0;
}

# Delete the targets
sub delete_targets {

	if ($delete eq "help") {
		print <<EOF;
      --delete <value>		delete all or selected targets
				The target will be deleted only if it's not used
				(no initiator is connected to it).
				If you want to delete targets which are in use,
				you have to add "--force" flag.

Example usage:
      --delete help	      - display this help
      --delete ALL	      - delete all targets
      --delete tid=4	      - delete target 4 (target with tid 4)
      --delete iqn.2008-08.com.example:some.target - delete this target

EOF
		exit;
	} elsif ($delete eq "ALL") {
		&process_targets;
		# Run over all targets and delete them
		my @all_targets = keys %tgtadm_output_tid;
		foreach my $current_target (@all_targets) {
			&main_delete($current_target);
		}
	} elsif ($delete =~ m/^tid=(.+)/) {
		# Delete by tid
		&process_targets;
		my $current_target = $tgtadm_output_name{$1};
		&main_delete($current_target, $1);
	} else {
		# Delete by name
		&process_targets;
		my $current_target = $delete;
		&main_delete($current_target);
	}
}

# Update targets
sub update_targets {
	if ($update eq "help") {
		print <<EOF;
      --update <value>		update all or selected targets
				The target will be update only if it's not used
				(no initiator is connected to it).
				If you want to update targets which are in use,
				you have to add "--force" flag.

Example usage:
      --update help	      - display this help
      --update ALL	      - update all targets
      --update tid=4	      - update target 4 (target with tid 4)
      --update iqn.2008-08.com.example:some.target - update this target

EOF
		exit;
	} elsif ($update eq "ALL") {
		# Run over all targets and delete them if they are not in use
		&parse_configs;
		&process_targets;
#		my @all_targets = keys %tgtadm_output_tid;
		my @targets_combined = &combine_targets;
		foreach my $current_target (@targets_combined) {
			my $configured = &check_configured($current_target);
			my $connected = &check_connected($current_target);
			my $in_configfile = &check_in_configfile($current_target);
			&combine_targets;
			if (($in_configfile == 0) && ($configured == 1)) {
				# Delete the target if it's not in the config file
				&main_delete($current_target);
			} else {
				&add_targets($current_target, $configured, $connected, $in_configfile);
			}

		}
	} elsif ($update =~ m/^tid=(.+)/) {
		# Update by tid
		&parse_configs;
		&process_targets;
		my $current_target = $tgtadm_output_name{$1};
		my $configured = &check_configured($current_target);
		my $connected = &check_connected($current_target);
		my $in_configfile = &check_in_configfile($current_target);
		if (($in_configfile == 0) && ($configured == 1)) {
			# Delete the target if it's not in the config file
			&main_delete($current_target);
		} elsif ($configured == 1) {
			&add_targets($current_target, $configured, $connected);
		} else {
			print "There is no target with tid $1, can't continue!\n";
			exit 1;
		}
	} else {
		# Update by name
		&parse_configs;
		&process_targets;
		my $current_target = $update;
		my $configured = &check_configured($current_target);
		my $connected = &check_connected($current_target);
		my $in_configfile = &check_in_configfile($current_target);
		if (($in_configfile == 0) && ($configured == 1)) {
			# Delete the target if it's not in the config file
			&main_delete($current_target);
		} else {
			&add_targets($current_target, $configured, $connected);
		}
	}
}

# Find the biggest tid
sub find_max_tid {
	my @all_targets = keys %tgtadm_output_tid;
	my $maxtid = 0;
	foreach my $var (@all_targets) {
		if ($tgtadm_output_tid{$var} > $maxtid) {
			$maxtid = $tgtadm_output_tid{$var};
		}
		return $maxtid;
	}
}

# Combine targets from the config file and currently configured targets
sub combine_targets {
	my @targets_in_configfile;
	my @all_targets = keys %tgtadm_output_tid;
	my @targets_combined;
	# Make an array of targets in the config file
	foreach my $k (sort keys %conf) {
		if ( $k eq "target" ) {
			foreach my $k2 (sort keys %{$conf{$k}}) {
				push(@targets_in_configfile, $k2)
			}
		}
	}
	# Use only unique elements from both arrays
	foreach my $current_target (@all_targets) {
		push (@targets_combined, $current_target) unless grep { $_ eq $current_target } @targets_in_configfile;
	}
	@targets_combined = (@targets_combined, @targets_in_configfile);
	return @targets_combined;
}

# Check if a value is correct
sub check_value {
	if ( not defined $_[0] or not length $_[0] ) {
		print "\nOption $option has a missing value!\n";
		print "Check your config file for errors (target: $target)\n";
		exit 1;
	}
}

# Check if the target is in the config file
sub check_in_configfile {
	my $current_target = $_[0];
	my $result;
	foreach my $k (sort keys %conf) {
		if ( $k eq "target" ) {
			foreach my $k2 (sort keys %{$conf{$k}}) {
				if ($k2 eq $current_target) {
					return 1;
				}
			}
			# If we're here, we didn't find a match
			return 0;
		}
	}
}

# Check if the target is configured in tgtd
sub check_configured {
	my $current_target = $_[0];
	if (length $tgtadm_output_tid{$current_target}) {
		return 1;
	} else {
		return 0;
	}
}

# Check if any initiators are connected to the target
sub check_connected {
	my $current_target = $_[0];
	if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) {
		return 1;
	} else {
		return 0;
	}
}

# Execute or just print (or both) everything we start or would start
sub execute {
	if ($pretend == 0) {

		my $args = "@_";
		if ($verbose == 1) {
			print "$args\n";
		}
		# Don't try to execute if it's a comment
		my @execargs = split(/#/, $args);
		if ( $execargs[0] ne undef  ) {
			system($args);

			# If non-zero exit code was return, exit
			my $exit_value  = $? >> 8;
			if (($exit_value != 0) && ($ignore_errors == 0)) {
				print "Command:\n\t$args\nexited with code: $exit_value.\n";
				exit $exit_value;
			}
		}

	} elsif ( $pretend == 1 ) {
		print "@_\n";
	}
}

if ($execute == 1) {
	&process_targets;
	&parse_configs;
	&add_targets;
	&remove_targets;
} elsif ($delete ne 0) {
	&delete_targets;
} elsif ($update ne 0) {
	&update_targets;
} elsif ($dump == 1) {
	&dump_config;
} elsif ($offline ne 0) {
	&ready_offline_targets("offline");
} elsif ($ready ne 0) {
	&ready_offline_targets("ready");
} else {
	print "No action specified.\n";
}

