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

# Simple fixed media configurator. Designed to be architecture- and distribution independent.
# Growing it in order to support the new disk-tool
#
# Copyright (C) 2000-2001 Ximian, Inc.
# Copyright (C) 2003 Alvaro del Castillo
#
# Authors: Hans Petter Jansson <hpj@ximian.com>
# Authors: Alvaro del Castillo <acs@barrapunto.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

# Running programs affected/used:
#
# fdisk
# mount
# awk (already used in guess_system.sh) 
# cat

# For debuging
# use Data::Dumper;

BEGIN {
  $SCRIPTSDIR = "/usr/share/setup-tool-backends/scripts";
  if ($SCRIPTSDIR =~ /^@scriptsdir[@]/)
  {
      $SCRIPTSDIR = ".";
      $DOTIN = ".in";
  }
  
  require "$SCRIPTSDIR/general.pl$DOTIN";
  require "$SCRIPTSDIR/platform.pl$DOTIN";
  require "$SCRIPTSDIR/util.pl$DOTIN";
  require "$SCRIPTSDIR/file.pl$DOTIN";
  require "$SCRIPTSDIR/xml.pl$DOTIN";
  require "$SCRIPTSDIR/filesys.pl$DOTIN";
}


# --- Tool information --- #

$name = "disks";
$version = "0.32.0";
@platforms = ("redhat-5.2", "redhat-6.0", "redhat-6.1", "redhat-6.2", "redhat-7.0",
              "redhat-7.1",  "mandrake-7.2", "debian-2.2", "debian-woody", "debian-sarge",
	      "suse-7.0", "suse-1.0", "unitedlinux-1.0", "turbolinux-7.0");

$description =<<"end_of_description;";
       Configures locally mounted partitioned media.
end_of_description;

$progress_max = 16;


# --- 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.

# Right now there's only one entry per array, as I couldn't find any
# typical deviations.

@fstab_names = ( "/etc/fstab" );


# --- Internal configuration variables --- #

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

@cf_disks = ();


# --- Backend-specific helper subs --- #

sub get_media_type
{
   my ($dev_clean, $dev_prefix);
   
   ($dev) = @_;   

   ($dev_clean) = ($dev =~ /^\/dev\/([a-zA-Z0-9]*)$/);
   
   ($dev_prefix) = ($dev_clean =~ /(^[a-z]*)[a-z]/);
  
   if ($dev_prefix eq "hd") {
      return ("disk-ide");
   } elsif ($dev_prefix eq "sd") {
      return ("disk-scsi");
   } else {
      return ("unknown");
   }
}

sub update_partition
{
  my ($disk, $device, $point, $fs, $options, $check);
  my ($listed, $bootable, $detected);
  my ($disk_found, $point_found) = (0, 0);
  my $label;
  my ($start, $end);

  ($disk, $device, $point, $fs, $options, $check, $size, $listed,
   $bootable, $detected, $label, $start, $end) = @_;


  if ($fs eq "auto") { $fs = ""; }
  if ($label eq "")
  {
    if ($device eq "") { return; }
    $label = gst_filesys_ext2_device_to_label ($device);
  }

  for ($i = 0; $cf_disks[$i]; $i++)
  {
    if ($disk eq "" || ($cf_disks[$i])->{device} eq $disk)
    {
      # Found disk. Now look for partition.
          
      for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++)
      {
        if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device ||
            ($label ne "") && ($cf_disks[$i]->{partitions}[$j]->{label} eq $label))
        {
          # Found partition.
          
          if ($options ne "")
          {
            if ($options =~ /noauto/)
            {
              if (`df | grep \"$device \"` eq "") {
                  $cf_disks[$i]->{partitions}[$j]->{mounted} = 0;
              }
            }
            else
            {
              $cf_disks[$i]->{partitions}[$j]->{mounted} = 1;
              # If the partition is mounted we can show free space
              $block_size = $cf_disks[$i]->{block_size};
              $free_space=0;
              $free_space = `df --block-size=$block_size $device | grep $device | awk  '/\\/dev/ {print \$4}'`;
              $cf_disks[$i]->{partitions}[$j]->{free} = $free_space if $free_space;
            }
          }

          if ($point ne "") { $cf_disks[$i]->{partitions}[$j]->{point} = $point; }
          if ($fs) { $cf_disks[$i]->{partitions}[$j]->{type} = $fs; }
          if ($listed) { $cf_disks[$i]->{partitions}[$j]->{listed} = 1; }
          if ($bootable) { $cf_disks[$i]->{partitions}[$j]->{bootable} = 1; }
          if ($detected) { $cf_disks[$i]->{partitions}[$j]->{detected} = 1; }
          if ($check) { $cf_disks[$i]->{partitions}[$j]->{check} = 1; }
          if ($size) { $cf_disks[$i]->{partitions}[$j]->{size} = $size; }
          if ($label) { $cf_disks[$i]->{partitions}[$j]->{label} = $label; }

          $disk_found = 1;
          $point_found = 1; last;
        }
      }

      if (!$point_found && $device)
      {
        # Make new partition entry.

        my %partition;

        # if ($options =~ /noauto/) { %partition->{mounted} = 0; }
        if (`df | grep \"$device \"` eq "") { %partition->{mounted} = 0; }
        else                      
        { 
            %partition->{mounted} = 1;
            $block_size = $cf_disks[$i]->{block_size};
            $free_space = 0;
            $free_space = `df --block-size=$block_size $device | grep $device | awk  '/\\/dev/ {print \$4}'`;
            %partition->{free} = $free_space if $free_space;
            %partition->{point} = `mount | grep $device | awk '{print \$3}'`;
            %partition->{type} =  `mount | grep $device | awk '{print \$5}'`;
        }

        %partition->{device} = $device;
        # we use better the mount info, no fstab info - KaL
        if (%partition->{mounted} == 0) {
            %partition->{point} = $point;
            %partition->{type} = $fs;
        }
        %partition->{listed} = $listed;
        if ($bootable) { %partition->{bootable} = 1; }
        if ($detected) { %partition->{detected} = 1; }
        if ($check) { %partition->{check} = 1; }
        %partition->{size} = $size;
        %partition->{label} = $label;
	if ($start < $end) {
	   %partition->{start} = $start;
	   %partition->{end} = $end;
	}

        $bleh = $cf_disks[$i]->{partitions};
        push(@$bleh, \%partition);

        $disk_found = 1; last;
      }
    }
  }

  if (!$disk_found)
  {
    # Make new disk entry containing this partition.

    my (%disk, %partition);

    if ($options =~ /noauto/) { %partition->{mounted} = 0; }
    else                      { %partition->{mounted} = 1; }

    %partition->{device} = $device;
    %partition->{point} = $point;
    %partition->{type} = $fs;
    %partition->{listed} = $listed;
    if ($bootable) { %partition->{bootable} = 1; }
    if ($detected) { %partition->{detected} = 1; }
    if ($check) { %partition->{check} = 1; }
    %partition->{size} = $size;
    %partition->{label} = $label;
    if ($start < $end) {
       %partition->{start} = $start;
       %partition->{end} = $end;
    }

    %disk->{device} = $disk;
    %disk->{partitions} = [];

    $bleh = %disk->{partitions};
    push(@$bleh, \%partition);
    push(@cf_disks, \%disk);
  }
}


