#!/usr/bin/perl -w

######################################################################
#
#  cxfsdump.pl
#
#  Copyright (c) 2002, Javier Herrero. All Rights Reserved.
#
#  This program is free software; you can redistribute it and/or
#  modify it under the same terms as Perl.
#
#  Javier Herrero. July 30, 2002
#  (jherrero@cnio.es)
#
#  Last revision: September 3, 2002
#
######################################################################

use strict;

# PATH to several commands
my $STACKER = "/usr/sbin/stacker";
my $CLUSTER_MGR = "/usr/cluster/bin/cmgr";
my $MT = "/usr/bin/mt";

# Hardware configuration
my $JUKEBOX;
my $TAPE_DRIVE;

# Cluster configuration
my $CLUSTER_NAME;
my $NODE_NAME;

# Backup configuration
my $FILESYSTEMS;
my $DEVICE_SETS;
my $LAST_SET;

# Other default values
my $VERBOSE = 2;
my $CONFIG_FILE = "/etc/cxfsdump.conf";

open(STDERR, ">&STDOUT");
&main(@ARGV);

######################################################################
##  MAIN
######################################################################
sub main {
	my (@ARGV) = @_;
	my ($run_string, $result);

	# Try to read the CONFIG_FILE from arguments
	map {$CONFIG_FILE = $1 if ($_ =~ /^\-f=?(.+)$/)} @ARGV;

	# Launch configuration tool and exits if requested
	map {&CONFIG::configure_system($CONFIG_FILE) if ($_ =~ /^config$/)} @ARGV;

	# Try to read the VERBOSITY LEVEL from arguments
	map {$VERBOSE = $1 if ($_ =~ /^\-v=?(\d+)$/)} @ARGV;

	if ($VERBOSE) {
		print "cxfsdump: reading configuration from $CONFIG_FILE.\n";
	}
	&CONFIG::read_configuration_file($CONFIG_FILE);

	# Get the number of configured sets
	my $number_of_sets = scalar(@{$DEVICE_SETS});
	if ($VERBOSE) {
		print "cxfsdump: Jukebox ($JUKEBOX).\n";
		print "cxfsdump: Tape drive ($TAPE_DRIVE).\n";
		print "cxfsdump: Cluster ($CLUSTER_NAME).\n";
		print "cxfsdump: Node ($NODE_NAME).\n";
		print "cxfsdump: Filesystems (", join(", ", @{$FILESYSTEMS}), ")\n";
		print "cxfsdump: $number_of_sets sets available.\n";
	}

	# Get the selected set
	if (defined($LAST_SET)) {
		$LAST_SET ++;
	} else {
		$LAST_SET = 1;
	}
	my $selected_set = $LAST_SET;
	$selected_set = 1 if ($selected_set > $number_of_sets);

	# Try to read the selected set from the arguments
	map {$selected_set = $1 if ($_ =~ /^\-set=?(\d+)$/)} @ARGV;
	if ($VERBOSE) {
		print "cxfsdump: set $selected_set selected.\n";
	}
	$LAST_SET = $selected_set;

	# Get selected set if it is available (or configured)
	if ($selected_set > $number_of_sets) {
		print "cxfsdump: ERROR (set $selected_set selected and only",
			" $number_of_sets sets available).\n";
		exit(1);
	}
	my @set_content = @{$DEVICE_SETS->[$selected_set-1]};
	if ($VERBOSE) {
		print "cxfsdump: set $selected_set include slots ",
				join(", ", @set_content), ".\n";
	}

	&dump_set($selected_set, @set_content);

	&CONFIG::save_configuration_file($CONFIG_FILE);
}

