#!/usr/bin/env perl
#-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-

# Memory configurator. 
# Designed to be architecture- and distribution independent.
#
# Copyright (C) 2000-2001 Ximian, Inc.
#
# Authors: Bradford Hovinen <hovinen@ximian.com>, Tambet Ingo <tambet@ximian.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published
# by the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

# Best viewed with 100 columns of width.

# Configuration files affected:
#
# /etc/fstab /proc/meminfo

# Running programs affected/used:
#
# swapon swapoff mkswap fdisk dd

# --- Common stuff --- #


require "/usr/share/ximian-setup-tools/scripts/general.pl";
require "/usr/share/ximian-setup-tools/scripts/platform.pl";
require "/usr/share/ximian-setup-tools/scripts/util.pl";
require "/usr/share/ximian-setup-tools/scripts/file.pl";
require "/usr/share/ximian-setup-tools/scripts/xml.pl";


$DEBUG = 1; # 1 == command; no debug. 2 == no commands, just report. 3 == report and command.

# --- Tool information --- #

$name = "memory";
$version = "0.1.0";
@platforms = ("redhat-5.2", "redhat-6.0", "redhat-6.1", "redhat-6.2", "redhat-7.0");

$description =<<"end_of_description;";
       Configures main and swap memory.
end_of_description;

$progress_max = 3;


# --- System config file locations --- #

# We list each config file type with as many alternate locations as possible.
# They are tried in array order. First found = used.

@fstab_names =       ( "/etc/fstab" );
@meminfo_names = ( "/proc/meminfo" );

# Where are the tools?

$cmd_swapon = &xst_locate_tool ("swapon");
$cmd_swapoff = &xst_locate_tool ("swapoff");
$cmd_mkswap = &xst_locate_tool ("mkswap");
$cmd_fdisk = &xst_locate_tool ("fdisk");
$cmd_dd = &xst_locate_tool ("dd");


# --- Internal configuration variables --- #

# Configuration is parsed/read to, and printed/written from, these temporary variables.

@cf_swapinfo = ();
@cf_partition = ();
@cf_memory = ();


# --- XML scanning --- #

sub xml_parse
  {
    # Scan XML to tree.
    
    $tree = &xst_xml_scan;
    
    # Walk the tree recursively and extract configuration parameters.
    # This is the top level - find and enter the "memory" tag.
    
    while (@$tree)
      {
	if ($$tree[0] eq "memory") { &xml_parse_memory($$tree[1]); }
	
	shift @$tree;
	shift @$tree;
      }
    
    return($tree);
  }


# <memory>...</memory>

sub xml_parse_memory
  {
    my $tree = $_[0];
    
    shift @$tree;		# Skip attributes.
    
    while (@$tree)
      {
	if ($$tree[0] eq "swapdev") 
	  { 
	    push @cf_swapinfo, &xml_parse_swapinfo ($$tree[1]); 
	  }
	
	shift @$tree;
	shift @$tree;
      }
  }


# <swap-device>...</swap-device>

sub xml_parse_swapinfo
  {
    my $tree = $_[0];
    my $node;
    my $entry = {};
    
    shift @$tree;		# Skip attributes.
    
    while (@$tree)
      {
	if ($$tree[0] eq 'device') 
	  { 
	    $entry->{device} = &xst_xml_get_word ($$tree[1]);
	  }
	elsif ($$tree[0] eq 'enabled') 
	  {
	    $node = $$tree[1];
	    $entry->{enabled} = &xst_read_boolean ($$node[0]->{state});
	  }
	elsif ($$tree[0] eq 'priority') 
	  { 
	    $entry->{priority} = &xst_xml_get_word ($$tree[1]);
	  }
	elsif ($$tree[0] eq 'size') 
	  { 
	    $entry->{size} = &xst_xml_get_size ($$tree[1]);
	  }
	elsif ($$tree[0] eq 'isfile') 
	  { 
	    $node = $$tree[1];
	    $entry->{is_file} = &xst_read_boolean ($$node[0]->{state});
	  }
	elsif ($$tree[0] eq 'isnew') 
	  { 
	    $node = $$tree[1];
	    $entry->{is_new} = &xst_read_boolean ($$node[0]->{state});
	  }
	
	shift @$tree;
	shift @$tree;
      }

    return $entry;
  }

# --- XML printing --- #