sub get_partition
{
  my ($disk, $device, $label) = @_;
  my ($i, $j);

  for ($i = 0; $cf_disks[$i]; $i++)
  {
    if ($disk eq "" || ($cf_disks[$i])->{device} eq $disk)
    {
      # Found disk. Now look for partition.

      for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++)
      {
        if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device ||
            ($device eq "" && $cf_disks[$i]->{partitions}[$j]->{label} eq $label))
        {
          # Found partition.

          return ($cf_disks[$i]->{partitions}[$j]);
        }
      }
    }
  }
}


sub get_partition_data
{
  my ($disk, $device, $label);

  ($disk, $device, $label) = @_;

  for ($i = 0; $cf_disks[$i]; $i++)
  {
    if ($disk eq "" || ($cf_disks[$i])->{device} eq $disk)
    {
      # Found disk. Now look for partition.

      for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++)
      {
        if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device ||
            ($device eq "" && $cf_disks[$i]->{partitions}[$j]->{label} eq $label))
        {
          # Found partition.

          return ($cf_disks[$i]->{partitions}[$j]->{point},
                  $cf_disks[$i]->{partitions}[$j]->{type},
                  $cf_disks[$i]->{partitions}[$j]->{listed},
                  $cf_disks[$i]->{partitions}[$j]->{mounted},
                  $cf_disks[$i]->{partitions}[$j]->{bootable},
                  $cf_disks[$i]->{partitions}[$j]->{check},
                  $cf_disks[$i]->{partitions}[$j]->{label});
        }
      }
    }
  }
}

sub get_ide_setting
{
   my ($dev_clean, $setting);

   ($dev_clean, $setting) = @_;

   $value = `cat /proc/ide/$dev_clean/settings 2>/dev/null | grep $setting | awk '{print \$2}'`;

   return $value;
}

sub get_cdrom_settings
{
   my @cd_name_sett = ("play-audio", "write-cdr", "write-cdrw", "read-dvd",
                       "write-dvdr", "write-dvdram");
   my @opts = ("Can play audio", "Can write CD-R", "Can write CD-RW", "Can read DVD", 
               "Can write DVD-R", "Can write DVD-RAM");
   
   my $proc_cdrom_file;
   local *PROC_CDROM_FILE;
   $proc_cdrom_file = &gst_file_open_read_from_names ("/proc/sys/dev/cdrom/info");
   if (not $proc_cdrom_file) { return; }
   *PROC_CDROM_FILE = $proc_cdrom_file;
   
   my @devs;
   while (<PROC_CDROM_FILE>) {
      if ($_ =~ /drive name/) {
	 ($line) = ($_ =~ /^drive name:(.*$)/);
	 $line =~ s/^\s*//;
	 $line =~ s/\s*$//;
	 my @columns = split (" ", $line);
	 for (my $i = 0; $columns[$i]; $i++) {
	    $devs[$i] = "/dev/$columns[$i]";
	 }
      } elsif ($_ =~ /$opts[0]/) {
	 ($line) = ($_ =~ /.*:(.*$)/);
	 $line =~ s/^\s*//;
	 $line =~ s/\s*$//;
	 my @columns = split (" ", $line);
	 my $i = 0;
	 foreach (@columns) {
	    $columns[$i] =~ s/^\s*//;
	    $columns[$i] =~ s/\s*$//;
	    &update_disk_data ($devs[$i], $cd_name_sett[0], "$columns[$i]");
	    $i++;
	 }
	 shift @opts;
	 shift @cd_name_sett;
      }
   }
	 
   close (PROC_CDROM_FILE);
}


sub update_disk_data
{
  my ($disk, $data, $value);


  ($disk, $data, $value) = @_;

  my $disk_found = 0;

  for ($i = 0; $cf_disks[$i]; $i++)
  {
    if (($cf_disks[$i])->{device} eq $disk)
    {
        # Found disk.
        $cf_disks[$i]->{$data} = $value;
        $disk_found = 1;
        last;
    }
  }

  if (!$disk_found)
  {
    # Make new disk entry

    my (%disk);

    %disk->{device} = $disk;
    %disk->{$data} = $value;
    %disk->{partitions} = [];

    push(@cf_disks, \%disk);
  }
}




# --- Configuration file manipulation --- #


# /etc/fstab
#
# <device> <mount point> <filesystem> <option,option,...> <dump> <fsck>
# <device> <mount point> <filesystem> <option,option,...> <dump> <fsck>
# ...
#
# Exists: (Presumably everywhere)
#
# Absent: (Presumably nowhere)