######################################################################
##  DUMP SET
######################################################################
sub dump_set {
	my ($selected_set, @set_content) = @_;
	my ($run_string, $result);

	my $current_set_number = 0;
	my $current_fs_number = 0;
	&load_tape_into_device($set_content[$current_set_number]);
	my $starting_media = 1;
	foreach my $fs (@{$FILESYSTEMS}) {
		$result = &relocate_metadataserver_to_current_node($fs);
		next if (!$result); # Skip dump on error

		my $current_tape = 0;
		my $dump_finished = 0;
		$run_string = &get_run_string_for_xfsdump($fs, $selected_set,
				$current_tape, $set_content[$current_set_number],
				$starting_media, 0);
		while (!$dump_finished) {
			if ($VERBOSE) {
				print "cxfsdump: exec ($run_string).\n";
			}
			$result = qx"$run_string";
			$starting_media = 0;

			$result =~ /xfsdump\: Dump Status\: (\w+)/;
			my $code = ($1 || ' -no code- ');
			if ($VERBOSE > 1) {
				$result =~ s/\n(.)/\n  $1/g;
				print "  ".$result;
			} elsif ($VERBOSE) {
				my @lines = split(/[\r\n]+/, $result);
				map {print " ".$_ if ($_ =~ /WARNING/)} @lines;
			}

			if ($code eq "SUCCESS") {
				if ($VERBOSE) {
					print "cxfsdump: successful dump of $fs.\n";
				}
				$dump_finished = 1;
			} elsif ($code eq "INTERRUPT") {
				if ($VERBOSE) {
					print "cxfsdump: dump of $fs will span to next tape.\n";
				}

				# Check for next tape
				if ($current_set_number >= (scalar(@set_content) - 1)) {
					print "cxfsdump: ERROR (No more tapes for this set).\n";
					&unload_tape_from_device($set_content[$current_set_number]);
					exit(2);
				}
				#Change media
				&unload_tape_from_device($set_content[$current_set_number]);
				$current_set_number++;
				&load_tape_into_device($set_content[$current_set_number]);
				$starting_media = 1;
				$current_tape ++;

				# Get run string for resuming dump
				$run_string = &get_run_string_for_xfsdump($fs, $selected_set,
						$current_tape, $set_content[$current_set_number],
						$starting_media, 1);
			} else {
				if ($VERBOSE <= 1) {
					$result =~ s/\n(.)/\n  $1/g;
					print "  ".$result;
				}
				print "cxfsdump: ERROR (dump exited with code $code).\n";
				&unload_tape_from_device($set_content[$current_set_number]);
				exit(2);
			}
		}
	}
	&unload_tape_from_device($set_content[$current_set_number]);
}

######################################################################
##  RELOCATE METADATASERVER TO CURRENT NODE
######################################################################
sub relocate_metadataserver_to_current_node {
	my ($fs) = @_;
	my ($run_string, $result);

	# Getting name of the filesystem
	my $fs_name = $fs;
	$fs_name =~ s/[^\/]*\///g;

	# Checking the filesystem exists in the cluster
	$run_string = "$CLUSTER_MGR -c \"show cxfs_filesystems in cluster".
			" $CLUSTER_NAME\"";
	if ($VERBOSE) {
		print "cxfsdump: exec ($run_string).\n";
	}
	$result = qx"$run_string";
	if ($result !~ /\b$fs_name\b/) {
		print "cxfsdump: WARNING (filesystem $fs_name is not a CXFS filesytem".
			" in cluster $CLUSTER_NAME).\n";

		# Trying to find filesystem in /etc/fstab
		$run_string = "fgrep '$fs' /etc/fstab";
		if ($VERBOSE) {
			print "cxfsdump: exec ($run_string).\n";
		}
		$result = qx"$run_string";
		if ($result) {
			print "cxfsdump: PASS (filesystem $fs is present in /etc/fstab).\n";
			return 2;
		} else {
			print "cxfsdump: WARNING (filesystem $fs not found /etc/fstab).\n";
			print "cxfsdump: ERROR (Aborting dump of $fs).\n";
			return 0;
		}
	}

	# Relocating the metadataserver of the filesystem to the current node
	$run_string = "$CLUSTER_MGR -c \"admin cxfs_relocate cxfs_filesystem".
			" $fs_name to node $NODE_NAME in cluster $CLUSTER_NAME\"";
	if ($VERBOSE) {
		print "cxfsdump: exec ($run_string).\n";
	}
	$result = qx"$run_string";
	
	if ($result !~ /cxfs_relocate operation successful/) {
		print "$result\n";
		return 0;
	}
	return 1;
}

