# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/lpp/bosinst/samples/NIM/Deploy/Mksysb/ImageData.pm 1.1 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2008,2009 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 

# @(#)55        1.1  src/bos/usr/lpp/bosinst/samples/NIM/Deploy/Mksysb/ImageData.pm, bosinst, bos720 3/29/09 15:20:49

package NIM::Deploy::Mksysb::ImageData;

=head1 NAME

NIM::Deploy::Mksysb::ImageData

=over 2

=item

Class for parsing and modifying an image.data file.

=back

=head1 VARIABLES

=head1 FUNCTIONS

=cut

#-------------------------------------------------------------------------------
BEGIN
{
  # print "NIM::Deploy::Mksysb::ImageData BEGIN\n";

  use NIM::Deploy;
  use Hash::Util;
  use File::Basename;
  use NIM::Util qw(:DEFAULT !log_print !log_printf);
  use POSIX qw(ceil floor);
  use Math::BigInt;

  *log_print = \&NIM::Deploy::log_print;
  *log_printf = \&NIM::Deploy::log_printf;
}

#-------------------------------------------------------------------------------
INIT
{
  # print "NIM::Deploy::Mksysb::ImageData INIT\n";

  # use Config; 
  # ($Config{use64bitint} eq 'define' || $Config{longsize} >= 8) && print "Supports 64-bit numbers\n";
}

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

=head2 new

=over 2

=item

C<new NIM::Deploy::Mksysb::ImageData($file_name)>

=back

=head3 DESCRIPTION

=over 2

=item

Creates a new instance of NIM::Deploy::Mksysb::ImageData.

=back

=head3 PARAMETERS

=over 2

=item * $file_name (REQUIRED)

=back

=head3 RETURNS

=over 2

=item * Reference to the newly created object.

=back

=head3 EXCEPTIONS

=cut

#-------------------------------------------------------------------------------
sub new
{
  my $type = shift;
  my ($file_name) = @_;

  ###

  log_print("NIM::Deploy::Mksysb::ImageData::new(\'$file_name\')\n");

  ###

  my $this = {};
  bless $this, $type;

  $this->{'file_name'} = $file_name;
  $this->{'content'} = $this->parse_file($file_name);

  ###

  return $this;
}

