#!/usr/bin/perl -w
#
# Analyze the LVM configuration
# and stor the configuration for restore time
#
# $Id$
#
# Copyright B. Cornec 2008
# Provided under the GPL v2

# Syntax: see below

use strict 'vars';
use Getopt::Long qw(:config auto_abbrev no_ignore_case);
use Data::Dumper;
use English;
use File::Basename;
use File::Copy;
use File::stat;
use File::Temp qw(tempdir);
use POSIX qw(strftime);
use lib qw (lib);
use ProjectBuilder::Base;
use ProjectBuilder::Distribution;

=pod

=head1 NAME

Analyze-lvm - A Mindi Tool to analyze the LVM configuration and store it

=head1 DESCRIPTION

B<anlyze-lvm> prints all the information related to the LVM configuration that may be used by a restoration process

=head1 SYNOPSIS

analyze-lvm [-v]|[-q]|[-h]|[--man]

=head1 OPTIONS

=over 4

=item B<-v|--verbose>

Print a brief help message and exits.


=item B<-q|--quiet>

Do not print any output.

=item B<-h|--help>

Print a brief help message and exits.

=item B<--man>

Prints the manual page and exits.

=item B<-i|--iso iso_image>

Name of the ISO image you want to created.

=back 

=head1 WEB SITES

The main Web site of the project is available at L<http://www.mondorescue.org/>. Bug reports should be filled using the trac instance of the project at L<http://trac.mondorescue.org/>.

=head1 USER MAILING LIST

The miling list of the project is available at L<mailto:mondo@lists.sf.net>
 
=head1 AUTHORS

The Mondorescue.org team L<http://www.mondorescue.org/> lead by Bruno Cornec L<mailto:bruno@mondorescue.org>.

=head1 COPYRIGHT

Analyze-LVM is distributed under the GPL v2.0 license
described in the file C<COPYING> included with the distribution.

=cut


# ---------------------------------------------------------------------------

# Initialize the syntax string

pb_syntax_init("analyze-lvm Version PBVER-rPBREV\n");

# Handle options
#
GetOptions("help|?|h" => \$opts{'h'}, 
		"man" => \$opts{'man'},
		"verbose|v+" => \$opts{'v'},
		"quiet|q" => \$opts{'q'},
		"log-files|l=s" => \$opts{'l'},
		"version|V" => \$opts{'V'},
) || pb_syntax(-1,0);

# easy options
if (defined $opts{'h'}) {
	pb_syntax(0,1);
}
if (defined $opts{'man'}) {
	pb_syntax(0,2);
}
if (defined $opts{'v'}) {
	$pbdebug = $opts{'v'};
}
if (defined $opts{'q'}) {
	$pbdebug=-1;
}

#
# Global variables
#
my $MINDI_VERSION = "PBVER-rPBREV";
my $MINDI_PREFIX = "PBPREFIX";
my $MINDI_CONF = "PBCONF";
my $MINDI_LIB = "PBLIB";
my $MINDI_SBIN = "$MINDI_PREFIX/sbin";
# 
# Temp dir
#
pb_temp_init();

my $lvmds = "/usr/sbin/lvmdiskscan";
my $lvmproc = "/proc/lvm/global";
my $lvmcmd = "/usr/sbi/lvm";