######################################################################
##  GET RUN STRING FOR XFSDUMP
######################################################################
sub get_run_string_for_xfsdump {
	my ($fs, $set, $current_tape, $current_slot, $starting_media, $resume_dump)
			= @_;

	my $session_label = $fs;
	$session_label =~ s/\//_/g;
	$session_label =~ s/^_+//;
	$session_label .= ".$set.R$current_tape";

	my $run_string = "xfsdump -f $TAPE_DRIVE -l 0";
	$run_string .= " -R" if ($resume_dump);
	$run_string .= " -o" if ($starting_media);
	$run_string .= " -F -L $session_label -M tape$current_slot $fs";

	return $run_string;
}

######################################################################
##  LOAD TAPE INTO DEVICE
######################################################################
sub load_tape_into_device {
	my ($slot) = @_;

	# Load tape into device
	my $run_string = "$STACKER -l $slot $JUKEBOX";
	if ($VERBOSE) {
		print "cxfsdump: exec ($run_string)\n";
	}
	my $result = qx"$run_string";

	if ($result ne "") {
		print "cxfsdump: ERROR (cannot load tape: $result).\n";
		exit(2);
	}
	sleep(30);
}

######################################################################
##  UNLOAD TAPE FROM DEVICE
######################################################################
sub unload_tape_from_device {
	my ($slot) = @_;
	my ($run_string, $result);

	sleep(30);
	# Rewind tape
	$run_string = "$MT -f $TAPE_DRIVE rewind";
	if ($VERBOSE) {
		print "cxfsdump: exec ($run_string)\n";
	}
	$result = qx"$run_string";
	if ($result ne "") {
		print "cxfsdump: ERROR (cannot rewind tape: $result).\n";
		exit(2);
	}

	# Eject tape from device
	sleep(30);
	$run_string = "$MT -f $TAPE_DRIVE unload";
	if ($VERBOSE) {
		print "cxfsdump: exec ($run_string)\n";
	}
	$result = qx"$run_string";
	if ($result ne "") {
		print "cxfsdump: ERROR (cannot eject tape: $result).\n";
		exit(2);
	}

	# Unload tape from device
	sleep(30);
	$run_string = "$STACKER -u $slot $JUKEBOX";
	if ($VERBOSE) {
		print "cxfsdump: exec ($run_string)\n";
	}
	$result = qx"$run_string";

	if ($result ne "") {
		print "cxfsdump: ERROR (cannot unload tape: $result).\n";
		exit(2);
	}
}

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

package CONFIG;

######################################################################
##  SAVE CONFIGURATION FILE
######################################################################
sub save_configuration_file {
	my ($config_file) = @_;

	&write_to_config_file($config_file, "verbose", $VERBOSE);
	&write_to_config_file($config_file, "jukebox", $JUKEBOX);
	&write_to_config_file($config_file, "tape_drive", $TAPE_DRIVE);
	&write_to_config_file($config_file, "cluster", $CLUSTER_NAME);
	&write_to_config_file($config_file, "node", $NODE_NAME);
	&write_to_config_file($config_file, "node", $NODE_NAME);
	&write_to_config_file($config_file, "filesystems",
			join("+", @{$FILESYSTEMS}));
	my $set_string  = "";
	foreach my $set_arr (@{$DEVICE_SETS}) {
		$set_string .= "[".join(" ", @$set_arr)."]+";
	}
	$set_string =~ s/\+$//;
	&write_to_config_file($config_file, "sets", $set_string);
	&write_to_config_file($config_file, "last_set", $LAST_SET);
}