sub xml_print
  {
    print "<?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>\n";
    print "<!DOCTYPE memory []>\n\n";
    print "<memory>\n";
    &xst_xml_enter ();

    &xst_xml_vspace ();
    &xst_xml_print ("<!-- Configuration starts here -->\n");
    &xst_xml_vspace ();

    foreach $entry (@cf_memory) {
      &xst_xml_print ("<memsize>$entry->{MemTotal}</memsize>\n");
      &xst_xml_print ("<swapsize>$entry->{SwapTotal}</swapsize>\n");
    }

    &xst_xml_vspace ();

    foreach $entry (@cf_partition) {
      &xst_xml_print ("<partition>\n");
			&xst_xml_enter;
      &xst_xml_print ("<device>$entry->{device}</device>\n");
      &xst_xml_print ("<size>$entry->{size}</size>\n");
			&xst_xml_leave;
      &xst_xml_print ("</partition>\n");
    }

    &xst_xml_vspace ();

    foreach $entry (@cf_swapinfo) {
      &xst_xml_print ("<swapdev>\n");
			&xst_xml_enter;
      &xst_xml_print ("<device>$entry->{device}</device>\n");
      &xst_xml_print ("<enabled state='", 
                            &xst_print_boolean_yesno ($entry->{enabled}), 
                            "'/>\n");
      &xst_xml_print ("<priority>$entry->{priority}</priority>\n");
      &xst_xml_print ("<isfile state='",
                            &xst_print_boolean_truefalse ($entry->{is_file}),
                            "'/>\n");
      &xst_xml_print ("<size>$entry->{size}</size>\n");
      &xst_xml_print ("<isnew state='",
                            &xst_print_boolean_truefalse ($entry->{is_new}),
                            "'/>\n");
			&xst_xml_leave;
      &xst_xml_print ("</swapdev>\n\n");
    }

    &xst_xml_print ("<!-- End of configuration -->\n");
    &xst_xml_vspace ();

    &xst_xml_leave ();
    print "</memory>\n";
  }


# --- Get (read) config --- #

sub get_memory
{
  my (@meminfo);
  my ($totmem);
  my ($totswap);
  my $ifh;
  local *FILE;

  $ifh = &xst_open_read_from_names(@meminfo_names);
  if (not $ifh) { return; } # We didn't find it.
  *FILE = $ifh;

  @meminfo = <FILE>;
  close FILE;

  foreach $entry (@meminfo)
    {
      if ($entry =~ /^MemTotal:(\s+)(\w+)/)
	{
	  $totmem = sprintf ("%.1fMB", $2 / 1024);	# In Mb, integer
	}
      elsif ($entry =~ /^SwapTotal:(\s+)(\w+)/)
	{
	  $totswap = sprintf ("%.1fMB", $2 / 1024);
	}
    }

  push @cf_memory, {
		"MemTotal" => $totmem,
		"SwapTotal" => $totswap
		};
}


sub get_fdisk
{
  # Okay, so this is strictly not portable either. Patches welcome.

  my @check_devs = ( "/dev/hda", "/dev/hdb", "/dev/hdc", "/dev/hdd",

                     "/dev/sda", "/dev/sdb", "/dev/sdc", "/dev/sdd",
                     "/dev/sde", "/dev/sdf",
                     
                     "/dev/eda", "/dev/edb", "/dev/edc", "/dev/edd",
                     
                     "/dev/xda", "/dev/xdb" );

  for $dev (@check_devs)
  {
    my $disk, $device, $point, $fs, $options, $check, $size, $bootable;
    
    &xst_report_info(01, "Looking for partitions on $dev");

    open(FDISK_HD, "$cmd_fdisk -l $dev 2>/dev/null |");
    while (<FDISK_HD>)
    {
      if (/^\/dev/)
      {
        @line = split(/[ \n\r\t]+/, $_);

        $device = @line[0]; shift @line;
        ($disk) = ($device =~ /([a-zA-Z\/]+)/);

        shift @line; shift @line;  # Start and end clusters.

        ($size) = (@line[0] =~ /([0-9]+)/);
	$size = sprintf ("%.1fMB", $size / 1024);
        shift @line;

        if (@line[0] ne "82") { next; }  # Not Swap
        else
	  {
	    push @cf_partition, {
				"device" => $device,
				"size" => $size
				};
	  }


      }
    }
    close(FDISK_HD);

    &xst_report_info(02, "Querying size of $dev");

#    open(FDISK_HD, "$fdisk_tool -s $dev 2>/dev/null |");
#    ($size) = (<FDISK_HD> =~ /([0-9]+)/);
#    if ($size eq "") { next; }
#    close(FDISK_HD);

  }
}



sub get_swap_size
  {
    my ($device) = $_[0];
    my ($is_file) = $_[1];
    my ($size) = 0;

    if ($is_file && -f $device)
      {
        # Is file, &get file size.
	my (@stats) = stat($device);
	$size = $stats[7];			# size in stat structure.
	$size = $size /1024 / 1024;		# Size from bytes to Mb.
	$size .= "MB";
      }
    elsif ($is_file == 0)
      {
        # Is partition, &get size.
        open(FD, "$cmd_fdisk -s $device 2>/dev/null |");
        while (<FD>)
          {
            if (/^\w+/)
              {
                 $size = sprintf("%.1fMB", $_ / 1024);		#size from Kb to Mb
              }
          }
      }

    return $size;
  }