sub read_fstab
{
  my $fstab_file;
  local *FSTAB_FILE;

  # Find the file.

  $fstab_file = &gst_file_open_read_from_names(@fstab_names);
  if (not $fstab_file) { return; }  # We didn't find it.
  *FSTAB_FILE = $fstab_file;

  # Parse the file.

  while (<FSTAB_FILE>)
  {
    my ($disk, $device, $point, $fs, $options, $check, $label);

    @line = split(/[ \n\r\t]+/, $_);

    if ($line[0] eq "") { shift @line; }
    if ($line[0] eq "") { next; }
    if (&gst_ignore_line($line[0])) { next; }
    ($device, $point, $fs, $options, $dump, $check) = @line;

    if ($device =~ /$LABEL=(.*)/) { $label = $1; $device = ""; }
    else                          { $label = ""; }

    # we want to inform the backend about the swap
    if ($fs eq "nfs" || $fs eq "smbfs" || $fs eq "proc" || $fs eq "devpts" ||
        $fs eq "iso9660")
#        $fs eq "iso9660" || $fs eq "swap")
    {
      next;  # We can skip these filesystems for sure.
    }

    if ($point eq "none") { $dir = ""; }
    ($disk) = ($device =~ /([a-zA-Z\/]+)/);

    if ($disk eq "/dev/fd" || ($disk ne "" && $disk eq $device))
    {
      next;  # Skip floppies and CD-ROMs.
    }

    $media_type = &get_media_type ($disk);
    if ($media_type eq "disk-ide") {
       ($dev_clean) = ($dev =~ /^\/dev\/([a-zA-Z0-9]*)$/);
       $model = `cat /proc/ide/$dev_clean/model 2>/dev/null`;
       if ($model ne "") { &update_disk_data ($disk, "model", $model); }
          $media = `cat /proc/ide/$dev_clean/media 2>/dev/null`;
       if ($media ne "") { &update_disk_data ($disk, "media", $media); }
       $dma = &get_ide_setting ($dev_clean, "using_dma");
       if ($dma ne "") { &update_disk_data ($disk, "dma", $dma); }
    } elsif ($media_type eq "disk-scsi") {
       &update_disk_data ($disk, "media", "disk");
    } elsif ($media_type eq "usb-storage") {
    }
    
    # (Find and update) or (add) our internal disk/partition record.

    for ($i = 0; $cf_disks[$i]; $i++) {
       if ($cf_disks[$i]->{device} eq $disk) {
	  for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) {
	     if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device) {
		$type = $cf_disks[$i]->{partitions}[$j]->{type};
		last;
	     }
	  }
	  last;
       }
    }
    
    if (($disk ne "" || $label ne "") && $type ne "empty") {
      &update_partition ($disk, $device, $point, $fs, $options, $check, "", 1, 0, "", $label, 0, 0);
    }
  }

  close(FILE);
}


sub write_fstab
{
  my ($ifh, $ofh);
  local (*INFILE, *OUTFILE);

  ($ifh, $ofh) = &gst_file_open_filter_write_from_names(@fstab_names);
  if (not $ofh) { return; }  # No point if we can't write.
  *INFILE = $ifh; *OUTFILE = $ofh;

  while (<INFILE>)
  {
    my ($disk, $device, $point, $fs, $options, $dump, $check, $label);
    my ($ipoint, $itype, $icheck, $ilisted, $imounted, $ibootable, $ilabel);

    @line = split(/[ \n\r\t]+/, $_);

    if ($line[0] eq "") { shift @line; }
    if ($line[0] eq "") { print OUTFILE; next; }
    if (&gst_ignore_line($line[0])) { print OUTFILE; next; }
    ($device, $point, $fs, $options, $dump, $check) = @line;

    if ($fs eq "nfs" || $fs eq "smbfs" || $fs eq "proc" || $fs eq "devpts" ||
        $fs eq "iso9660" || $fs eq "swap" || $device =~ /$\/dev\/fd.*/)
    {
      print OUTFILE; next;  # We can skip these filesystems for sure.
    }

    # By now, we know that the "entry" is "interesting". Check if known.

    if ($device =~ /$LABEL=(.*)/)
    {
      $label = $1;
      $device = "";
      $disk = "";
    }
    else
    {
      $label = "";
      ($disk) = ($device =~ /([a-zA-Z\/]+)/);
    }

    if ($disk eq "/dev/fd") { print OUTFILE; next; }

    ($ipoint, $itype, $ilisted, $imounted, $ibootable, $icheck, $ilabel) =
      &get_partition_data($disk, $device, $label);

    if ($ilisted)
    {
      # Write record if listedness requested.

      if ($ilabel ne "")
      {
        print OUTFILE "LABEL=" . $ilabel . " ";
      }
      else
      {
        print OUTFILE $device . " ";
      }

      if ($ipoint eq "") { print OUTFILE "none "; }
      else { print OUTFILE $ipoint . " "; }

      if ($itype eq "") { print OUTFILE "auto "; }
      else              { print OUTFILE $itype . " "; }

      # Options merging and printing.

      my $prev = 0;
      if (!$imounted) { print OUTFILE "noauto"; $prev = 1; }
      my @options = ($options =~ /([a-zA-Z0-9=-]+),?/mg);
      for $option (@options)
      {
        # Strip options we handle, keep the rest.

        if ($option eq "auto" || $option eq "noauto" ||
            $option eq "defaults") { next; }
        if ($prev) { print OUTFILE ","; }
        print OUTFILE $option;
        $prev = 1;
      }
      
      if (!$prev) { print OUTFILE "defaults"; }
      
      # Leave dump alone.
      
      print OUTFILE " $dump ";
      
      # Fsck onboot priority.

      if ($icheck eq "") { $icheck = 0; }
      if ($icheck == 1)
      {
        if ($ipoint eq "/") { print OUTFILE "1\n"; }
        else { print OUTFILE "2\n"; }
      }
      else { print OUTFILE "0\n"; }
      
      # Indicate that parameters for this partition have been stored.
      
      my $partition = &get_partition($disk, $device, $label);
      %$partition->{stored} = 1;
    }
    
    # Unknown or unlisted-by-request partitions are not written.
  }
  
  # Print the remaining partitions from our internal list. These are
  # newly added, and didn't exist in the fstab previously.
  
  for ($i = 0; $cf_disks[$i]; $i++)
  {
    for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++)
    {
      if ($cf_disks[$i]->{partitions}[$j]->{listed} &&
          !$cf_disks[$i]->{partitions}[$j]->{stored})
      {
        my $part = $cf_disks[$i]->{partitions}[$j];

        &gst_report ("disks_fstab_add", $cf_disks[$i]->{partitions}[$j]->{device});
        
        # Write record.

        print OUTFILE %$part->{device} . " ";
        if (%$part->{point} eq "") { print OUTFILE "none "; }
        else { print OUTFILE %$part->{point} . " "; }

        if (%$part->{type} eq "") { print OUTFILE "auto "; }
        else                     { print OUTFILE %$part->{type} . " "; }

        # Options printing.

        if (!%$part->{mounted}) { print OUTFILE "noauto "; }
        else { print OUTFILE "defaults "; }

        # No dumping by default.

        print OUTFILE " 0 ";
      
        # Fsck onboot priority.

        if (%$part->{check} == 1)
        {
          if (%$part->{point} eq "/") { print OUTFILE "1\n"; }
          else { print OUTFILE "2\n"; }
        }
        else { print OUTFILE "0\n"; }
      }
    }
  }
  
  close OUTFILE;
}