######################################################################
##  READ CONFIGURATION FILE
######################################################################
sub read_configuration_file {
	my ($config_file) = @_;

	# Hardware configuration
	$JUKEBOX = &try_to_read_from_config_file($config_file, "jukebox");
	$JUKEBOX = &configure_jukebox() if (!$JUKEBOX);
	$TAPE_DRIVE = &try_to_read_from_config_file($config_file, "tape_drive");
	$TAPE_DRIVE = &configure_tapedrive() if (!$TAPE_DRIVE);

	# Cluster configuration
	$CLUSTER_NAME = &try_to_read_from_config_file($config_file, "cluster");
	$CLUSTER_NAME = &configure_cluster() if (!$CLUSTER_NAME);
	$NODE_NAME = &try_to_read_from_config_file($config_file, "node");
	$NODE_NAME = &configure_node($CLUSTER_NAME) if (!$NODE_NAME);

	# Backup configuration
	my $filesystems_resp =
			&try_to_read_from_config_file($config_file, "filesystems");
	$filesystems_resp =
			&configure_filesystems($CLUSTER_NAME) if (!$filesystems_resp);
	$FILESYSTEMS = [split(/\s*\+\s*/, $filesystems_resp)];

	my $set_resp = &try_to_read_from_config_file($config_file, "sets");
	$set_resp = &configure_sets($JUKEBOX) if (!$set_resp);
	my @sets = split(/\s*\+\s*/, $set_resp);
	foreach my $set (@sets) {
		$set =~ s/[\[\]]//g;
		push(@{$DEVICE_SETS}, [split(/ +/, $set)]);
	}

	$LAST_SET = &try_to_read_from_config_file($config_file, "last_set");
}

######################################################################
##  TRY TO READ FROM CONFIG FILE
######################################################################
sub try_to_read_from_config_file {
	my ($config_file, $param) = @_;
	my $return = 0;

	open(CONFIG, "$config_file") || return 0;
	my @lines = <CONFIG>;
	map {$return = $1 if ($_ =~ /$param\s*=\s*(.+)$/i)} @lines;
	close(CONFIG);

	if ($return) {
		$return =~ s/^\s+//;
		$return =~ s/\s+$//;
	}

	return $return;
}

######################################################################
##  WRITE TO CONFIG FILE
######################################################################
sub write_to_config_file {
	my ($config_file, $param, @values) = @_;
	my @lines;

	return if (!@values);

	if (open(CONFIG, "$config_file")) {
		@lines = <CONFIG>;
		close(CONFIG);
	}

	map {$_ = "" if ($_ =~ /$param=\S+/i)} @lines;
	push(@lines, "$param=".join("+", @values)."\n");

	if (open(CONFIG, ">$config_file")) {
		print CONFIG @lines;
		close(CONFIG);
		chmod 0600, $config_file;
	} else {
		print STDERR "cxfsdump: WARNING (Cannot write config file)\n";
	}
}

######################################################################
##  CONFIGURE SYSTEM
##
##  Things to be configurated:
##
##  - jukebox device ($JUKEBOX)
##  - tape device ($TAPE_DRIVE)
##  - cluster name ($CLUSTER_NAME)
##  - node name ($NODE_NAME)
##  - cxfs filesytems ($FILESYSTEMS)
##  - tape sets ($DEVICE_SETS)
##  - default verbose level ($VERBOSE)
##
######################################################################
sub configure_system {
	my ($config_file) = @_;
	my $resp;

	print "Writting configuration to $config_file...\n";
	
	# Configure Jukebox device ($JUKEBOX)
	my $jukebox_device = &configure_jukebox($config_file);

	# Configure TAPE device ($TAPE_DRIVE)
	my $tape_device =  &configure_tapedrive($config_file);

	# Configure cluster name
	my $cluster =  &configure_cluster($config_file);

	# Configure node name
	my $node =  &configure_node($cluster, $config_file);
	
	# Configure cxfs_filesystems
	my @filesystems = &configure_filesystems($cluster, $config_file);
	
	# Configure sets of slots
	&configure_sets($jukebox_device, $config_file);

	exit(0);
}