# -------------------------------- main -----------------------------------
mr_exit(-1,"$lvmds doesn't exist. No LVM handling.") if ((! -x $lvmds) ;
mr_exit(-1,"$lvmproc doesn't exist. No LVM handling.") if ((! -x $lvmproc) ;

# Check LVM volumes presence
open(LVM,$lvmproc) || mr_exit(-1,"Unable to open $lvmproc");
while (<LVM>) {
	mr_exit(1,"No LVM volumes found in $lvmproc") if (/0 VGs 0 PVs 0 LVs/);
}
close(LVM);

# Check LVM version
my $lvmver=0;
open(LVM,"$lvmds --help 2>&1 |") || mr_exit(-1,"Unable to execute $lvmds");
while (<LVM>) {
		if (/Logical Volume Manager/ || /LVM version:/) {
				$lvmver = $_;
				$lvmver =~ s/:([0-9])\..*/$1/;
		}
}
close(LVM);
#lvmversion=`lvmdiskscan --help 2>&1 |
#grep -E "Logical Volume Manager|LVM version:" |
#cut -d: -f2 | cut -d. -f1 |
#awk '{print $NF}' |
#sed -e 's/ //g'`

if ($lvmver = 0) {
	# Still not found
	if (-x $lvmcmd) {
		open(LVM,"$lvmcmd version |") || mr_exit(-1,"Unable to execute $lvmcmd");
		while (<LVM>) {
			if (/LVM version/) {
				$lvmver = $_;
				$lvmver =~ s/LVM version ([0-9])\..*/$1/;
			}
		}
		close(LVM);
	}
}

if ($lvmver = 0) {
	# Still not found
	mr_exit(-1,"Unable to determine LVM version.\nPlease report to the dev team with the result of the commands\n$lvmds and $lvmvmd version");
} elsif ($lvmver = 1) {
	$lvmcmd = "";
}
pb_log(0,"Found LVM version $lvmver");

# For the moment on stdout
OUTPUT = \*STDOUT;

# Generate the startup scrit on the fly needed to restore LVM conf
print OUTPUT "# Desactivate Volume Groups\n";
print OUTPUT "$lvmcmd vgchange -an\n";
print OUTPUT "\n";

# Analyze the existing physical volumes
my @lvmpvs = ();
open(LVM,"$lvmcmd pvdisplay |") || mr_exit(-1,"Unable to execute $lvmcmd pvdisplay");
while (<LVM>) {
		if (/PV Name/) {
				my $lvmpv = $_;
				$lvmpv =~ s/^\s*PV Name ([A-z0-9/])/$1/;
		}
		push($lvmpv,@lvmpvs);
}
close(LVM);

ListAllPhysicalVolumes() {
	if [ $lvmversion = 2 ]; then
		$LVMCMD pvscan 2> /dev/null | grep 'PV' | awk '{print $2}'
	else
		pvscan 2> /dev/null | grep '"' | cut -d'"' -f2
	fi
}
print OUTPUT "# Creating Physical Volumes\n";
foreach my $pv (@lvmpvs) {
	print OUTPUT "echo y | $lvmcmd pvcreate -ff $pv\n";
}
print OUTPUT "\n";

print OUTPUT "# Scanning again Volume Groups\n";
print OUTPUT "$lvmcmd vgscan\n";
print OUTPUT "\n";

# Analyze the existing volume groups
print OUTPUT "# Creating Volume Groups and Activating them\n";
my @lvmvgs = ();
open(LVM,"$lvmcmd vgdisplay |") || mr_exit(-1,"Unable to execute $lvmcmd vgdisplay");
while (<LVM>) {
		if (/VG Name/) {
				my $lvmvg = $_;
				$lvmvg =~ s/^\s*VG Name ([A-z0-9/])/$1/;
				#$LVMCMD vgdisplay 2> /dev/null | awk '/^ *VG Name/ {print $3;}'
		}
		push($lvmvg,@lvmvgs);
}
close(LVM);

foreach my $vg (@lvmvgs) {
	if ($lvmver < 2) {
		print OUTPUT "# Removing device first as LVM v1 doesn't do it\n";
		print OUTPUT "rm -Rf /dev/$vg\n";
	}
	mr_lvm_create_vg($vg);
}

sub mr_lvm_create_vg {

my $vg = shift;

$vg_params=`GenerateVgcreateParameters $vg`
GenerateVgcreateParameters() {
	local current_VG device fname incoming VG_info_file max_logical_volumes max_physical_volumes physical_extent_size output blanklines
	current_VG=$1
	VG_info_file=$MINDI_TMP/$$.vg-info.txt
	$LVMCMD vgdisplay $current_VG > $VG_info_file
	max_logical_volumes=`GetValueFromField "$VG_info_file" "MAX LV"`
	[ $max_logical_volumes -ge 256 ] && max_logical_volumes=255
	max_physical_volumes=`GetValueFromField "$VG_info_file" "MAX PV"`
	[ $max_physical_volumes -ge 256 ] && max_physical_volumes=255
	physical_extent_size=`GetValueFromField "$VG_info_file" "PE Size"`
	output=""
	[ "$max_logical_volumes" ]  && output="$output -l $max_logical_volumes"
	[ "$max_physical_volumes" ] && output="$output -p $max_physical_volumes"
	[ "$physical_extent_size" ] && output="$output -s $physical_extent_size"
	echo "$output"
	rm -f $VG_info_file
}


if ($lvmver = 1) {
	$info_file = "/proc/lvm/VGs/$vg/group";
	$pvs=`ls /proc/lvm/VGs/$vg/PVs`
		$list_of_devices=""
		for i in $pvs ; do
	    	fname=/proc/lvm/VGs/$vg/PVs/$i
	    	device=`GetValueFromField $fname "name:"`
	    	$list_of_devices="$list_of_devices $device"
		done
} elsif ($lvmver = 2) {
		$list_of_devices=`$lvmcmd pvs | grep " $vg " | awk '{print $1}'`
}
	print OUTPUT "# Create Volume Group $vg\n";
	print OUTPUT "$lvmcmd vgcreate $vg $vg_param $list_of_devices\n";
	print OUTPUT "# Activate Volume Group $vg\n";
	print OUTPUT "$lvmcmd vgchnge -a y $vg\n";
}

echo "Finally, create the LV's (logical volumes)."
all_logical_volumes=`ListAllLogicalVolumes`
for current_LV in $all_logical_volumes ; do
	ProcessLogicalVolume $current_LV
done
echo ""
echo "# $LVMCMD vgscan"
echo "Now you may format the LV's:-"
for i in `ListAllLogicalVolumes` ; do
	echo "(mkfs -t foo $i or something like that)"
done
WriteShutdownScript
exit 0


GetValueFromField() {
	local res
	sed s/'    '/~/ "$1" | tr -s ' ' ' ' | sed s/'~ '/'~'/ | grep -i "$2~" | cut -d'~' -f2,3,4,5 | tr '~' ' ' | gawk '{ if ($2=="MB") {printf "%dm",$1;} else if ($2=="KB") {printf "%dk",$1;} else if ($2=="GB") {printf "%fg",$1;} else {print $0;};}'
}


GetLastBit() {
	local i res
	i=20
	res=""
	while [ ! "$res" ] ; do
		i=$(($i-1))
		res=`echo "$1" | cut -d'/' -f$i`
	done
	echo "$res"
}


ProcessLogicalVolume() {
	local LV_full_string fname logical_volume volume_group device
	LV_full_string=$1
	[ ! -e "$1" ] && Die "Cannot find LV file $1"
	volume_group=`echo "$LV_full_string" | cut -d'/' -f3`
	logical_volume=`echo "$LV_full_string" | cut -d'/' -f4`
	if [ $lvmversion = 2 ]; then
		device=$LV_full_string
		params=`GenerateLvcreateParameters $device`
	else
		fname=/proc/lvm/VGs/$volume_group/LVs/$logical_volume
		if [ ! -e "$fname" ] ; then
	    	echo "Warning - cannot find $volume_group's $logical_volume LV file"
		else
	    	device=`GetValueFromField $fname "name:"`
	    	params=`GenerateLvcreateParameters $device`
		fi
	fi
	echo "# $LVMCMD lvcreate$params -n $logical_volume $volume_group"
}


GenerateLvcreateParameters() {
	local device stripes stripesize device fname allocation output readahead
	fname=$MINDI_TMP/PLF.$$.txt
	device=$1
	output=""
	$LVMCMD lvdisplay $device > $fname
	stripes=`GetValueFromField $fname "Stripes"`
	stripesize=`GetValueFromField $fname "Stripe size (MByte)"`m
	[ "$stripesize" = "m" ] && stripesize=`GetValueFromField $fname "Stripe size (KByte)"`k
	[ "$stripesize" = "k" ] && stripesize=""
	allocation=`GetValueFromField $fname "LV Size"`
	[ ! "`echo "$allocation" | grep "[k,m,g]"`" ] && allocation="$allocation"m
	if echo "$allocation" | grep -E '^.*g$' > /dev/null 2> /dev/null ; then
		val=`echo "$allocation" | sed s/g//`
		allocation=`echo "$val" | awk '{c=$1; printf "%d", c*1024;}'`m
	fi
	readahead=`GetValueFromField $fname "Read ahead sectors"`
	rm -f $fname
	[ "$stripes" ]    && output="$output -i $stripes"
	[ "$stripesize" ] && output="$output -I $stripesize"
	[ "$allocation" ] && output="$output -L $allocation"
	[ "$readahead" ]  && output="$output -r $readahead"
	echo "$output"
}






ListAllLogicalVolumes() {
	if [ $lvmversion = 2 ]; then
		$LVMCMD lvscan 2> /dev/null | grep "'" | cut -d"'" -f2
	else
		lvscan 2> /dev/null | grep '"' | cut -d'"' -f2
	fi
}



WriteShutdownScript() {
	local i
	echo ""
	echo "Finally, to shut down and delete the volumes, do this:-"
	for i in `ListAllLogicalVolumes` ; do
	    echo "($LVMCMD lvremove -f $i)"
	done
	for i in `ListAllVolumeGroups` ; do
	    echo "($LVMCMD vgchange -a n $i)"
	done
	for i in `ListAllVolumeGroups` ; do
	    echo "($LVMCMD vgremove $i)"
	done
	if [ $lvmversion = 2 ]; then
		echo "(rmmod dm-mod & rmmod dm_mod & )"
	else
		echo "(rmmod lvm-mod)"
	fi
}