sub get_cdroms
{
   my @list_devices;

   $line = `cat /proc/sys/dev/cdrom/info | grep "drive name" | cut -d: -f2 2>/dev/null`;
   $line =~ s/^\s*//;
   $line =~ s/\s*$//;
   @list_devices = split (/[ \r\t]+/, $line);

   for (my $i = 0; $list_devices[$i]; $i++) {
      my $dev = "/dev/$list_devices[$i]";
      &update_disk_data ($dev, "media", "cdrom");
      if ($list_devices[$i] =~ /^hd[a-d]/) {
	 $model = `cat /proc/ide/$list_devices[$i]/model 2>/dev/null`;
	 if ($model ne "") { &update_disk_data ($dev, "model", $model); }
	 $dma = &get_ide_setting ($list_devices[$i], "using_dma");
	 if ($dma ne "") { &update_disk_data ($dev, "dma", $dma); }
      } 
   }
}
	       

# fdisk -l <disk device>
#
# <&filtered lines>
#
# <partition device> [*] <start cluster> <end cluster> <blocks> <id> <verbose id>
# <partition device> [*] <start cluster> <end cluster> <blocks> <id> <verbose id>
# ...
#
# Exists: Red Hat 6.2 (Presumably all Linux)
#
# Absent:
#
# The star is optional, and means that the partition is bootable. We could've
# used /proc/partitions to get this information, but it lists CD-ROM devices
# and whatnot as well, without a disambiguating identifier. Then there is
# portability, forward-compatibility, people without /proc, etc.

# fdisk -s <disk device>
#
# <blocksize>

sub sort_by_start
{
   return ($a->{'start'} <=> $b->{'start'});
}