######################################################################
##  CONFIGURE SETS
######################################################################
sub configure_sets {
	my ($jukebox_device, $config_file) = @_;
	my $resp;

	$resp = qx{$STACKER -c $jukebox_device};

	# Auto-conf mode
	if (!$config_file) {
		if ($resp =~ /(\d+) slot/) {
			my $number = $1;
			if ($number % 2 == 1) {
				$number--;
			}
			return "[".join(" ", (1 .. ($number/2)))."]+[".
					join(" ", ((1+$number/2) .. $number))."]";
		} else {
			return undef;
		}
	}

	if ($resp =~ /(\d+) slot/) {
		my $slots = $1;
		print "Your jukebox has $slots available slots.\n";
		print "Enter the number of sets you want to configure: ";
		$resp = <STDIN>;
		if ($resp =~ /(\d+)/) {
			my $number = $1;
			$slots -= ($slots % $number);
			my @sets;
			my $first = 1;
			my $last = ($slots / $number);
			for (my $a=0; $a<$number; $a++) {
				push(@sets, "[". join(" ", ($first .. $last)). "]");
				$first += ($slots / $number);
				$last += ($slots / $number);
			}
			&write_to_config_file($config_file, "sets", @sets);
		}
	} else {
		print "ERROR: I can't find any jukebox in $jukebox_device\n";
	}
}

######################################################################
##  CONFIGURE FILESYSTEMS
######################################################################
sub configure_filesystems {
	my ($cluster, $config_file) = @_;
	my $resp;

	$resp = qx{$CLUSTER_MGR -c "show cxfs_filesystems in cluster $cluster"};
	$resp =~ s/^\s+//;
	$resp =~ s/\s+$//;
	my @filesystems = split(/\s+/, $resp);
	my @filesystem_devices;
	foreach my $filesystem (@filesystems) {
		push(@filesystem_devices, "/dev/cxvm/$filesystem")
				if (-e "/dev/cxvm/$filesystem");
	}

	# Auto-conf mode
	if (!$config_file) {
		if (@filesystem_devices) {
			return join("+", @filesystem_devices);
		} else {
			return undef;
		}
	}

	if (@filesystem_devices) {
		&write_to_config_file($config_file, "filesystems", @filesystem_devices);
		print "FILESYSTEMS found:\n  - ",
				join ("\n  - ", @filesystem_devices), "\n";
		return @filesystems;
	} else {
		print "ERROR: I can't find any cxfs_filesystem in cluster $cluster\n";
		return undef;
	}
}

######################################################################
##  CONFIGURE NODE
######################################################################
sub configure_node {
	my ($cluster, $config_file) = @_;
	my $resp;

	my $node;
	my $hostname = qx{hostname};
	$resp = qx{$CLUSTER_MGR -c "show nodes in cluster $cluster"};
	$resp =~ s/^\s*Cluster \S+ has following \d+ machine\(s\)\s+//;
	my @machines = split(/\s+/, $resp);
	map {$node = $_ if ($hostname =~ /$_/)} @machines;

	# Auto-conf mode
	if (!$config_file) {
		if ($node) {
			return $node;
		} else {
			return undef;
		}
	}

	if ($node) {
		&write_to_config_file($config_file, "node", $node);
		print "NODE found: $node\n";
	} elsif (!@machines) {
		print "ERROR: I can't find any node in cluster $cluster\n";
	} else {
		print "Here is the list of available nodes:\n";
		print "  - ", join("\n  - ", @machines), "\n";
		print "Enter this node name :";
		$resp = <STDIN>;
		if ($resp =~ /(\S+)/) {
			$node = $1;
			&write_to_config_file($config_file, "node", $node);
		}
	}
}

######################################################################
##  CONFIGURE CLUSTER
######################################################################
sub configure_cluster {
	my ($config_file) = @_;
	my $resp;

	$resp = qx{$CLUSTER_MGR -c "show clusters"};

	# Auto-conf mode
	if (!$config_file) {
		if ($resp =~ /\s1 Cluster\(s\) defined\s+(\S+)/) {
			return $1;
		} else {
			return undef;
		}
	}

	my $cluster;
	if ($resp =~ /\s1 Cluster\(s\) defined\s+(\S+)/) {
		$cluster = $1;
		&write_to_config_file($config_file, "cluster", $1);
		print "CLUSTER found: $cluster\n";
	} else {
		print "Here is the list of available clusters:\n";
		$resp =~ s/^\s*\d+ Cluster\(s\) defined\s+//;
		print "  - ", join("\n  - ", split(/\s+/, $resp)), "\n";
		print "Enter cluster name :";
		$resp = <STDIN>;
		if ($resp =~ /(\S+)/) {
			$cluster = $1;
			&write_to_config_file($config_file, "cluster", $cluster);
		}
	}

	return $cluster;
}