sub get_swap_entries
  {
    my $fstab_file;
    local *FSTAB_FILE;

    $fstab_file = &xst_open_read_from_names (@fstab_names);
    if (!$fstab_file) { return; }
    *FSTAB_FILE = $fstab_file;

    while (<FSTAB_FILE>)
      {
	my ($device, $mount_pt, $type, $options, $fs_freq, $fs_passno) = split;
	my (@option_list) = split /\,/, $options;
	my ($priority) = -1;
	my ($enabled);
        my ($size);
        my ($is_new) = 0;
        my ($is_file) = 1;

	next if $type ne 'swap';

	&xst_report_info(03, "Found swap entry $device");

	if ($device =~ /^#(.*)/)
	  {
	    $device = $1;
	    $enabled = 0;
	  }
	else 
	  {
	    $enabled = 1;
	  }

        if ($device =~ /^(#?)\/dev\//)
          {
            $is_file = 0;
          }

        $size = &get_swap_size($device, $is_file);

	foreach $item (@option_list)
	  {
	    my ($key, $value) = split /\=/, $item;

	    $priority = $value if $key eq 'pri';
	  }

	push @cf_swapinfo, { 
			    "device" => $device, 
			    "enabled" => $enabled, 
			    "priority" => $priority,
                            "is_file" => $is_file,
                            "size" => $size,
                            "is_new" => $is_new
			   };
      }
  }


sub get
{
    &xst_report_info(04, "Getting swap entries");

    &get_memory; &xst_print_progress();
    &get_fdisk;  &xst_print_progress();
    &get_swap_entries; &xst_print_progress();
    
    &xst_end();
    &xml_print ();
  }


# --- Set (write) config --- #

sub setup_swap_files
{
  my $command;

  $command = "$cmd_swapoff -a";  # To make sure removed swap spaces &get removed
  &xst_run ($command);
    
  my $num_done = 0;

  foreach $entry (@cf_swapinfo)
  {
    if ($entry->{is_new})
    {
      if ($entry->{is_file})
      {
         my ($size) = $entry->{size};
         $size =~ tr/MB$//d;	# Remove Mb from the end
         $size *= 1024;		# Make it to Kb

         $command = "$cmd_dd if=/dev/null of=$entry->{device} bs=1024 count=$size";
         &xst_run ($command);
       }

       $command = "$cmd_mkswap $entry->{device} >/dev/null 2>/dev/null";
       &xst_run ($command);
    }

    elsif ($entry->{is_file})
    {
      my ($size) = $entry->{size};
      $size =~ tr/MB$//d;	# Remove Mb from the end
      $size *= 1024;		# Make it to Kb

      if ($size != $entry->{size})
      {
        $command = "$cmd_dd if=/dev/zero of=$entry->{device} bs=1024 count=$size";
        &xst_run ($command);

        $command = "$cmd_mkswap $entry->{device} >/dev/null 2>/dev/null";
        &xst_run ($command);
      }
    }

    if ($entry->{priority} == -1)
    {
      $priority = "";
    }
    else
    {
      $priority = "-p $entry->{priority}";
    }

    if ($entry->{enabled})
    {
      $command = "$cmd_swapon $entry->{device} $priority >/dev/null 2>/dev/null";
      &xst_run ($command);
    }
	
    $num_done++;
    &xst_progress(90 / ($#cf_swapinfo - $num_done + 2));
  }
}

sub set_swap_entries
  {
    my ($fstab_in_file, $fstab_out_file);
    local (*FSTAB_IN_FILE, *FSTAB_OUT_FILE);
    my (@lines);
    my ($priority_str);

    ($fstab_in_file, $fstab_out_file) = 
      &xst_open_filter_write_from_names (@fstab_names);
    if (!$fstab_out_file) { return; }
    *FSTAB_IN_FILE = $fstab_in_file; *FSTAB_OUT_FILE = $fstab_out_file;

    while (<FSTAB_IN_FILE>)
      {
	my ($device, $mount_pt, $type, $options, $fs_freq, $fs_passno) = 
	  split /\s+/;

	print FSTAB_OUT_FILE if $type ne 'swap';
      }

    foreach $entry (@cf_swapinfo)
      {
	if ($entry->{priority} >= 0) 
	  {
	    $priority_str = sprintf "pri=%-11u", $entry->{priority};
	  }
	else
	  {
	    $priority_str = "defaults       ";
	  }

	print FSTAB_OUT_FILE "#" if !$entry->{enabled};
	printf FSTAB_OUT_FILE 
	  "%-23s swap                    swap    %s 0 0\n",
	  $entry->{device},
	  $priority_str;
      }

    close FSTAB_FILE;
  }


sub set
  {
    &xml_parse ();
    
    if ($xst_do_immediate)
      {
	&setup_swap_files;
      }

    &set_swap_entries;
    &xst_end();
  }


# --- Filter config: XML in, XML out --- #


sub filter
  {
    &xml_parse ();
    &xst_end();
    &xml_print ();
  }


# --- Main --- #

&xst_init ($name, $version, $description, @ARGV);
&xst_platform_ensure_supported (@platforms);

# Do our thing.

if    ($xst_operation eq "get")    { &get; }
elsif ($xst_operation eq "set")    { &set; }
elsif ($xst_operation eq "filter") { &filter; }