sub get_fdisk
{
  my $fdisk_tool;

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

  my @check_devs = ( "/dev/hda", "/dev/hdb", "/dev/hdc", "/dev/hdd",
                     "/dev/hde", "/dev/hdf", "/dev/hdg", "/dev/hdh",

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

  $fdisk_tool = &gst_file_locate_tool("fdisk");
  #my @check_devs = ("/dev/hdc"); # debug

  for $dev (@check_devs)
  {
    my ($disk, $device, $point, $fs, $options, $check, $size, $bootable, $fd);
    
    &gst_report ("disks_partition_probe", $dev);

    $fd = &gst_file_run_pipe_read ("fdisk -l $dev");
    #$fd = &gst_file_open_read_from_names ("/tmp/fdisk"); # debug
    # We want to cache fdisk work!
    
    my @fdisk_data = <$fd>; 
    #close ($fd); # debug
    my ($block_size, $dev_size);
    my $unit = "";

    # First of all we get the global disk data: size, unit
    foreach (@fdisk_data) {
        if (/^Block size/) {
	    # fdisk in Debian PowerPC
            # Block size=512, Number of Blocks=78140160
            ($block_size, $dev_size) = ($_ =~ /^Block size=([0-9]*)[^0-9]*([0-9]*).*$/);
	    $last_end = $dev_size;
	    # $unit must be 1024
	    # my $fsize = ($fend - $fstart + 1) * ($unit / 1024);
	    # we need ($unit / 1024) = 1
	    $unit = 1024;
            &update_disk_data ($dev, "size", $dev_size);
            &update_disk_data ($dev, "block_size", $block_size);
            
        } elsif (/^Disk/) {
            # fdisk in Debian i386 
            # Disk /dev/hda: 60.0 GB, 60022480896 bytes
            ($dev_size) = ($_ =~ /^Disk [^0-9]* .* ([0-9]*) bytes$/);
            $block_size=1024;
	    
            # fdisk in Suse Desktop i386
            # Disk /dev/hda: 255 heads, 63 sectors, 2491 cylinders
            if ($dev_size eq "") {
                $dev_size = `fdisk -s $dev`;
                $dev_size = $dev_size * $block_size;
		($last_end) = ($_ =~ /^Disk [^0-9]* [0-9]+ heads, [0-9]+ sectors, ([0-9]+) cylinders$/);
            } 
            
            &update_disk_data ($dev, "size", $dev_size/$block_size);
            &update_disk_data ($dev, "block_size", $block_size);
        } elsif (/^Units/) {
	   # i386
	   # Units = cylinders * 512
	   
	   # fdisk in Debian i386
	   # Units = cylinders of 16065 * 512 = 8225280 bytes
	   ($unit) = ($_ =~ /^Units = cylinders of [0-9]+ \* [0-9]+ = ([0-9]+) bytes$/);

	   if ($unit eq "") {
	      # fdisk in Suse Desktop i386
	      # Units = cylinders of 16065 * 512 bytes
	      my ($a, $b) = ($_ =~ /^Units = cylinders of ([0-9]+) \* ([0-9]+) bytes$/);
	      $unit = $a * $b;
	   }
	} elsif (/^[0-9]+ heads/) {
	   # i386
	   # fdisk in Debian i386
	   # 255 heads, 63 sectors/track, 1222 cylinders
	   ($last_end) = ($_ =~ /^[0-9]+ heads, [0-9]+ sectors\/track, ([0-9]+) cylinders$/);
	}

    }
    
    # Now we get the data of each partition
    my @fdisk_hash = ();
    foreach (@fdisk_data)
    {
      if (/^\/dev/)
      {
        @line = split(/[ \n\r\t]+/, $_);

	my (%parts);

	# fdisk i386
	#    Device Boot    Start       End    Blocks   Id  System
	# /dev/hda3           694      1046   2835472+  83  Linux
	#
	# fdisk powerpc
	#         #                    type name                 length   base     ( size )  system
	# /dev/hdc3          Apple_Driver43 Macintosh                56 @ 120      ( 28.0k)  Driver 4.3

	
        # fdisk in Debian PowerBook gives the disks as the first line 
        next if scalar @line == 1;

	# device file
	%parts->{'device'} = $line[0];
        $device = $line[0]; shift @line;
        ($disk) = ($device =~ /([a-zA-Z\/]+)/);
        if ($line[0] eq "\*") {
          # NOTE: Currently unused.
	  %parts->{'bootable'} = 1;
          $bootable = 1; shift @line;
        }
        else { %parts->{'bootable'} = 0; $bootable = 0; }
	
	# first cylinder of the partition
	($start) = ($line[0] =~ /^([0-9]+)/);
	if ($start eq "") {
	   # there is no start cylinder, we are in powerpc
	   # look for length @ base
	   while ($line[0] ne "@") {
	      ($length) = ($line[0] =~ /^([0-9]+)/);
	      shift @line;
	   }
	   # skip @
	   shift @line;
	   #print "start $line[0]\n";
	   ($start) = ($line[0] =~ /^([0-9]+)/);
	   %parts->{'start'} = $start;
	   $end = $start + $length - 1;
	   %parts->{'end'} = $end;
	   %parts->{'size'} = $length;
	   %parts->{'type'} = "";
	   push (@fdisk_hash, \%parts);
	   # go to next device
	   next;
	}
	%parts->{'start'} = $start;
	shift @line;

	# last cylinder of the partition
	($end) = ($line[0] =~ /^([0-9]+)/);
	%parts->{'end'} = $end;
	shift @line;

	# size of partition in blocks
	($size) = ($line[0] =~ /([0-9]+)/);
	%parts->{'size'} = $size;
        shift @line;

	# FIXME: add new popular ones, such as reiser and xfs and add
	# those documented by fdisk.
	if ($line[0] eq "5"  || $line[0] eq "f"  || $line[0] eq "85") { next; } # extended.
	elsif ($line[0] eq "82") { $type = "swap"; }
	elsif ($line[0] eq "83") { $type = "ext2"; }
	elsif ($line[0] eq "e")  { $type = "vfat"; }
	elsif ($line[0] eq "c" || $line[0] eq "b") { $type = "fat32"; }
	elsif ($line[0] eq "6" || $line[0] eq "4" || $line[0] eq "1") { $type = "msdos"; }
	elsif ($line[0] eq "7")  { $type = "ntfs"; }
	else  { $type = ""; }

	# fs type
	%parts->{'type'} = $type;
	push (@fdisk_hash, \%parts);
      }
    }

    my ($prev_end) = 0; # last cylinder of previous partition
    my $max_cyl = 0; # bigest cylinder found
    
    # In order to find empty partitions we have to sort 
    # the array by start (first cylinder)
    @ord = sort sort_by_start (@fdisk_hash);
    my $empty_count = 1;

    # Now we update the data and detect empty partitions
    foreach $partition (@ord)
    {
       if ($partition->{'end'} > $max_cyl) {
	  $max_cyl = $partition->{'end'};
       }
       
       if ($prev_end ne 0){
	  if ($partition->{'start'} - 1 ne $prev_end) {
	     # There is no partition, free space found
	     my $fstart = $prev_end + 1;
	     my $fend;
	     $fend = $partition->{'start'} - 1;
	     # size = (number of cylinders) * (unit in bloks)
	     # number of cylinders = last cylinder - first cylinder + 1
	     my $fsize = ($fend - $fstart + 1) * ($unit / 1024);
             if ($fend > $max_cyl) { 
	        $max_cyl = $fend; 
	     }
             if ($fstart ne $fend) {
	        &update_partition ($disk, "/dev/empty$empty_count", "", "empty", "noauto", 0, $fsize, 0,
	                           0, 1, "", $fstart, $fend);
		$empty_count ++;
	     }
	  }
       } else {
	  if ($partition->{'start'} ne 1) {
	     # There is no partition, free space found in the top of the disk
	     my $fstart = 1;
             my $fend = $partition->{'start'} - 1;
	     # size = (number of cylinders) * (unit in bloks)
	     # number of cylinders = last cylinder - first cylinder + 1
             my $fsize = ($fend - $fstart + 1) * ($unit / 1024);
             if ($fend > $max_cyl) {
		$max_cyl = $fend;
	     }
	     if ($fend ne $fstart) {
		&update_partition ($disk, "/dev/empty$empty_count", "", "empty", "noauto", 0, $fsize, 0,
	                           0, 1, "", $fstart, $fend);
		$empty_count ++;
	     }
	  }
       }

       # partition found update it
       if ($partition->{'end'} ne $partition->{'start'}) {
	  &update_partition ($disk, $partition->{'device'}, "", $partition->{'type'}, "noauto", 0, 
	                     $partition->{'size'}, 0, $partition->{'bootable'}, 1, "", 
			     $partition->{'start'}, $partition->{'end'});
       }
       
       if ($partition->{'end'} eq $last_end) {
	  $prev_end = 0;
       } else {
	  $prev_end = $partition->{'end'};
       }	
    } 
    
    # Now we are out of the loop and we have to check if the current disk is 
    # valid
    if ($disk ne "") {
       if ($max_cyl lt $last_end and $max_cyl lt ($last_end - 1)) {
	  # There is no partition, free space found in the bottom of the disk
	  my $fstart = $max_cyl + 1;
          my $fend = $last_end;
          # size = (number of cylinders) * (unit in bloks)
          # number of cylinders = last cylinder - first cylinder + 1
          my $fsize = ($fend - $fstart + 1) * ($unit / 1024);
          &update_partition ($disk, "/dev/empty$empty_count", "", "empty", "noauto", 0, $fsize, 0,
                             0, 1, "", $fstart, $fend);
       }
    }

    # Time to get the model and media type
    # We support IDE, SCSI and USB medias in general
    # For the moment, we fix a IDE device

    $media_type = &get_media_type ($dev);
    if ($media_type eq "disk-ide") {
       ($dev_clean) = ($dev =~ /^\/dev\/([a-zA-Z0-9]*)$/);
       $model = `cat /proc/ide/$dev_clean/model 2>/dev/null`;
       if ($model ne "") { &update_disk_data ($dev, "model", $model); }
       $media = `cat /proc/ide/$dev_clean/media 2>/dev/null`;
       if ($media ne "") {
	  &update_disk_data ($dev, "media", $media);
       }
       $dma = &get_ide_setting ($dev_clean, "using_dma");
       if ($dma ne "") { &update_disk_data ($dev, "dma", $dma); }
    } elsif ($media_type eq "disk-scsi") {
       $head = `fdisk -l $dev 2>/dev/null`;
       if ($head ne "") {
	  &update_disk_data ($dev, "media", "disk");
       }
    } elsif ($media_type eq "usb-storage") {
    }
    &gst_file_close ($fd);

    &gst_report ("disks_size_query", $dev);
    
    # Floppy disk support
    $floppy = `grep fd /proc/devices | wc -l`;
    chomp $floopy;
    if ($floppy >= 1)
    {
       $dev="/dev/fd0";
       &update_disk_data ($dev, "media", "floppy"); 
    } elsif (system ("fdisk /dev/fd1 > /dev/null 2>&1")) {
       $dev="/dev/fd1";
       &update_disk_data ($dev, "media", "floppy");
    }

    &gst_print_progress();
  }
}

sub get_scsi_options
{
   my $proc_scsi_file;
   local *PROC_SCSI_FILE;
   $proc_scsi_file = &gst_file_open_read_from_names ("/proc/scsi/scsi");
   if (not $proc_scsi_file) { return; }
   *PROC_SCSI_FILE = $proc_scsi_file;

   my $disk = 'a';
   my $cdrom = 0;
   my ($vendor, $model, $dev);
   while (<PROC_SCSI_FILE>) {
      my $data = $_;
      $data =~ s/^\s*//;
      $data =~ s/\s*$//;
      @line = split(":", $data);
      if ($line[0] eq "Vendor") {
	 ($vendor, $model) = ($data =~ /^Vendor:(.*)Model:(.*)Rev:.*$/);
	 $vendor =~ s/^\s*//;
	 $vendor =~ s/\s*$//;
	 $model  =~ s/^\s*//;
	 $model  =~ s/\s*$//;
      } elsif ($line[0] eq "Type") {
	 if ($data =~ /CD-ROM/) {
	    $dev = "/dev/sr$cdrom";
	    $cdrom++;
	 } elsif ($data =~ /Direct-Access/) {
	    $dev = "/dev/sd$disk";
	    $disk++;
	 } 
	 if ($dev ne "") { &update_disk_data ($dev, "model", "$vendor $model"); }
      }  
   }

   close (PROC_SCSI_FILE);
}

# --- XML parsing --- #

sub xml_parse
{
  # Scan XML to tree.

  $tree = &gst_xml_scan;

  # Walk the tree recursively and extract configuration parameters.
  # This is the top level - find and enter the toplevel tag.

  while (@$tree)
  {
    if ($$tree[0] eq "disks") { &xml_parse_toplevel($$tree[1]); }

    shift @$tree;
    shift @$tree;
  }

  return($tree);
}


sub xml_parse_toplevel
{
  my $tree = $_[0];

  shift @$tree;  # Skip attributes.

  while (@$tree)
  {
    if ($$tree[0] eq "disk") { &xml_parse_disk($$tree[1]); }

    shift @$tree;
    shift @$tree;
  }
}


sub xml_parse_disk
{
  my %disk;

  my $tree = $_[0];
  shift @$tree;  # Skip attributes.

  %disk->{partitions} = [];  # Init partition list.

  while (@$tree)
  {
    if ($$tree[0] eq "device") { %disk->{device} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "size") { %disk->{size} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "partition")
    {
      my %partition = &xml_parse_partition($$tree[1]);
      $bleh = %disk->{partitions};
      push(@$bleh, \%partition);
    }
    
    shift @$tree; shift @$tree;
  }

  push(@cf_disks, \%disk);
}


sub xml_parse_partition
{
  my %partition;

  my $tree = $_[0];
  shift @$tree;  # Skip attributes.

  while (@$tree)
  {
    if ($$tree[0] eq "device") { %partition->{device} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "type") { %partition->{type} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "point") { %partition->{point} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "label") { %partition->{label} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "size") { %partition->{size} = &gst_xml_get_word($$tree[1]); }
    elsif ($$tree[0] eq "bootable") { %partition->{bootable} = &xml_parse_state($$tree[1]); }
    elsif ($$tree[0] eq "integritycheck") { %partition->{check} = &xml_parse_state($$tree[1]); }
    elsif ($$tree[0] eq "mounted") { %partition->{mounted} = &xml_parse_state($$tree[1]); }
    elsif ($$tree[0] eq "listed") { %partition->{listed} = &xml_parse_state($$tree[1]); }
    elsif ($$tree[0] eq "detected") { %partition->{detected} = &xml_parse_state($$tree[1]); }
    
    shift @$tree; shift @$tree;
  }

  return(%partition);
}


sub xml_parse_state
{
  my $tree = $_[0];

  # Check attribute; 'yes', 'true', 'no', 'false'.

  return(&gst_util_read_boolean($$tree[0]->{state}));
}


# --- XML printing --- #


sub xml_print
{
  # print Dumper (@cf_disks);
  
  &gst_xml_print_begin ();

  &gst_xml_print_line ("<!-- Local fixed media and partitions -->\n");
  &gst_xml_print_vspace ();

  my @disks = @cf_disks;

  while (@disks)
  {
    if ($disks[0])
    {
      my $disk = $disks[0];
      
      &gst_xml_print_vspace ();
      &gst_xml_print_line ("<disk>\n");
      &gst_xml_enter ();

      &gst_xml_print_line ("<device>" . %$disk->{device} . "</device>\n");
      
      if (%$disk->{size}) { 
          # The frontend wants KB
          &gst_xml_print_line ("<size>" . %$disk->{size}*(%$disk->{block_size}/1024) . "</size>\n"); 
      }
      if (%$disk->{media}) { &gst_xml_print_line ("<media>" . %$disk->{media} . "</media>\n"); }
      if (%$disk->{model}) { &gst_xml_print_line ("<model>" . %$disk->{model} . "</model>\n"); }
      if (%$disk->{media} =~ /cdrom/) { 
	 &get_removable_storage_info (%$disk->{device});
	 &gst_xml_print_state_tag ("play-audio",   %$disk->{"play-audio"});
	 &gst_xml_print_state_tag ("write-cdr",    %$disk->{"write-cdr"});
	 &gst_xml_print_state_tag ("write-cdrw",   %$disk->{"write-cdrw"});
	 &gst_xml_print_state_tag ("read-dvd",     %$disk->{"read-dvd"});
	 &gst_xml_print_state_tag ("write-dvdr",   %$disk->{"write-dvdr"});
	 &gst_xml_print_state_tag ("write-dvdram", %$disk->{"write-dvdram"});
      }
      if (%$disk->{dma}) { &gst_xml_print_state_tag ("dma", %$disk->{dma}); }
      
      my $partitions = %$disk->{partitions};
      
      while (@$partitions)
      {
        my $partition = $$partitions[0];

        &gst_xml_print_vspace ();
        &gst_xml_print_line ("<partition>\n");
        &gst_xml_enter ();

        &gst_xml_print_line ("<device>" . %$partition->{device} . "</device>\n");
        if (%$partition->{type}) { &gst_xml_print_line ("<type>" . %$partition->{type} . "</type>\n"); }
        if (%$partition->{point}) { &gst_xml_print_line ("<point>" . %$partition->{point} . "</point>\n"); }
        if (%$partition->{label}) { &gst_xml_print_line ("<label>" . %$partition->{label} . "</label>\n"); }
        if (%$partition->{size})
        {
          # The frontend wants KB 
          &gst_xml_print_line ("<size>" . %$partition->{size}*(%$disk->{block_size}/1024) . "</size>\n");
        }

	if (%$partition->{start}) { &gst_xml_print_line ("<start>" . %$partition->{start} . "</start>\n"); }
	if (%$partition->{end}) { &gst_xml_print_line ("<end>" . %$partition->{end} . "</end>\n"); }

        &gst_xml_print_state_tag ("bootable", %$partition->{bootable});
        &gst_xml_print_state_tag ("integritycheck", %$partition->{check});
        &gst_xml_print_state_tag ("mounted", %$partition->{mounted});
        if (%$partition->{free})
        {
          # The frontend wants KB
          &gst_xml_print_line ("<free>" . %$partition->{free}*(%$disk->{block_size}/1024) . "</free>\n");
        }        
        &gst_xml_print_state_tag ("listed", %$partition->{listed});
        &gst_xml_print_state_tag ("detected", %$partition->{detected});

        &gst_xml_leave ();
        &gst_xml_print_line ("</partition>\n");
        
        shift @$partitions;
      }

      &gst_xml_leave ();
      &gst_xml_print_line ("</disk>\n");
      &gst_xml_print_vspace ();
    }
  
    shift @disks;
  }

  &gst_xml_print_end ();
}


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

sub get
{
  setlocale (LC_ALL, "en_US");
  
  &get_cdroms;
  &get_cdrom_settings;
  &get_fdisk;
  &get_scsi_options;
  &read_fstab;

  &gst_report_end ();
  &xml_print ();
}


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


sub set_immediate
{
  my $mount_tool;
  my $umount_tool;

  $mount_tool = &gst_file_locate_tool("mount");
  $umount_tool = &gst_file_locate_tool("umount");

  # Count partitions.

  my $i; my $j;
  my $num_partitions = 0;
  my $num_done = 0;
  
  for ($i = 0; $cf_disks[$i]; $i++)
  {
    for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++)
    {
      $num_partitions++;
    }
  }

  # Update mount status.

  if (($mount_tool ne "") && ($umount_tool ne ""))
  {
    my $i; my $j;

    for ($i = 0; $cf_disks[$i]; $i++)
    {
      for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++)
      {
        if ($cf_disks[$i]->{partitions}[$j]->{mounted})
        {
          &gst_report ("disks_mount", $cf_disks[$i]->{partitions}[$j]->{device});
          system "$mount_tool " . $cf_disks[$i]->{partitions}[$j]->{device} . " " .
                 $cf_disks[$i]->{partitions}[$j]->{point} . " >/dev/null 2>/dev/null";
        }
        else
        {
          &gst_report ("disks_umount", $cf_disks[$i]->{partitions}[$j]->{device});
          system "$umount_tool " . $cf_disks[$i]->{partitions}[$j]->{device} . " >/dev/null 2>/dev/null";
        }
	
        $num_done++;
        &gst_progress(10 + (80 / ($num_partitions - $num_done + 1)));
      }
    }
  }
  else
  {
    &gst_report ("disks_mount_error");
  }
  
  &gst_progress(90);
}