######################################################################
##  CONFIGURE TAPEDRIVE
######################################################################
sub configure_tapedrive {
	my ($config_file) = @_;
	my $resp;

	# Auto-conf mode
	if (!$config_file) {
		if (-e "/dev/nrtape") {
			return "/dev/nrtape";
		} else {
			return undef;
		}
	}

	my $tape_device = "";
	if (-e "/dev/nrtape") {
		$tape_device = "/dev/nrtape";
		print "TAPE DRIVE found: $tape_device\n";
	} else {
		my @tapes = &get_rmt_devices();
		if (@tapes) {
			print "Here is the list of available tape devices:\n";
			foreach my $tape (@tapes) {
				if ($tape =~ /tps(\d)d(\d)nr$/) {
					print "  Tape drive: unit $2 on SCSI controller $1 [$tape].\n";
				} elsif ($tape =~ /tps(\d)d(\d)nrc$/) {
					print "  Tape drive: unit $2 on SCSI controller $1",
						" (with data compression) [$tape].\n";
				}
			}
		} else {
			print "I can't find any tape device in </dev/rmt/>.\n";
		}
		print "Enter tape device";
		print " [$tape_device]" if ($tape_device);
		print " :";
		$resp = <STDIN>;
		$tape_device = $1 if ($resp =~ /(\S+)/);
	}

	&write_to_config_file($config_file, "tape_drive", $tape_device);
	return $tape_device;
}

######################################################################
##  CONFIGURE JUKEBOX
######################################################################
sub configure_jukebox {
	my ($config_file) = @_;
	my $resp;

	my $hinv = qx"hinv";
	my @jukeboxes;
	foreach my $line (split(/[\r\n]+/, $hinv)) {
		push(@jukeboxes, $line) if ($line =~ /Jukebox:/i);
	}

	# Auto-conf mode
	if (!$config_file) {
		if (scalar(@jukeboxes) == 1) {
			return &get_scsi_device_from_hinv_description($jukeboxes[0]);
		} else {
			return undef;
		}
	}

	my $jukebox_device = "";
	if (!@jukeboxes) {
		print "I can't find any jukebox device in you hardware inventory.\n";
	} elsif (scalar(@jukeboxes) > 1) {
		print "I found several jukebox devices in you hardware inventory.\n";
		print "Here is the list of available tape robots:\n";
		foreach my $jukebox (@jukeboxes) {
			$jukebox_device = &get_scsi_device_from_hinv_description($jukebox);
			print " $jukebox [$jukebox_device]\n";
		}
		$jukebox_device = "";
	} else {
		$jukebox_device = &get_scsi_device_from_hinv_description($jukeboxes[0]);
		print "JUKEBOX found: $jukebox_device\n";
	}
	if (!$jukebox_device) {
		print "Enter manually the device node for controlling the robot.\n";
		print "Enter jukebox (tape robot) device: ";
		$resp = <STDIN>;
		$jukebox_device = $1 if ($resp =~ /(\S+)/);
	}

	&write_to_config_file($config_file, "jukebox", $jukebox_device);
	return $jukebox_device;
}

######################################################################
##  GET SCSI DEVICE FROM HINV DESCRIPTION
######################################################################
sub get_scsi_device_from_hinv_description {
	my ($hinv_description) = @_;
	my ($unit, $lun, $scsi);
	$unit='?';
	$lun='?';
	$scsi='?';

	$unit = $1 if ($hinv_description =~ /unit (\d)/i);
	$lun = $1 if ($hinv_description =~ /lun (\d)/i);
	$scsi = $1 if ($hinv_description =~ /scsi controller (\d)/i);
	my $dev = qx{ls /dev/scsi/sc${scsi}d${unit}l${lun}};
	$dev =~ s/\s//g;

	return $dev;
}