#-------------------------------------------------------------------------------
sub parse_file
{
  my $this = shift;
  my ($file_name) = @_;

  log_print "NIM::Deploy::Mksysb::ImageData::parse_file(\'$file_name\')\n";

  my %rc = ();
  open(IMAGE_DATA, "<$file_name") || die "Can't open $file_name: $!";

  my $lineno = 0;
  my $stanza_name = '';
  my $stanza = undef;
  my $key = '';

  while (<IMAGE_DATA>)
  {
    my $line = $_;
    chomp($line);

    # print "line = \'$line\'\n";	# yyy

    $lineno++;
    if (($line =~ m/^[\s]*#.*$/) || ($line =~ m/^[\s]*$/))
    {
    }
    elsif ($line =~ m/^[\s]*([^\s]+):[\s]*$/)
    {
      $stanza_name = $1;
      $stanza = {};
      
      # print "stanza = \'$stanza_name\'\n"; # yyy

      if (! defined $rc{$stanza_name})
      {
	$rc{$stanza_name} = [];
      }
      push @{$rc{$stanza_name}}, ($stanza);

      $key = '';
    }
    elsif ((defined $stanza) && ($line =~ m/^[\s]*([^\s]+)[\s]*=[\s]*(.*)[\s]*$/))
    {
      $stanza->{$1} = $2;

      # print "\t\'$1\' = \'$2\'\n"; # yyy
    }
    else
    {
      warn "Error parsing file $file_name / line ${lineno}: \'$line\'";
    }
  }
  close(IMAGE_DATA);
  return \%rc;
}

#-------------------------------------------------------------------------------
sub print
{
  my $this = shift;
  my ($fh) = @_;
  if (! $fh)
  {
    $fh = STDOUT;
  }

  log_print "NIM::Deploy::Mksysb::ImageData::print()\n";
  
  my @stanza_sections = ('image_data',
			 'logical_volume_policy',
			 'ils_data',
			 'vg_data',
			 'source_disk_data',
			 'lv_data',
			 'fs_data',
			 'post_install_data',
			 'post_restvg');

  my $stanza_hash = $this->{'content'};

  # while (my ($stanza_name, $stanzas) = each(%$stanza_hash));
  foreach my $stanza_name (@stanza_sections)
  {
    my $stanzas = $stanza_hash->{$stanza_name};

    foreach my $stanza (@$stanzas)
    {
      print $fh "$stanza_name:\n";
      while (my ($key, $value) = each(%$stanza))
      {
	print $fh "\t$key= $value\n";
      }
      print $fh "\n";
    }
  }
}

#-------------------------------------------------------------------------------
sub vg_disk_names
{
  my $this = shift;

  my @rc = ();
  eval
  {
    my $disks = $this->{'content'}->{'vg_data'}->[0]->{'VG_SOURCE_DISK_LIST'};
    @rc = split(/[ \t]+/, $disks);
  };
  return ($@ ? () : @rc);
}

#-------------------------------------------------------------------------------
sub bint
{
  Math::BigInt->new(shift);
}

#-------------------------------------------------------------------------------
sub disk_size
{
  my $this = shift;
  my ($disk_name) = @_;

  log_print "NIM::Deploy::Mksysb::ImageData::disk_size(\'$disk_name\')\n";

  my $rc = bint(0);
  eval
  {
    foreach my $source_disk (@{$this->{'content'}->{'source_disk_data'}})
    {
      if ($source_disk->{'HDISKNAME'} eq $disk_name)
      {
	$rc = bint($source_disk->{'SIZE_MB'} * 1024 * 1024);
      }
    }
  };
  $rc = ($@ ? bint(0) : $rc);

  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub num_disks
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::num_disks()\n";

  my @disk_names = $this->vg_disk_names();
  my $rc = ($#disk_names >= 0) ? ($#disk_names + 1) : 0;
  
  log_print"\trc = $rc\n";
  return $rc;
}

#-------------------------------------------------------------------------------
sub image_size
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::image_size()\n";

  my $rc = bint(0);
  my @disk_names = $this->vg_disk_names();

  foreach my $disk_name (@disk_names)
  {
    $rc += $this->disk_size($disk_name);
  }
  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub lps_to_fs_size
{
  my $this = shift;
  my ($lps, $lv_data) = @_;

  #  FS_SIZE = (# of 512 blocks) = PP_SIZE * (# of 512 blocks per PP) * LPs * (PPs / LPs)
  # size in bytes = FS_SIZE * (512 bytes / block)

  my $fs_size = bint(0);
  eval
  {
    $fs_size = (bint($lv_data->{'PP_SIZE'}) * 1024) * 2 * $lps * ($lv_data->{'PP'} / $lv_data->{'LPs'});
  };
  return ($@ ? bint(0) : $fs_size);
}

#-------------------------------------------------------------------------------
sub lps_to_size
{
  my $this = shift;
  my ($lps, $lv_data) = @_;

  # size in bytes = FS_SIZE * (512 bytes / block)

  return ($this->lps_to_fs_size($lps, $lv_data) * 512);
}

#-------------------------------------------------------------------------------
sub allocated_size			# includes all logical volumes
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::allocated_size()\n";

  my $rc = bint(0);
  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      log_print("\tprocessing logical volume $lv_data->{'LOGICAL_VOLUME'}\n");
      log_print("\tLPs = $lv_data->{'LPs'}\n");
      
      my $size = $this->lps_to_size($lv_data->{'LPs'}, $lv_data);
      log_printf("\tsize = %s\n", $size->bstr());
      $rc += $size;
    }
  };
  $rc = ($@ ? bint(0) : $rc);

  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub unallocated_size			# includes all logical volumes
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::unallocated_size()\n";
  my $rc = $this->image_size() - $this->allocated_size();
  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub free_space_size
{
  my $this = shift;
  my ($min_lp_delta) = @_;

  if (! defined $min_lp_delta)
  {
    $min_lp_delta = 0;
  }

  log_print "NIM::Deploy::Mksysb::ImageData::free_space_size(\'$min_lp_delta\')\n";

  my $rc = bint(0);
  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      if ($lv_data->{'FS_TAG'})
      {
	log_print "\tprocessing logical volume $lv_data->{'LOGICAL_VOLUME'}\n";
	log_print "\tLPs = $lv_data->{'LPs'} / LV_MIN_LPS = $lv_data->{'LV_MIN_LPS'}\n";

	if (($lv_data->{'LPs'} - $lv_data->{'LV_MIN_LPS'}) > $min_lp_delta)
	{
	  my $fs_free_space = $this->lps_to_size($lv_data->{'LPs'} - ($lv_data->{'LV_MIN_LPS'} + $min_lp_delta), $lv_data);
	  $rc += $fs_free_space;

	  log_printf("\tfree space = %s\n", $fs_free_space->bstr());
	}	  
      }
    }
  };
  $rc = ($@ ? bint(0) : $rc);

  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub set_min_free_space_lps_per_filesystem
{
  my $this = shift;
  my ($min_lp_delta) = @_; # $min_lp_delta is the required free space LPs available per filesystem logical volume

  log_print "NIM::Deploy::Mksysb::ImageData::set_min_free_space_lps_per_filesystem(\'$min_lp_delta\')\n";
  
  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      if ($lv_data->{'FS_TAG'})
      {
	my $lv_name = $lv_data->{'LOGICAL_VOLUME'};
	log_print "\tprocessing logical volume: $lv_name\n";
	log_print "\tLPs = $lv_data->{'LPs'} / LV_MIN_LPS = $lv_data->{'LV_MIN_LPS'}\n";
	
	my $lv_min_lps = $lv_data->{'LV_MIN_LPS'} + $min_lp_delta;
	if ($lv_data->{'LPs'} < $lv_min_lps)
	{
	  if ($lv_min_lps > $lv_data->{'MAX_LPS'})
	  {
	    $lv_min_lps = $lv_data->{'MAX_LPS'};
	  }

	  my $lps = $lv_min_lps;
	  my $pps = $lps * $lv_data->{'PP'} / $lv_data->{'LPs'};
	  
	  $lv_data->{'LPs'} = $lps;
	  $lv_data->{'PP'} = $pps;
	  
	  log_print "\tsetting LPs = $lps / PP = $pps\n";
	  
	  my $pp_size = $lv_data->{'PP_SIZE'} * 1024;
	  foreach my $fs_data (@{$this->{'content'}->{'fs_data'}})
	  {
	    if ($fs_data->{'FS_LV'} eq "/dev/$lv_name")
	    {
	      log_print "\tprocessing filesystem: /dev/$lv_name\n";
	      log_print "\tFS_SIZE = $fs_data->{'FS_SIZE'}\n";
	      
	      my $fs_size = bint($pps) * $pp_size;
	      $fs_size *= 2; # fs_size == number of 512-byte blocks, 2 per pp
	      $fs_data->{'FS_SIZE'} = $fs_size;
	      
	      log_printf("\tsetting FS_SIZE = %s\n", $fs_size->bstr());
	      last;
	    }
	  }
	}
      }
    }
  }
}