sub set
{
  &xml_parse ();

  &write_fstab; &gst_progress(10);

  if ($gst_do_immediate)
  {
    &set_immediate;
  }
  
  &gst_report_end ();
}


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


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

# --- Test XML file: return to the frontend a fixed XML file --- #
sub test_xml
{
    my ($tool, $file) = @_;

    &gst_report_end ();
    &gst_progress(100);
    # lazy boy - acs
    system ("cat $file");
}

sub get_removable_storage_info
{
   my ($device) = @_;

   if (!open (RD, "<$device")) {
      &gst_xml_print_state_tag ("empty", 1);
      return;
   }

   &gst_xml_print_state_tag ("empty", 0);

   if ($^O eq 'linux') {
      $CDROMREADTOCHDR   = 0x5305;
      $CDROMREADTOCENTRY = 0x5306;
   } elsif ($^O =~ /bsd/) {
      $CDROMREADTOCHDR   = 0x40046304;
      $CDROMREADTOCENTRY = 0xc0086305;
   } elsif (($^O eq 'solaris') || ($^O eq 'sunos')) {
      $CDROMREADTOCHDR   = 0x49b;
      $CDROMREADTOCENTRY = 0x49c;
   } else {
      return;
   }

   $CDROM_DATA_TRACK = 0x04;
   $CDROM_MSF = 0x02;
   

   if (!ioctl (RD, 0x5305, $tochdr)) {
      &gst_xml_print_line ("<type-content>" . "blank"  . "</type-content>\n");
      close (RD);
      return;
   }
   
   my ($start, $end);
   if ($^O =~ /bsd/) {
      ($start, $end) = unpack "CC", (substr $tochdr, 2, 2);
   } else {
      ($start, $end) = unpack "CC", $tochdr;
   }
   
   for (my $i = $start; $i <= $end; $i++ ) {
      push @tracks, $i;
   }
   push @tracks, 0xAA;

   $audio = 0;
   $data = 0;
   foreach (@tracks) {
      ioctl (RD, 0x5306, $tocentry = pack ("CCC", $_, 0, $CDROM_MSF, 0, 0, 0, 0));
      my @times = unpack ("CCCCCCCCC", $tocentry);
      if ($times[1] & ($CDROM_DATA_TRACK << 4)) {
	 $data ++;
      } else {
	 $audio ++;
      }
   }

   close (RD);

   if ($data gt 0) {
      if ($audio gt 0) {
	 &gst_xml_print_line ("<type-content>" . "mixed"  . "</type-content>\n");
      } else {
	 &gst_xml_print_line ("<type-content>" . "data"  . "</type-content>\n");
      }
   } else {
      &gst_xml_print_line ("<type-content>" . "audio"  . "</type-content>\n");
   }
}
  