######################################################################
##  GET RMT DEVICES
######################################################################
sub get_rmt_devices {
	my @dev;
	my $resp = qx{ls /dev/rmt/tps?d?nr};
	@dev = split(/\s+/, $resp);
	$resp = qx{ls /dev/rmt/tps?d?nrc};
	push(@dev, split(/\s+/, $resp));

	return sort @dev;
}


=pod

=head1 NAME

cxfsdump - Dump CXFS filesystems using a jukebox (tape robot)

=head1 SYNOPSYS

B<cxfsdump> [B<-f=>CONFIG_FILE] [B<-set=>BACKUP_SET] [B<-v=>VERBOSE]
B<cxfsdump> [B<-f=>CONFIG_FILE] B<config>

=head1 PREREQUISITE

This script requires C<mt>, C<stacker>, C<cmgr> and C<xfsdump> programs. It
also needs a jukebox and a tape drive attached to the system. The configuration
module uses the C<hinv> command.

=head1 DESCRIPTION

This script does all the job of loading tapes with the stacker (the robotics
control program), relocating the metadataserver for the CXFS filesytems,
launching the xfsdump utility, rewinding and ejecting tape and unloading it with
the stacker again. If a xfsdump needs more than 1 tape, it will change
the tape and span the dump to the next tape.

The script is intended for CXFS filesystems but it is also able to dump local
filesytems.

Several sets of tapes can be configured. Each one will be used successively for
dumping all the configured filesystems. Current implementation does not support
incremental backups.

=head1 COMMANDS

=over 4

=item B<config>

Use this command the first time you use this program. It will try to find all
the needed information. You can combine this command with the -f option (see
below) for saving the configuration in a specific file.

=item B<E<lt>defaultE<gt>>

If no command is specified, the script launch the backup. It will use next set
of tapes, according to the configuration file (see below). If no configuration
file is found or some information is missing, the script will try to configure
itself automatically (see AUTO-CONFIGURATION below).

=back

=head1 OPTIONS

=over 4

=item B<-f=CONFIG_FILE>

Use CONFIG_FILE instead of F</etc/cxfsdump.conf>.

=item B<-set=SET_NUMBER>

You can configure several sets of tapes for backups. This flag allows you to
specify the set you want to use. By default, next set of tapes is used. This
information is stored in the CONFIG_FILE.

=item B<-v=VERBOSE>

Set the verbosity level to VERBOSE. Possible values are 0, 1 or 2.

=back

=head1 CONFIGURATION FILE FORMAT

Every line contains the name of a parameter and its value separated by an equal
symbol. Here is the list of available parameters:

=over 4

=item B<jukebox>

The name of the jukebox device. (/dev/scsi/sc1d1l0)

=item B<tape_drive>

The name of the tape device. (/dev/nrtape or /dev/rmt/tps1d2nrc)

=item B<cluster>

The name of the CXFS cluster. You can see the list of available clusters
using C<cmgr -c "show clusters">

=item B<node>

The name of this node in the CXFS cluster. You can see the list of available
nodes using C<cmgr -c "show nodes in cluster I<my_cluster>">

=item B<filesystems>

The name of CXFS filesystems you want to backup. You can see the list of
available CXFS filesystems using C<cmgr -c "show cxfs_filesystems in cluster
I<my_cluster>">, but you must provide the full path to the filesystem. The
filesystems must be separated a C<+>.

=item B<sets>

This is the most complicated parameter. Your jukebox (tape robot) should have
several slots for tapes. This parameter will tell the program which tape you
want to use for every backup. Each set is a list of slot numbers separated by
white spaces and enclosed by square brackets. The sets are separated by a C<+>.

For example for 10 slots divided into 2 sets of tapes:
sets=[1 2 3 4 5]+[6 7 8 9 10]

For example for 9 slots divided into 3 sets of tapes:
sets=[1 2 3]+[4 5 6]+[7 8 9]

=item B<verbose>

The verbosity level. Available values are 0, 1 or 2.