#-------------------------------------------------------------------------------
sub scale_fs_down
{
  my $this = shift;
  my ($target_size_mb, $min_lp_delta) = @_; # $min_lp_delta is the desired free space LPs available per filesystem logical volume

  log_printf("NIM::Deploy::Mksysb::ImageData::scale_fs_down(\'%llu MB\', \'$min_lp_delta\')\n", $target_size_mb);
  my $target_size = bint($target_size_mb) * 1024 * 1024;

  my $alloced_size = $this->allocated_size();
  my $reduce_amount = ($alloced_size > $target_size) ? ($alloced_size - $target_size) : bint(0);
  
  log_printf("\ttargeted reduction amount = %s (%s - %s)\n",
	     $reduce_amount->bstr(), $alloced_size->bstr(), $target_size->bstr());
  
  if ($reduce_amount > 0)
  {
    if ($this->num_disks() == 1)
    {
      if (! $min_lp_delta)
      {
	$min_lp_delta = 4;
      }
      my $free_space = $this->free_space_size($min_lp_delta);
      
      if ($free_space > 0)
      {
	if ($reduce_amount <= $free_space)
	{
	  my $reduce_percent = ($reduce_amount * 100) / $free_space;
	  if (($reduce_amount * 100) % $free_space)
	  {
	    $reduce_percent++;
	  }
	  log_print "\treduce percent = $reduce_percent\n";
	  my $percent = 100 - $reduce_percent;

	  eval
	  {
	    my $freed_bytes = bint(0);
	    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
	    {
	      if ($lv_data->{'FS_TAG'})
	      {
		my $lv_name = $lv_data->{'LOGICAL_VOLUME'};
		log_print "\tprocessing logical volume: $lv_name\n";
		log_print "\tLPs = $lv_data->{'LPs'} / LV_MIN_LPS = $lv_data->{'LV_MIN_LPS'}\n";

		my $lv_min_lps = $lv_data->{'LV_MIN_LPS'} + $min_lp_delta;
		if ($lv_data->{'LPs'} > $lv_min_lps)
		{
		  my $free_lps = ($percent * ($lv_data->{'LPs'} - $lv_min_lps)) / 100;
		  my $lps = $lv_min_lps + $free_lps;
		  my $pps = $lps * $lv_data->{'PP'} / $lv_data->{'LPs'};
		  
		  my $lv_freed_bytes = $this->lps_to_size($lv_data->{'LPs'} - $lv_min_lps - $free_lps, $lv_data);
		  $freed_bytes += $lv_freed_bytes;
		  
		  $lv_data->{'LPs'} = $lps;
		  $lv_data->{'PP'} = $pps;
		  
		  log_print "\tsetting LPs = $lps / PP = $pps\n";
		  
		  my $pp_size = $lv_data->{'PP_SIZE'} * 1024;
		  foreach my $fs_data (@{$this->{'content'}->{'fs_data'}})
		  {
		    if ($fs_data->{'FS_LV'} eq "/dev/$lv_name")
		    {
		      log_print "\tprocessing filesystem: /dev/$lv_name\n";
		      log_print "\tFS_SIZE = $fs_data->{'FS_SIZE'}\n";
		      
		      my $fs_size = bint($pps) * $pp_size;
		      $fs_size *= 2; # fs_size == number of 512-byte blocks, 2 per pp
		      $fs_data->{'FS_SIZE'} = $fs_size;
		      
		      log_printf("\tsetting FS_SIZE = %s\n", $fs_size->bstr());
		      last;
		    }
		  }
		}
	      }
	    }
	    
	    log_printf("\tactual reduction amount = %s\n", $freed_bytes->bstr());

	    # my $source_disk = $this->{'content'}->{'source_disk_data'}->[0];
	    # $source_disk->{'SIZE_MB'} = $this->allocated_size() / (1024 * 1024);
	  };
	}
	elsif ($reduce_amount > $free_space)
	{
	  log_print "\tthe reduction amount required is larger than the available free space\n";

	  # $this->{'content'}->{'logical_volume_policy'}->[0]->{'SHRINK'} = 'yes';
	}
      }
      else
      {
	log_print "\tno free space available for reduction\n";
      }
    }
    else
    {
      log_print "\tmore than one disk specified in the image.data file - no action performed\n";
    }
  }
  else
  {
    log_print "\ttarget size is already large enough to contain the logical volume sizes\n";
  }
}