sub removable_info
{
   my ($tool, $device) = @_;
   
   &gst_report_end ();
   &gst_xml_print_begin ("removable_info");
   &get_removable_storage_info ($device);
   &gst_xml_print_end ("removable_info");
}	       

   
# --- Calculates the speed of a device in human readable Kib/sec, Mib/sec, etc.--- #
sub get_dev_speed
{
   my ($device) = @_;

   use IO::Handle;
   use Time::HiRes qw( setitimer getitimer ITIMER_REAL );

   sysopen (HANDLE, $device, O_RDONLY|O_NONBLOCK);

   IO::Handle::sync (HANDLE);
   sleep (3);
   flush STDOUT;

   setitimer (ITIMER_REAL, (1000.0, 1000.0));

   $max_iterations = 1024;
   $iterations = 0;
   ($e11, $e12) = getitimer (ITIMER_REAL);

   do {
      ++$iterations;
      if (($rc = sysread (HANDLE, $buf, (2 * 1024 * 1024))) != (2 * 1024 * 1024)) {
	 return;
      }
      for ($i = 0; $i < (2 * 1024 * 1024); $i += 512) {
	 $buf[$i] &= 1;
      }
      ($e21, $e22) = getitimer (ITIMER_REAL);
      $elapsed = ($e11 - $e21) + (($e12 - $e22) / 1000000.0);
   } while ($elapsed < 3.0 and $iterations < $max_iterations);

   close HANDLE;

   $total = ($iterations * 2) / $elapsed;

   if ($total > 1.0) { # more than 1MiB/s 
      $total =~ s/^([0-9]+\.[0-9][0-9]).+/$1/;
      &gst_xml_print_line ("<speed>" . "$total MiB/sec"  . "</speed>\n");
   } else {
      $total *= 1024;
      $total =~ s/^([0-9]+\.[0-9][0-9]).+/$1/;
      &gst_xml_print_line ("<speed>" . "$total KiB/sec"  . "</speed>\n");
   }
}

sub dev_speed
{
   my ($tool, $device) = @_;

   &gst_report_end ();
   &gst_xml_print_begin ("dev_speed");
   &get_dev_speed ($device);
   &gst_xml_print_end ("dev_speed");
}

# --- Main --- #

# get, set and filter are special cases that don't need more parameters than a ref to their function.
# Read general.pl.in:gst_run_directive to know about the format of this hash.

$directives = {
  "get"       => [ \&get,      [], "" ],
  "set"       => [ \&set,      [], "" ],
  "filter"    => [ \&filter,   [], "" ],
  "test_xml"  => [ \&test_xml, ["test_xml_file"],
                  "Return a XML file to the frontend." ],
  "dev_speed" => [ \&dev_speed, ["device"],
                  "Return the speed of a device in kb/s" ],
  "removable_info" => [ \&removable_info, ["device"],
                       "Return info about the removable storages"],
};

$tool = &gst_init ($name, $version, $description, $directives, @ARGV);
&gst_platform_ensure_supported ($tool, @platforms);
&gst_run ($tool);