=item B<last_set>

The last set dumped. This values will be used to dump next set next time.

=back

=head1 CONFIGURATION FILE EXAMPLE

  jukebox=/dev/scsi/sc1d1l0
  tape_drive=/dev/nrtape
  cluster=my_cluster
  node=my_cxfs_server
  filesystems=/dev/cxvm/my_raid0+/dev/cxvm/my_raid1+/dev/cxvm/my_raid2
  sets=[1 2 3]+[4 5 6]+[7 8 9]

=head1 AUTO-CONFIGURATION

If your hardware configuration is simple enough you can even skip the
configuration step: if you have only one jukebox, only one tape drive, only one
cluster defined and the name of the backup host (this host) in the cluster
configuration is the same as its hostname, the script will use all of this and
dump all available CXFS filesystems. By default it will split available slots
into two sets and dump files to the first one.


=head1 COMPLEX HARDWARE CONFIGURATIONS

C<cxfsdump> is intended for simple configuration, i.e., 1 jukebox with 1 tape
device for dumping all the CXFS filesystems of the unique cluster available, but
you can easily fit your hardware needings by using several configuration files,
one for each replicated device.

=head1 LAUNCH CXFSDUMP FROM CRON

Since the last used set number is saved in the configuration file, you can use
the same command line for all the sets. This feature allows to run the dump, let
say weekly from cron:

This will run cxfsdump (you maybe need to prepend the complete path) every
Saturday. If you configured several sets of tapes, it will use a different set
every week.

  15 20 * * 6 cxfsdump

If you have got two tape device in your jukebox and made two configuration
files for dumping different CXFS filesystems with each tape:

  15 20 * * 6 cxfsdump -f /etc/cxfsdump_tape1.conf
  15 20 * * 6 cxfsdump -f /etc/cxfsdump_tape2.conf



=head1 KNOWN BUGS

C<xfsdump> must be executed from the metadataserver of the CXFS filesystem to be
dumped. C<cmgr> does no show which node is the present metadataserver, so this
scripts tries always to relocate it to the present node. If the present node is
the metadaserver already you will get a message like C<Filesystem "/cxfs/raid0":
I<my_cxfs_server> is the server already> in the SYSLOG file. You can ignore it.

Incremental backups are not implemented yet.

I did not fully checked the configuration module. I am sorry but I cannot test
all the possible hardware configurations. I hope it will be OK in your case.
Drop me an email if something goes wrong...

=head1 FILES

F</etc/cxfsdump.conf>

F</dev/cxvm/*>

F</dev/scsi/sc*>

F</dev/rmt/tps*>

=head1 AUTHOR

Javier Herrero E<lt>jherrero@cnio.esE<gt>

=head1 WARRANTY

Just forget it. This script comes with absolutely no warranty. You can use it
if you like it and find it useful but do not blame me if something goes wrong.


In any case I recommend you to use the maximum verbosity level at the beginning
and check that everything is OK.

=head1 SEE ALSO

L<cmgr|cmgr>, L<stacker|stacker>, L<mt|mt>, L<hinv|hinv>.

L<cron|cron>, L<crontab|crontab>.


=head1 COPYRIGHT AND LICENSE

Copyright (c) 2002, Javier Herrero. All Rights Reserved.

This program is free software; you can redistribute it and/or modify it under
the same terms as Perl.

=begin comment

=head1 README

This script is intended for dumping CXFS filesystems using a jukebox
(tape robot)

It does all the job of loading tapes with the stacker (the robotics
control program), relocating the metadataserver for the CXFS filesytems,
launching the xfsdump utility, rewinding and ejecting tape and unloading it with
the stacker again. If a xfsdump needs more than 1 tape, it is able to change
the tape and span the dump to the next tape.

The script is intended for CXFS filesystems but it is also able to dump local
filesytems.

Several sets of tapes can be configured. Each one will be used successively for
dumping all the configured filesystems. Current implementation does not support
incremental backups.

=pod OSNAMES

Irix

=pod SCRIPT CATEGORIES

UNIX/System_administration

=cut