#-------------------------------------------------------------------------------
sub shrink_size
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::shrink_size()\n";

  my $rc = bint(0);
  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      $rc += $this->lps_to_size($lv_data->{'LV_MIN_LPS'}, $lv_data);
    }
  };
  $rc = ($@ ? bint(0) : $rc);

  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub paging_space_size
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::paging_space_size()\n";

  my $rc = bint(0);
  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      if ($lv_data->{'TYPE'} eq 'paging')
      {
	$rc += $this->lps_to_size($lv_data->{'LPs'}, $lv_data);
	# last;
      }
    }
  };
  $rc = ($@ ? bint(0) : $rc);

  log_printf("\trc = %s\n", $rc->bstr());
  return $rc;
}

#-------------------------------------------------------------------------------
sub num_paging_space
{
  my $this = shift;

  log_print "NIM::Deploy::Mksysb::ImageData::num_paging_space()\n";

  my $rc = 0;
  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      if ($lv_data->{'TYPE'} eq 'paging')
      {
	$rc++;
      }
    }
  };
  $rc = ($@ ? 0 : $rc);

  log_printf("\trc = %d\n", $rc);
  return $rc;
}

#-------------------------------------------------------------------------------
sub resize_paging_space
{
  my $this = shift;
  my ($target_size_mb) = @_;

  log_printf("NIM::Deploy::Mksysb::ImageData::resize_paging_space(\'%llu MB\')\n", $target_size_mb);

  my $target_size = bint($target_size_mb) * 1024 * 1024;

  my $num_ps = $this->num_paging_space();
  my $ps_size = $this->paging_space_size();

  my $divided_ps_size = $target_size / $num_ps;
  if ($divided_ps_size <= 0)
  {
    $divided_ps_size = bint(1);
  }
  log_printf("\teach paging space size = %s\n", $divided_ps_size->bstr());

  eval
  {
    foreach my $lv_data (@{$this->{'content'}->{'lv_data'}})
    {
      if ($lv_data->{'TYPE'} eq 'paging')
      {
	my $pp_size = $lv_data->{'PP_SIZE'} * 1024;
	my $pp_lp_ratio = $lv_data->{'PP'} / $lv_data->{'LPs'}; # account for mirroring

	my $lp_size = $pp_size * $pp_lp_ratio;

	$lp_size *= 2;		# fs_size == number of 512-byte blocks, 2 per pp
	$lp_size *= 512;	# size in bytes

	my $lps = $divided_ps_size / $lp_size;
	if ($lv_data->{'MAX_LPS'} < $lps)
	{
	  $lps = $lv_data->{'MAX_LPS'};
	}
	if ($lps < $lv_data->{'LV_MIN_LPS'})
	{
	  $lps = $lv_data->{'LV_MIN_LPS'};
	}
	my $pps = $lps *  $pp_lp_ratio;

	$lv_data->{'LPs'} = $lps;
	$lv_data->{'PP'} = $pps;
	
	log_print("\tresizing paging space: $lv_data->{'LOGICAL_VOLUME'}\n");
	log_printf("\tnew paging space size = %s\n", $divided_ps_size->bstr());
	log_print("\tLPs = $lps\n");
	log_print("\tPPs = $pps\n");
      }
    }
  };

my $comment = q {  
  my $ps_delta = $this->paging_space_size() - $ps_size;
  if ($ps_delta > 0)
  {
    my $ps_delta_mb = $ps_delta / (1024 * 1024);
    if ($ps_delta % (1024 * 1024))
    {
      $ps_delta_mb++;
    }

    foreach my $source_disk (@{$this->{'content'}->{'source_disk_data'}})
    {
      $source_disk->{'SIZE_MB'} += $ps_delta_mb;
      log_print("\tresizing $source_disk->{'HDISKNAME'} disk to $source_disk->{'SIZE_MB'} MB\n");
    }
  }
};
}

#-------------------------------------------------------------------------------
sub set_default_image_data_values
{
  my $this = shift;
  my ($shrink, $exact_fit) = @_;

  log_print "NIM::Deploy::Mksysb::ImageData::set_default_image_data_values(\'$shrink\', \'$exact_fit\')\n";

  if (! $shrink)
  {
    $shrink = 'no';
  }
  if (! $exact_fit)
  {
    $exact_fit = 'no';
  }

  foreach my $lv_policy (@{$this->{'content'}->{'logical_volume_policy'}})
  {
    $lv_policy->{'SHRINK'} = $shrink;
    $lv_policy->{'EXACT_FIT'} = $exact_fit;
  }
}

1;

# __END__

=head1 AUTHOR

IBM

=head1 BUGS

Please report any bugs to the L</"AUTHOR">.

=head1 SUPPORT

=head1 ACKNOWLEDGEMENTS

=head1 SEE ALSO

=begin html

<ul>
<li><a href="../Mksysb.html">NIM::Deploy::Mksysb</a>
</ul>

=end html

=cut
